From e89b554ae0af20136e11742c1ea04df20df390ba Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Sep 2023 14:38:18 +0200 Subject: [PATCH 1/9] frontend-plugin-api: refactor to remove root modules Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/api-report.md | 3 ++ .../src/extensions/createApiExtension.ts | 8 +-- .../extensions/createPageExtension.test.tsx | 4 +- .../src/extensions/createPageExtension.tsx | 4 +- packages/frontend-plugin-api/src/index.ts | 15 +----- .../{ => schema}/createSchemaFromZod.test.ts | 0 .../src/{ => schema}/createSchemaFromZod.ts | 7 +-- .../frontend-plugin-api/src/schema/index.ts | 18 +++++++ .../frontend-plugin-api/src/schema/types.ts | 23 +++++++++ .../src/wiring/coreExtensionData.ts | 27 ++++++++++ .../{types.ts => wiring/createExtension.ts} | 49 ++----------------- .../src/wiring/createExtensionDataRef.ts | 28 +++++++++++ .../src/wiring/createPlugin.test.ts | 10 ++-- .../src/wiring/createPlugin.ts | 2 +- .../frontend-plugin-api/src/wiring/index.ts | 12 +++++ .../frontend-plugin-api/src/wiring/types.ts | 40 +++++++++++++++ 16 files changed, 169 insertions(+), 81 deletions(-) rename packages/frontend-plugin-api/src/{ => schema}/createSchemaFromZod.test.ts (100%) rename packages/frontend-plugin-api/src/{ => schema}/createSchemaFromZod.ts (93%) create mode 100644 packages/frontend-plugin-api/src/schema/index.ts create mode 100644 packages/frontend-plugin-api/src/schema/types.ts create mode 100644 packages/frontend-plugin-api/src/wiring/coreExtensionData.ts rename packages/frontend-plugin-api/src/{types.ts => wiring/createExtension.ts} (55%) create mode 100644 packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts create mode 100644 packages/frontend-plugin-api/src/wiring/types.ts diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index a27875bcf8..e83d963348 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -80,6 +80,9 @@ export function createExtension< TConfig = never, >(options: CreateExtensionOptions): Extension; +// @public (undocumented) +export function createExtensionDataRef(id: string): ExtensionDataRef; + // @public (undocumented) export interface CreateExtensionOptions< TData extends AnyExtensionDataMap, diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts index 5dab38aec8..d59a7ab2c3 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts @@ -15,13 +15,13 @@ */ import { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api'; -import { PortableSchema } from '../createSchemaFromZod'; +import { PortableSchema } from '../schema'; import { AnyExtensionDataMap, - coreExtensionData, - createExtension, ExtensionDataValue, -} from '../types'; + createExtension, + coreExtensionData, +} from '../wiring'; /** @public */ export function createApiExtension< diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx index f1caaf7fdf..e5939d24ea 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx @@ -15,8 +15,8 @@ */ import React from 'react'; -import { PortableSchema } from '../createSchemaFromZod'; -import { coreExtensionData } from '../types'; +import { PortableSchema } from '../schema'; +import { coreExtensionData } from '../wiring'; import { createPageExtension } from './createPageExtension'; describe('createPageExtension', () => { diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index fb2d930844..e136f1bc2a 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -17,14 +17,14 @@ import { RouteRef } from '@backstage/core-plugin-api'; import React from 'react'; import { ExtensionBoundary } from '../components'; -import { createSchemaFromZod, PortableSchema } from '../createSchemaFromZod'; +import { createSchemaFromZod, PortableSchema } from '../schema'; import { AnyExtensionDataMap, coreExtensionData, createExtension, Extension, ExtensionDataValue, -} from '../types'; +} from '../wiring'; /** * Helper for creating extensions for a routable React page component. diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts index 16f226c483..4e1a383899 100644 --- a/packages/frontend-plugin-api/src/index.ts +++ b/packages/frontend-plugin-api/src/index.ts @@ -20,21 +20,8 @@ * @packageDocumentation */ -export { - createSchemaFromZod, - type PortableSchema, -} from './createSchemaFromZod'; export * from './components'; export * from './extensions'; -export { - coreExtensionData, - createExtension, - type AnyExtensionDataMap, - type CreateExtensionOptions, - type Extension, - type ExtensionDataBind, - type ExtensionDataRef, - type ExtensionDataValue, -} from './types'; +export * from './schema'; export * from './wiring'; export * from './routing'; diff --git a/packages/frontend-plugin-api/src/createSchemaFromZod.test.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.test.ts similarity index 100% rename from packages/frontend-plugin-api/src/createSchemaFromZod.test.ts rename to packages/frontend-plugin-api/src/schema/createSchemaFromZod.test.ts diff --git a/packages/frontend-plugin-api/src/createSchemaFromZod.ts b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts similarity index 93% rename from packages/frontend-plugin-api/src/createSchemaFromZod.ts rename to packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts index 447cea8bbd..bfaf57f522 100644 --- a/packages/frontend-plugin-api/src/createSchemaFromZod.ts +++ b/packages/frontend-plugin-api/src/schema/createSchemaFromZod.ts @@ -17,12 +17,7 @@ import { JsonObject } from '@backstage/types'; import { z, ZodSchema, ZodTypeDef } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; - -/** @public */ -export type PortableSchema = { - parse: (input: unknown) => TOutput; - schema: JsonObject; -}; +import { PortableSchema } from './types'; /** @public */ export function createSchemaFromZod( diff --git a/packages/frontend-plugin-api/src/schema/index.ts b/packages/frontend-plugin-api/src/schema/index.ts new file mode 100644 index 0000000000..7f21c4e07d --- /dev/null +++ b/packages/frontend-plugin-api/src/schema/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2023 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. + */ + +export { createSchemaFromZod } from './createSchemaFromZod'; +export { type PortableSchema } from './types'; diff --git a/packages/frontend-plugin-api/src/schema/types.ts b/packages/frontend-plugin-api/src/schema/types.ts new file mode 100644 index 0000000000..9636d3b896 --- /dev/null +++ b/packages/frontend-plugin-api/src/schema/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2023 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 { JsonObject } from '@backstage/types'; + +/** @public */ +export type PortableSchema = { + parse: (input: unknown) => TOutput; + schema: JsonObject; +}; diff --git a/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts new file mode 100644 index 0000000000..1f4e4370c9 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/coreExtensionData.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2023 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 { AnyApiFactory, RouteRef } from '@backstage/core-plugin-api'; +import { ComponentType } from 'react'; +import { createExtensionDataRef } from './createExtensionDataRef'; + +/** @public */ +export const coreExtensionData = { + reactComponent: createExtensionDataRef('core.reactComponent'), + routePath: createExtensionDataRef('core.routing.path'), + apiFactory: createExtensionDataRef('core.api.factory'), + routeRef: createExtensionDataRef('core.routing.ref'), +}; diff --git a/packages/frontend-plugin-api/src/types.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts similarity index 55% rename from packages/frontend-plugin-api/src/types.ts rename to packages/frontend-plugin-api/src/wiring/createExtension.ts index 940e821af5..e7f673cd2f 100644 --- a/packages/frontend-plugin-api/src/types.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -14,35 +14,9 @@ * limitations under the License. */ -import { AnyApiFactory } from '@backstage/core-plugin-api'; -import { RouteRef } from '@backstage/core-plugin-api'; -import { ComponentType } from 'react'; -import { PortableSchema } from './createSchemaFromZod'; -import { BackstagePlugin } from './wiring'; - -/** @public */ -export type ExtensionDataRef = { - id: string; - T: T; - $$type: 'extension-data'; -}; - -/** @public */ -// TODO: change to options object with ID. -export function createExtensionDataRef(id: string): ExtensionDataRef { - return { id, $$type: 'extension-data' } as ExtensionDataRef; -} - -/** @public */ -export const coreExtensionData = { - reactComponent: createExtensionDataRef('core.reactComponent'), - routePath: createExtensionDataRef('core.routing.path'), - apiFactory: createExtensionDataRef('core.api.factory'), - routeRef: createExtensionDataRef('core.routing.ref'), -}; - -/** @public */ -export type AnyExtensionDataMap = Record>; +import { PortableSchema } from '../schema'; +import { BackstagePlugin } from './createPlugin'; +import { AnyExtensionDataMap, Extension } from './types'; /** @public */ export type ExtensionDataBind = { @@ -78,23 +52,6 @@ export interface CreateExtensionOptions< }): void; } -/** @public */ -export interface Extension { - $$type: 'extension'; - id: string; - at: string; - disabled: boolean; - inputs: Record; - output: AnyExtensionDataMap; - configSchema?: PortableSchema; - factory(options: { - source?: BackstagePlugin; - bind: ExtensionDataBind; - config: TConfig; - inputs: Record>>; - }): void; -} - /** @public */ export function createExtension< TData extends AnyExtensionDataMap, diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts new file mode 100644 index 0000000000..10a931a65d --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2023 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. + */ + +/** @public */ +export type ExtensionDataRef = { + id: string; + T: T; + $$type: 'extension-data'; +}; + +/** @public */ +// TODO: change to options object with ID. +export function createExtensionDataRef(id: string): ExtensionDataRef { + return { id, $$type: 'extension-data' } as ExtensionDataRef; +} diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts index 36389d7e63..7ebcb62ceb 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts @@ -17,14 +17,12 @@ import React from 'react'; import { createApp } from '@backstage/frontend-app-api'; import { render, screen } from '@testing-library/react'; -import { createSchemaFromZod } from '../createSchemaFromZod'; -import { - createExtension, - coreExtensionData, - createExtensionDataRef, -} from '../types'; +import { createSchemaFromZod } from '../schema/createSchemaFromZod'; import { createPlugin, BackstagePlugin } from './createPlugin'; import { JsonObject } from '@backstage/types'; +import { createExtension } from './createExtension'; +import { createExtensionDataRef } from './createExtensionDataRef'; +import { coreExtensionData } from './coreExtensionData'; const nameExtensionDataRef = createExtensionDataRef('name'); diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.ts index 3e15fb0b19..7f7e44ff03 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Extension } from '../types'; +import { Extension } from './types'; /** @public */ export interface PluginOptions { diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index d96ba09971..1a243eb2e3 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -14,8 +14,20 @@ * limitations under the License. */ +export { coreExtensionData } from './coreExtensionData'; +export { + createExtension, + type CreateExtensionOptions, + type ExtensionDataBind, + type ExtensionDataValue, +} from './createExtension'; +export { + createExtensionDataRef, + type ExtensionDataRef, +} from './createExtensionDataRef'; export { createPlugin, type BackstagePlugin, type PluginOptions, } from './createPlugin'; +export type { AnyExtensionDataMap, Extension } from './types'; diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts new file mode 100644 index 0000000000..3779a5c676 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2023 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 { PortableSchema } from '../schema'; +import { ExtensionDataBind } from './createExtension'; +import { ExtensionDataRef } from './createExtensionDataRef'; +import { BackstagePlugin } from './createPlugin'; + +/** @public */ +export type AnyExtensionDataMap = Record>; + +/** @public */ +export interface Extension { + $$type: 'extension'; + id: string; + at: string; + disabled: boolean; + inputs: Record; + output: AnyExtensionDataMap; + configSchema?: PortableSchema; + factory(options: { + source?: BackstagePlugin; + bind: ExtensionDataBind; + config: TConfig; + inputs: Record>>; + }): void; +} From e36fae2c2a6e9a213ccfb30fee7774d2f803e391 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Sep 2023 14:59:28 +0200 Subject: [PATCH 2/9] frontend-plugin-api: initial createExtension test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Co-authored-by: Camila Belo Co-authored-by: Philipp Hugenroth Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtension.test.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 packages/frontend-plugin-api/src/wiring/createExtension.test.ts diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts new file mode 100644 index 0000000000..69ff83dfe9 --- /dev/null +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2023 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 { createExtension } from './createExtension'; +import { createExtensionDataRef } from './createExtensionDataRef'; + +const stringData = createExtensionDataRef('string'); + +describe('createExtension', () => { + it('should create an extension with a simple output', () => { + const extension = createExtension({ + id: 'test', + at: 'root', + output: { + foo: coreExtensionData.title, + foo2: stringData, + }, + factory({ bind }) { + // Make it work well with required and optional output + // HOH - High Order Helper + bind({ + foo: 'bar', + }); + // @ts-expect-error + bind.foo(3); + // @ts-expect-error + bind.foo(); + // @ts-expect-error + bind.bar('bar'); + }, + }); + expect(extension.id).toBe('test'); + }); +}); From d1a1cad0e713bca807f81dc49b873a04403b3dca Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Sep 2023 16:31:37 +0200 Subject: [PATCH 3/9] frontend-plugin-api: swtich extension factory to bind all outputs at once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Johan Haals Co-authored-by: Fredrik Adelöw Co-authored-by: Philipp Hugenroth Signed-off-by: Patrik Oldsberg --- .../src/createExtensionInstance.ts | 14 +++++-- .../src/extensions/CoreRouter.tsx | 12 +++--- .../src/extensions/createApiExtension.ts | 4 +- .../src/extensions/createPageExtension.tsx | 23 ++++++----- .../src/wiring/createExtension.test.ts | 19 +++++---- .../src/wiring/createExtension.ts | 40 +++++++++++++------ .../src/wiring/createPlugin.test.ts | 19 ++++----- 7 files changed, 81 insertions(+), 50 deletions(-) diff --git a/packages/frontend-app-api/src/createExtensionInstance.ts b/packages/frontend-app-api/src/createExtensionInstance.ts index a7c49d48cb..d767f21d58 100644 --- a/packages/frontend-app-api/src/createExtensionInstance.ts +++ b/packages/frontend-app-api/src/createExtensionInstance.ts @@ -54,9 +54,17 @@ export function createExtensionInstance(options: { extension.factory({ source, config: parsedConfig, - bind: mapValues(extension.output, ref => { - return (value: unknown) => extensionData.set(ref.id, value); - }), + bind: namedOutputs => { + for (const [name, output] of Object.entries(namedOutputs)) { + const ref = extension.output[name]; + if (!ref) { + throw new Error( + `Extension instance '${extension.id}' tried to bind unknown output '${name}'`, + ); + } + extensionData.set(ref.id, output); + } + }, inputs: mapValues( extension.inputs, ({ extensionData: pointData }, inputName) => { diff --git a/packages/frontend-app-api/src/extensions/CoreRouter.tsx b/packages/frontend-app-api/src/extensions/CoreRouter.tsx index f2a760d25e..05f84aba6d 100644 --- a/packages/frontend-app-api/src/extensions/CoreRouter.tsx +++ b/packages/frontend-app-api/src/extensions/CoreRouter.tsx @@ -47,10 +47,12 @@ export const CoreRouter = createExtension({ return element; }; - bind.component(() => ( - - - - )); + bind({ + component: () => ( + + + + ), + }); }, }); diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts index d59a7ab2c3..057addc4a8 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts @@ -63,9 +63,9 @@ export function createApiExtension< }, factory({ bind, config, inputs }) { if (typeof factory === 'function') { - bind.api(factory({ config, inputs })); + bind({ api: factory({ config, inputs }) }); } else { - bind.api(factory); + bind({ api: factory }); } }, }); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index e136f1bc2a..7f16a81e24 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -82,17 +82,18 @@ export function createPageExtension< .component({ config, inputs }) .then(element => ({ default: () => element })), ); - bind.path(config.path); - bind.component(() => ( - - - - - - )); - if (options.routeRef) { - bind.routeRef!(options.routeRef); - } + + bind({ + path: config.path, + component: () => ( + + + + + + ), + routeRef: options.routeRef, + }); }, }); } diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 69ff83dfe9..14b1fd2d50 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -25,21 +25,26 @@ describe('createExtension', () => { id: 'test', at: 'root', output: { - foo: coreExtensionData.title, - foo2: stringData, + foo: stringData, }, factory({ bind }) { - // Make it work well with required and optional output - // HOH - High Order Helper bind({ foo: 'bar', }); + bind({ + // @ts-expect-error + foo: 3, + }); + bind({ + // @ts-expect-error + bar: 'bar', + }); // @ts-expect-error - bind.foo(3); + bind({}); // @ts-expect-error - bind.foo(); + bind(); // @ts-expect-error - bind.bar('bar'); + bind('bar'); }, }); expect(extension.id).toBe('test'); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index e7f673cd2f..1ef0d3c272 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -19,9 +19,9 @@ import { BackstagePlugin } from './createPlugin'; import { AnyExtensionDataMap, Extension } from './types'; /** @public */ -export type ExtensionDataBind = { - [K in keyof TData]: (value: TData[K]['T']) => void; -}; +export type ExtensionDataBind = (outputs: { + [K in keyof TOutput]: TOutput[K]['T']; +}) => void; /** @public */ export type ExtensionDataValue = { @@ -30,23 +30,23 @@ export type ExtensionDataValue = { /** @public */ export interface CreateExtensionOptions< - TData extends AnyExtensionDataMap, - TPoint extends Record, + TOutput extends AnyExtensionDataMap, + TInputs extends Record, TConfig, > { id: string; at: string; disabled?: boolean; - inputs?: TPoint; - output: TData; + inputs?: TInputs; + output: TOutput; configSchema?: PortableSchema; factory(options: { source?: BackstagePlugin; - bind: ExtensionDataBind; + bind: ExtensionDataBind; config: TConfig; inputs: { - [pointName in keyof TPoint]: ExtensionDataValue< - TPoint[pointName]['extensionData'] + [pointName in keyof TInputs]: ExtensionDataValue< + TInputs[pointName]['extensionData'] >[]; }; }): void; @@ -54,14 +54,28 @@ export interface CreateExtensionOptions< /** @public */ export function createExtension< - TData extends AnyExtensionDataMap, - TPoint extends Record, + TOutput extends AnyExtensionDataMap, + TInputs extends Record, TConfig = never, ->(options: CreateExtensionOptions): Extension { +>( + options: CreateExtensionOptions, +): Extension { return { ...options, disabled: options.disabled ?? false, $$type: 'extension', inputs: options.inputs ?? {}, + factory({ bind, config, inputs }) { + // TODO: Simplify this, but TS wouldn't infer the input type for some reason + return options.factory({ + bind, + config, + inputs: inputs as { + [pointName in keyof TInputs]: ExtensionDataValue< + TInputs[pointName]['extensionData'] + >[]; + }, + }); + }, }; } diff --git a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts index 7ebcb62ceb..724dad5143 100644 --- a/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createPlugin.test.ts @@ -33,7 +33,7 @@ const TechRadarPage = createExtension({ name: nameExtensionDataRef, }, factory({ bind }) { - bind.name('TechRadar'); + bind({ name: 'TechRadar' }); }, }); @@ -47,7 +47,7 @@ const CatalogPage = createExtension({ z.object({ name: z.string().default('Catalog') }), ), factory({ bind, config }) { - bind.name(config.name); + bind({ name: config.name }); }, }); @@ -61,7 +61,7 @@ const TechDocsAddon = createExtension({ z.object({ name: z.string().default('TechDocsAddon') }), ), factory({ bind, config }) { - bind.name(config.name); + bind({ name: config.name }); }, }); @@ -79,7 +79,7 @@ const TechDocsPage = createExtension({ name: nameExtensionDataRef, }, factory({ bind, inputs }) { - bind.name(`TechDocs-${inputs.addons.map(n => n.name).join('-')}`); + bind({ name: `TechDocs-${inputs.addons.map(n => n.name).join('-')}` }); }, }); @@ -97,11 +97,12 @@ const outputExtension = createExtension({ component: coreExtensionData.reactComponent, }, factory({ bind, inputs }) { - bind.component(() => - React.createElement('span', {}, [ - `Names: ${inputs.names.map(n => n.name).join(', ')}`, - ]), - ); + bind({ + component: () => + React.createElement('span', {}, [ + `Names: ${inputs.names.map(n => n.name).join(', ')}`, + ]), + }); }, }); From 46e36bfad0f1067f67932814606a15fd1e271efa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Sep 2023 17:01:50 +0200 Subject: [PATCH 4/9] frontend-plugin-api: initial optional extension output implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Co-authored-by: Philipp Hugenroth Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtension.test.ts | 38 +++++++++++++++++++ .../src/wiring/createExtension.ts | 22 +++++++++-- .../src/wiring/createExtensionDataRef.ts | 31 ++++++++++++--- .../frontend-plugin-api/src/wiring/types.ts | 2 +- 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 14b1fd2d50..104f03c84d 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -49,4 +49,42 @@ describe('createExtension', () => { }); expect(extension.id).toBe('test'); }); + + it('should create an extension with a some optional output', () => { + const extension = createExtension({ + id: 'test', + at: 'root', + output: { + foo: stringData, + bar: stringData.optional(), + }, + factory({ bind }) { + bind({ + foo: 'bar', + }); + bind({ + foo: 'bar', + bar: 'baz', + }); + bind({ + // @ts-expect-error + foo: 3, + }); + bind({ + foo: 'bar', + // @ts-expect-error + bar: 3, + }); + // @ts-expect-error + bind({ bar: 'bar' }); + // @ts-expect-error + bind({}); + // @ts-expect-error + bind(); + // @ts-expect-error + bind('bar'); + }, + }); + expect(extension.id).toBe('test'); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 1ef0d3c272..c545fe7fea 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -18,10 +18,26 @@ import { PortableSchema } from '../schema'; import { BackstagePlugin } from './createPlugin'; import { AnyExtensionDataMap, Extension } from './types'; +type OnlyRequiredKeys = { + [K in keyof TOutput]: TOutput[K]['config']['optional'] extends true + ? never + : K; +}[keyof TOutput]; + +type OnlyOptionalKeys = { + [K in keyof TOutput]: TOutput[K]['config']['optional'] extends false + ? never + : K; +}[keyof TOutput]; + /** @public */ -export type ExtensionDataBind = (outputs: { - [K in keyof TOutput]: TOutput[K]['T']; -}) => void; +export type ExtensionDataBind = ( + outputs: { + [K in OnlyRequiredKeys]: TOutput[K]['T']; + } & { + [K in OnlyOptionalKeys]?: TOutput[K]['T']; + }, +) => void; /** @public */ export type ExtensionDataValue = { diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index 10a931a65d..95e8bdeb95 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -15,14 +15,35 @@ */ /** @public */ -export type ExtensionDataRef = { +export type ExtensionDataRef< + TData, + TConfig extends { optional: boolean } = { optional: false }, +> = { id: string; - T: T; + T: TData; + config: TConfig; $$type: 'extension-data'; }; /** @public */ -// TODO: change to options object with ID. -export function createExtensionDataRef(id: string): ExtensionDataRef { - return { id, $$type: 'extension-data' } as ExtensionDataRef; +export interface ConfigurableExtensionDataRef< + TData, + TConfig extends { optional: boolean }, +> extends ExtensionDataRef { + optional(): ConfigurableExtensionDataRef; +} + +/** @public */ +// TODO: change to options object with ID. +export function createExtensionDataRef( + id: string, +): ConfigurableExtensionDataRef { + return { + id, + $$type: 'extension-data', + config: { optional: false }, + optional() { + return { ...this, config: { ...this.config, optional: true } }; + }, + } as ConfigurableExtensionDataRef; } diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index 3779a5c676..f3f5008f27 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -20,7 +20,7 @@ import { ExtensionDataRef } from './createExtensionDataRef'; import { BackstagePlugin } from './createPlugin'; /** @public */ -export type AnyExtensionDataMap = Record>; +export type AnyExtensionDataMap = Record>; /** @public */ export interface Extension { From f2bacd19dbc538cbbda47de69dd8573e7d9ded5c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 5 Sep 2023 11:27:50 +0200 Subject: [PATCH 5/9] frontend-plugin-api: switch createPageExtension to use optional output Signed-off-by: Patrik Oldsberg --- .../frontend-plugin-api/src/extensions/createPageExtension.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index 7f16a81e24..f1d10153b5 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -72,7 +72,7 @@ export function createPageExtension< output: { component: coreExtensionData.reactComponent, path: coreExtensionData.routePath, - ...(options.routeRef && { routeRef: coreExtensionData.routeRef }), + routeRef: coreExtensionData.routeRef.optional(), }, inputs: options.inputs, configSchema, From 44dcc3b591f11def6a7de3a38e3ffb3641581651 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 5 Sep 2023 12:17:55 +0200 Subject: [PATCH 6/9] frontend-plugin-api: simply output mapping types + add inputs mapped types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Johan Haals Co-authored-by: Philipp Hugenroth Co-authored-by: Camila Belo Signed-off-by: Patrik Oldsberg --- .../src/wiring/createExtension.test.ts | 66 +++++++++++++++++++ .../src/wiring/createExtension.ts | 64 +++++++++--------- .../frontend-plugin-api/src/wiring/index.ts | 2 +- .../frontend-plugin-api/src/wiring/types.ts | 4 +- 4 files changed, 104 insertions(+), 32 deletions(-) diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts index 104f03c84d..a992642d61 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.test.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.test.ts @@ -19,6 +19,8 @@ import { createExtensionDataRef } from './createExtensionDataRef'; const stringData = createExtensionDataRef('string'); +function unused(..._any: any[]) {} + describe('createExtension', () => { it('should create an extension with a simple output', () => { const extension = createExtension({ @@ -87,4 +89,68 @@ describe('createExtension', () => { }); expect(extension.id).toBe('test'); }); + + it('should create an extension with input', () => { + const extension = createExtension({ + id: 'test', + at: 'root', + inputs: { + mixed: { + extensionData: { + required: stringData, + optional: stringData.optional(), + }, + }, + onlyRequired: { + extensionData: { + required: stringData, + }, + }, + onlyOptional: { + extensionData: { + optional: stringData.optional(), + }, + }, + }, + output: { + foo: stringData, + }, + factory({ bind, inputs }) { + const a1: string = inputs.mixed?.[0].required; + // @ts-expect-error + const a2: number = inputs.mixed?.[0].required; + // @ts-expect-error + const a3: any = inputs.mixed?.[0].nonExistent; + unused(a1, a2, a3); + + const b1: string | undefined = inputs.mixed?.[0].optional; + // @ts-expect-error + const b2: string = inputs.mixed?.[0].optional; + // @ts-expect-error + const b3: number = inputs.mixed?.[0].optional; + // @ts-expect-error + const b4: number | undefined = inputs.mixed?.[0].optional; + unused(b1, b2, b3, b4); + + const c1: string = inputs.onlyRequired?.[0].required; + // @ts-expect-error + const c2: number = inputs.onlyRequired?.[0].required; + unused(c1, c2); + + const d1: string | undefined = inputs.onlyOptional?.[0].optional; + // @ts-expect-error + const d2: string = inputs.onlyOptional?.[0].optional; + // @ts-expect-error + const d3: number = inputs.onlyOptional?.[0].optional; + // @ts-expect-error + const d4: number | undefined = inputs.onlyOptional?.[0].optional; + unused(d1, d2, d3, d4); + + bind({ + foo: 'bar', + }); + }, + }); + expect(extension.id).toBe('test'); + }); }); diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index c545fe7fea..575271cb24 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -18,32 +18,44 @@ import { PortableSchema } from '../schema'; import { BackstagePlugin } from './createPlugin'; import { AnyExtensionDataMap, Extension } from './types'; -type OnlyRequiredKeys = { - [K in keyof TOutput]: TOutput[K]['config']['optional'] extends true - ? never - : K; -}[keyof TOutput]; - -type OnlyOptionalKeys = { - [K in keyof TOutput]: TOutput[K]['config']['optional'] extends false - ? never - : K; -}[keyof TOutput]; +/** @public */ +export type ExtensionDataInputValues< + TInputs extends { [name in string]: { extensionData: AnyExtensionDataMap } }, +> = { + [InputName in keyof TInputs]: Array< + { + [DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends { + optional: true; + } + ? DataName + : never]?: TInputs[InputName]['extensionData'][DataName]['T']; + } & { + [DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends { + optional: false; + } + ? DataName + : never]: TInputs[InputName]['extensionData'][DataName]['T']; + } + >; +}; /** @public */ -export type ExtensionDataBind = ( - outputs: { - [K in OnlyRequiredKeys]: TOutput[K]['T']; +export type ExtensionDataBind = ( + values: { + [DataName in keyof TMap as TMap[DataName]['config'] extends { + optional: false; + } + ? DataName + : never]: TMap[DataName]['T']; } & { - [K in OnlyOptionalKeys]?: TOutput[K]['T']; + [DataName in keyof TMap as TMap[DataName]['config'] extends { + optional: true; + } + ? DataName + : never]?: TMap[DataName]['T']; }, ) => void; -/** @public */ -export type ExtensionDataValue = { - [K in keyof TData]: TData[K]['T']; -}; - /** @public */ export interface CreateExtensionOptions< TOutput extends AnyExtensionDataMap, @@ -60,11 +72,7 @@ export interface CreateExtensionOptions< source?: BackstagePlugin; bind: ExtensionDataBind; config: TConfig; - inputs: { - [pointName in keyof TInputs]: ExtensionDataValue< - TInputs[pointName]['extensionData'] - >[]; - }; + inputs: ExtensionDataInputValues; }): void; } @@ -86,11 +94,7 @@ export function createExtension< return options.factory({ bind, config, - inputs: inputs as { - [pointName in keyof TInputs]: ExtensionDataValue< - TInputs[pointName]['extensionData'] - >[]; - }, + inputs: inputs as ExtensionDataInputValues, }); }, }; diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 1a243eb2e3..4830d7dd88 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -19,7 +19,7 @@ export { createExtension, type CreateExtensionOptions, type ExtensionDataBind, - type ExtensionDataValue, + type ExtensionDataInputValues, } from './createExtension'; export { createExtensionDataRef, diff --git a/packages/frontend-plugin-api/src/wiring/types.ts b/packages/frontend-plugin-api/src/wiring/types.ts index f3f5008f27..dbe4633ff5 100644 --- a/packages/frontend-plugin-api/src/wiring/types.ts +++ b/packages/frontend-plugin-api/src/wiring/types.ts @@ -20,7 +20,9 @@ import { ExtensionDataRef } from './createExtensionDataRef'; import { BackstagePlugin } from './createPlugin'; /** @public */ -export type AnyExtensionDataMap = Record>; +export type AnyExtensionDataMap = { + [name in string]: ExtensionDataRef; +}; /** @public */ export interface Extension { From fb29700ae52c5ff1ac26c685a3c7bee9526a1b0d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 5 Sep 2023 12:21:05 +0200 Subject: [PATCH 7/9] frontend-plugin-api: test fixes + type cleanup Signed-off-by: Patrik Oldsberg --- .../src/extensions/createApiExtension.test.ts | 14 ++++++++++---- .../src/extensions/createApiExtension.ts | 8 ++------ .../src/extensions/createPageExtension.test.tsx | 3 +++ .../src/extensions/createPageExtension.tsx | 8 ++------ 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts index a3662e5ef1..b868fb235c 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts @@ -38,10 +38,13 @@ describe('createApiExtension', () => { configSchema: undefined, inputs: {}, output: { - api: { + api: expect.objectContaining({ $$type: 'extension-data', id: 'core.api.factory', - }, + config: { + optional: false, + }, + }), }, factory: expect.any(Function), }); @@ -71,10 +74,13 @@ describe('createApiExtension', () => { configSchema: undefined, inputs: {}, output: { - api: { + api: expect.objectContaining({ $$type: 'extension-data', id: 'core.api.factory', - }, + config: { + optional: false, + }, + }), }, factory: expect.any(Function), }); diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts index 057addc4a8..f81f07ca72 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts @@ -18,7 +18,7 @@ import { AnyApiFactory, AnyApiRef } from '@backstage/core-plugin-api'; import { PortableSchema } from '../schema'; import { AnyExtensionDataMap, - ExtensionDataValue, + ExtensionDataInputValues, createExtension, coreExtensionData, } from '../wiring'; @@ -33,11 +33,7 @@ export function createApiExtension< api: AnyApiRef; factory: (options: { config: TConfig; - inputs: { - [pointName in keyof TInputs]: ExtensionDataValue< - TInputs[pointName]['extensionData'] - >[]; - }; + inputs: ExtensionDataInputValues; }) => AnyApiFactory; } | { diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx index e5939d24ea..db389ae4b1 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.test.tsx @@ -42,6 +42,7 @@ describe('createPageExtension', () => { output: { component: expect.anything(), path: expect.anything(), + routeRef: expect.anything(), }, factory: expect.any(Function), }); @@ -73,6 +74,7 @@ describe('createPageExtension', () => { output: { component: expect.anything(), path: expect.anything(), + routeRef: expect.anything(), }, factory: expect.any(Function), }); @@ -93,6 +95,7 @@ describe('createPageExtension', () => { output: { component: expect.anything(), path: expect.anything(), + routeRef: expect.anything(), }, factory: expect.any(Function), }); diff --git a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx index f1d10153b5..167dd397dd 100644 --- a/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createPageExtension.tsx @@ -23,7 +23,7 @@ import { coreExtensionData, createExtension, Extension, - ExtensionDataValue, + ExtensionDataInputValues, } from '../wiring'; /** @@ -50,11 +50,7 @@ export function createPageExtension< routeRef?: RouteRef; component: (props: { config: TConfig; - inputs: { - [pointName in keyof TInputs]: ExtensionDataValue< - TInputs[pointName]['extensionData'] - >[]; - }; + inputs: ExtensionDataInputValues; }) => Promise; }, ): Extension { From 1d04ba7854c90d56f0a473bf64a6a25c744ea961 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 5 Sep 2023 14:02:51 +0200 Subject: [PATCH 8/9] frontend-plugin-api: update API report + fix Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/api-report.md | 151 +++++++++++++----- .../src/wiring/createExtensionDataRef.ts | 2 +- .../frontend-plugin-api/src/wiring/index.ts | 1 + 3 files changed, 115 insertions(+), 39 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index e83d963348..85bb6aa996 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -17,7 +17,9 @@ import { ZodSchema } from 'zod'; import { ZodTypeDef } from 'zod'; // @public (undocumented) -export type AnyExtensionDataMap = Record>; +export type AnyExtensionDataMap = { + [name in string]: ExtensionDataRef; +}; // @public (undocumented) export interface BackstagePlugin { @@ -29,12 +31,48 @@ export interface BackstagePlugin { id: string; } +// @public (undocumented) +export interface ConfigurableExtensionDataRef< + TData, + TConfig extends { + optional: boolean; + }, +> extends ExtensionDataRef { + // (undocumented) + optional(): ConfigurableExtensionDataRef< + TData, + TData & { + optional: true; + } + >; +} + // @public (undocumented) export const coreExtensionData: { - reactComponent: ExtensionDataRef>; - routePath: ExtensionDataRef; - apiFactory: ExtensionDataRef; - routeRef: ExtensionDataRef>; + reactComponent: ConfigurableExtensionDataRef< + ComponentType<{}>, + { + optional: false; + } + >; + routePath: ConfigurableExtensionDataRef< + string, + { + optional: false; + } + >; + apiFactory: ConfigurableExtensionDataRef< + AnyApiFactory, + { + optional: false; + } + >; + routeRef: ConfigurableExtensionDataRef< + RouteRef, + { + optional: false; + } + >; }; // @public (undocumented) @@ -52,11 +90,7 @@ export function createApiExtension< api: AnyApiRef; factory: (options: { config: TConfig; - inputs: { - [pointName in keyof TInputs]: ExtensionDataValue< - TInputs[pointName]['extensionData'] - >[]; - }; + inputs: ExtensionDataInputValues; }) => AnyApiFactory; } | { @@ -70,23 +104,32 @@ export function createApiExtension< // @public (undocumented) export function createExtension< - TData extends AnyExtensionDataMap, - TPoint extends Record< + TOutput extends AnyExtensionDataMap, + TInputs extends Record< string, { extensionData: AnyExtensionDataMap; } >, TConfig = never, ->(options: CreateExtensionOptions): Extension; +>( + options: CreateExtensionOptions, +): Extension; // @public (undocumented) -export function createExtensionDataRef(id: string): ExtensionDataRef; +export function createExtensionDataRef( + id: string, +): ConfigurableExtensionDataRef< + TData, + { + optional: false; + } +>; // @public (undocumented) export interface CreateExtensionOptions< - TData extends AnyExtensionDataMap, - TPoint extends Record< + TOutput extends AnyExtensionDataMap, + TInputs extends Record< string, { extensionData: AnyExtensionDataMap; @@ -103,20 +146,16 @@ export interface CreateExtensionOptions< // (undocumented) factory(options: { source?: BackstagePlugin; - bind: ExtensionDataBind; + bind: ExtensionDataBind; config: TConfig; - inputs: { - [pointName in keyof TPoint]: ExtensionDataValue< - TPoint[pointName]['extensionData'] - >[]; - }; + inputs: ExtensionDataInputValues; }): void; // (undocumented) id: string; // (undocumented) - inputs?: TPoint; + inputs?: TInputs; // (undocumented) - output: TData; + output: TOutput; } // @public @@ -146,11 +185,7 @@ export function createPageExtension< routeRef?: RouteRef; component: (props: { config: TConfig; - inputs: { - [pointName in keyof TInputs]: ExtensionDataValue< - TInputs[pointName]['extensionData'] - >[]; - }; + inputs: ExtensionDataInputValues; }) => Promise; }, ): Extension; @@ -207,22 +242,62 @@ export interface ExtensionBoundaryProps { } // @public (undocumented) -export type ExtensionDataBind = { - [K in keyof TData]: (value: TData[K]['T']) => void; +export type ExtensionDataBind = ( + values: { + [DataName in keyof TMap as TMap[DataName]['config'] extends { + optional: false; + } + ? DataName + : never]: TMap[DataName]['T']; + } & { + [DataName in keyof TMap as TMap[DataName]['config'] extends { + optional: true; + } + ? DataName + : never]?: TMap[DataName]['T']; + }, +) => void; + +// @public (undocumented) +export type ExtensionDataInputValues< + TInputs extends { + [name in string]: { + extensionData: AnyExtensionDataMap; + }; + }, +> = { + [InputName in keyof TInputs]: Array< + { + [DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends { + optional: true; + } + ? DataName + : never]?: TInputs[InputName]['extensionData'][DataName]['T']; + } & { + [DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends { + optional: false; + } + ? DataName + : never]: TInputs[InputName]['extensionData'][DataName]['T']; + } + >; }; // @public (undocumented) -export type ExtensionDataRef = { +export type ExtensionDataRef< + TData, + TConfig extends { + optional: boolean; + } = { + optional: false; + }, +> = { id: string; - T: T; + T: TData; + config: TConfig; $$type: 'extension-data'; }; -// @public (undocumented) -export type ExtensionDataValue = { - [K in keyof TData]: TData[K]['T']; -}; - // @public (undocumented) export interface PluginOptions { // (undocumented) diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index 95e8bdeb95..0a3cfa5951 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -33,8 +33,8 @@ export interface ConfigurableExtensionDataRef< optional(): ConfigurableExtensionDataRef; } -/** @public */ // TODO: change to options object with ID. +/** @public */ export function createExtensionDataRef( id: string, ): ConfigurableExtensionDataRef { diff --git a/packages/frontend-plugin-api/src/wiring/index.ts b/packages/frontend-plugin-api/src/wiring/index.ts index 4830d7dd88..175200cf0c 100644 --- a/packages/frontend-plugin-api/src/wiring/index.ts +++ b/packages/frontend-plugin-api/src/wiring/index.ts @@ -24,6 +24,7 @@ export { export { createExtensionDataRef, type ExtensionDataRef, + type ConfigurableExtensionDataRef, } from './createExtensionDataRef'; export { createPlugin, From c1de5ddf2afbd4469177155185acee1db80d60b2 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 5 Sep 2023 14:41:52 +0200 Subject: [PATCH 9/9] frontend-plugin-api: clean up configurable extension data ref type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Co-authored-by: Philipp Hugenroth Signed-off-by: Patrik Oldsberg --- packages/frontend-plugin-api/api-report.md | 59 +++++-------------- .../src/extensions/createApiExtension.test.ts | 8 +-- .../src/wiring/createExtension.ts | 14 ++--- .../src/wiring/createExtensionDataRef.ts | 10 ++-- 4 files changed, 30 insertions(+), 61 deletions(-) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 85bb6aa996..2959dcb92e 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -35,8 +35,8 @@ export interface BackstagePlugin { export interface ConfigurableExtensionDataRef< TData, TConfig extends { - optional: boolean; - }, + optional?: true; + } = {}, > extends ExtensionDataRef { // (undocumented) optional(): ConfigurableExtensionDataRef< @@ -49,30 +49,10 @@ export interface ConfigurableExtensionDataRef< // @public (undocumented) export const coreExtensionData: { - reactComponent: ConfigurableExtensionDataRef< - ComponentType<{}>, - { - optional: false; - } - >; - routePath: ConfigurableExtensionDataRef< - string, - { - optional: false; - } - >; - apiFactory: ConfigurableExtensionDataRef< - AnyApiFactory, - { - optional: false; - } - >; - routeRef: ConfigurableExtensionDataRef< - RouteRef, - { - optional: false; - } - >; + reactComponent: ConfigurableExtensionDataRef, {}>; + routePath: ConfigurableExtensionDataRef; + apiFactory: ConfigurableExtensionDataRef; + routeRef: ConfigurableExtensionDataRef, {}>; }; // @public (undocumented) @@ -119,12 +99,7 @@ export function createExtension< // @public (undocumented) export function createExtensionDataRef( id: string, -): ConfigurableExtensionDataRef< - TData, - { - optional: false; - } ->; +): ConfigurableExtensionDataRef; // @public (undocumented) export interface CreateExtensionOptions< @@ -245,10 +220,10 @@ export interface ExtensionBoundaryProps { export type ExtensionDataBind = ( values: { [DataName in keyof TMap as TMap[DataName]['config'] extends { - optional: false; + optional: true; } - ? DataName - : never]: TMap[DataName]['T']; + ? never + : DataName]: TMap[DataName]['T']; } & { [DataName in keyof TMap as TMap[DataName]['config'] extends { optional: true; @@ -271,14 +246,14 @@ export type ExtensionDataInputValues< [DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends { optional: true; } - ? DataName - : never]?: TInputs[InputName]['extensionData'][DataName]['T']; + ? never + : DataName]: TInputs[InputName]['extensionData'][DataName]['T']; } & { [DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends { - optional: false; + optional: true; } ? DataName - : never]: TInputs[InputName]['extensionData'][DataName]['T']; + : never]?: TInputs[InputName]['extensionData'][DataName]['T']; } >; }; @@ -287,10 +262,8 @@ export type ExtensionDataInputValues< export type ExtensionDataRef< TData, TConfig extends { - optional: boolean; - } = { - optional: false; - }, + optional?: true; + } = {}, > = { id: string; T: TData; diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts index b868fb235c..fadb814d4f 100644 --- a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts @@ -41,9 +41,7 @@ describe('createApiExtension', () => { api: expect.objectContaining({ $$type: 'extension-data', id: 'core.api.factory', - config: { - optional: false, - }, + config: {}, }), }, factory: expect.any(Function), @@ -77,9 +75,7 @@ describe('createApiExtension', () => { api: expect.objectContaining({ $$type: 'extension-data', id: 'core.api.factory', - config: { - optional: false, - }, + config: {}, }), }, factory: expect.any(Function), diff --git a/packages/frontend-plugin-api/src/wiring/createExtension.ts b/packages/frontend-plugin-api/src/wiring/createExtension.ts index 575271cb24..bcea03595b 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtension.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtension.ts @@ -27,14 +27,14 @@ export type ExtensionDataInputValues< [DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends { optional: true; } - ? DataName - : never]?: TInputs[InputName]['extensionData'][DataName]['T']; + ? never + : DataName]: TInputs[InputName]['extensionData'][DataName]['T']; } & { [DataName in keyof TInputs[InputName]['extensionData'] as TInputs[InputName]['extensionData'][DataName]['config'] extends { - optional: false; + optional: true; } ? DataName - : never]: TInputs[InputName]['extensionData'][DataName]['T']; + : never]?: TInputs[InputName]['extensionData'][DataName]['T']; } >; }; @@ -43,10 +43,10 @@ export type ExtensionDataInputValues< export type ExtensionDataBind = ( values: { [DataName in keyof TMap as TMap[DataName]['config'] extends { - optional: false; + optional: true; } - ? DataName - : never]: TMap[DataName]['T']; + ? never + : DataName]: TMap[DataName]['T']; } & { [DataName in keyof TMap as TMap[DataName]['config'] extends { optional: true; diff --git a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts index 0a3cfa5951..6d6309bee4 100644 --- a/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts +++ b/packages/frontend-plugin-api/src/wiring/createExtensionDataRef.ts @@ -17,7 +17,7 @@ /** @public */ export type ExtensionDataRef< TData, - TConfig extends { optional: boolean } = { optional: false }, + TConfig extends { optional?: true } = {}, > = { id: string; T: TData; @@ -28,7 +28,7 @@ export type ExtensionDataRef< /** @public */ export interface ConfigurableExtensionDataRef< TData, - TConfig extends { optional: boolean }, + TConfig extends { optional?: true } = {}, > extends ExtensionDataRef { optional(): ConfigurableExtensionDataRef; } @@ -37,13 +37,13 @@ export interface ConfigurableExtensionDataRef< /** @public */ export function createExtensionDataRef( id: string, -): ConfigurableExtensionDataRef { +): ConfigurableExtensionDataRef { return { id, $$type: 'extension-data', - config: { optional: false }, + config: {}, optional() { return { ...this, config: { ...this.config, optional: true } }; }, - } as ConfigurableExtensionDataRef; + } as ConfigurableExtensionDataRef; }