From 09696af4e46626fcb521a671d3079af072dc2201 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 31 Aug 2023 13:35:25 +0200 Subject: [PATCH] implement createApiExtension MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Co-authored-by: Johan Haals Signed-off-by: Fredrik Adelöw --- packages/frontend-plugin-api/api-report.md | 34 ++++++++ packages/frontend-plugin-api/package.json | 1 + .../src/extensions/createApiExtension.test.ts | 82 +++++++++++++++++++ .../src/extensions/createApiExtension.ts | 72 ++++++++++++++++ .../src/extensions/index.ts | 1 + packages/frontend-plugin-api/src/types.ts | 2 + yarn.lock | 1 + 7 files changed, 193 insertions(+) create mode 100644 packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts create mode 100644 packages/frontend-plugin-api/src/extensions/createApiExtension.ts diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 84aa3396e8..92fc11ef17 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -5,6 +5,8 @@ ```ts /// +import { AnyApiFactory } from '@backstage/core-plugin-api'; +import { AnyApiRef } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { JsonObject } from '@backstage/types'; import { z } from 'zod'; @@ -28,8 +30,40 @@ export interface BackstagePlugin { export const coreExtensionData: { reactComponent: ExtensionDataRef>; routePath: ExtensionDataRef; + apiFactory: ExtensionDataRef; }; +// @public (undocumented) +export function createApiExtension< + TConfig extends {}, + TInputs extends Record< + string, + { + extensionData: AnyExtensionDataMap; + } + >, +>( + options: ( + | { + api: AnyApiRef; + factory: (options: { + config: TConfig; + inputs: { + [pointName in keyof TInputs]: ExtensionDataValue< + TInputs[pointName]['extensionData'] + >[]; + }; + }) => AnyApiFactory; + } + | { + factory: AnyApiFactory; + } + ) & { + configSchema?: PortableSchema; + inputs?: TInputs; + }, +): Extension; + // @public (undocumented) export function createExtension< TData extends AnyExtensionDataMap, diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 0c420abbf3..fa2b4ff093 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -35,6 +35,7 @@ "react": "^16.13.1 || ^17.0.0" }, "dependencies": { + "@backstage/core-plugin-api": "workspace:^", "@backstage/types": "workspace:^", "lodash": "^4.17.21", "zod": "^3.21.4", diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts new file mode 100644 index 0000000000..a3662e5ef1 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { createApiExtension } from './createApiExtension'; +import { createApiFactory, createApiRef } from '@backstage/core-plugin-api'; + +describe('createApiExtension', () => { + it('fills in the expected values for an existing factory', () => { + const api = createApiRef<{ foo: string }>({ id: 'test' }); + const factory = createApiFactory({ + api, + deps: {}, + factory: () => ({ foo: 'bar' }), + }); + + const extension = createApiExtension({ + factory, + }); + + expect(extension).toEqual({ + $$type: 'extension', + id: 'apis.test', + at: 'core/apis', + disabled: false, + configSchema: undefined, + inputs: {}, + output: { + api: { + $$type: 'extension-data', + id: 'core.api.factory', + }, + }, + factory: expect.any(Function), + }); + }); + + it('fills in the expected values for a ref and custom factory', () => { + const api = createApiRef<{ foo: string }>({ id: 'test' }); + const factory = jest.fn(() => ({ foo: 'bar' })); + + const extension = createApiExtension({ + api, + inputs: {}, + factory({ config: _config, inputs: _inputs }) { + return createApiFactory({ + api, + deps: {}, + factory, + }); + }, + }); + + expect(extension).toEqual({ + $$type: 'extension', + id: 'apis.test', + at: 'core/apis', + disabled: false, + configSchema: undefined, + inputs: {}, + output: { + api: { + $$type: 'extension-data', + id: 'core.api.factory', + }, + }, + factory: expect.any(Function), + }); + }); +}); diff --git a/packages/frontend-plugin-api/src/extensions/createApiExtension.ts b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts new file mode 100644 index 0000000000..5dab38aec8 --- /dev/null +++ b/packages/frontend-plugin-api/src/extensions/createApiExtension.ts @@ -0,0 +1,72 @@ +/* + * 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, AnyApiRef } from '@backstage/core-plugin-api'; +import { PortableSchema } from '../createSchemaFromZod'; +import { + AnyExtensionDataMap, + coreExtensionData, + createExtension, + ExtensionDataValue, +} from '../types'; + +/** @public */ +export function createApiExtension< + TConfig extends {}, + TInputs extends Record, +>( + options: ( + | { + api: AnyApiRef; + factory: (options: { + config: TConfig; + inputs: { + [pointName in keyof TInputs]: ExtensionDataValue< + TInputs[pointName]['extensionData'] + >[]; + }; + }) => AnyApiFactory; + } + | { + factory: AnyApiFactory; + } + ) & { + configSchema?: PortableSchema; + inputs?: TInputs; + }, +) { + const { factory, configSchema, inputs: extensionInputs } = options; + + const apiRef = + 'api' in options ? options.api : (factory as { api: AnyApiRef }).api; + + return createExtension({ + id: `apis.${apiRef.id}`, + at: 'core/apis', + inputs: extensionInputs, + configSchema, + output: { + api: coreExtensionData.apiFactory, + }, + factory({ bind, config, inputs }) { + if (typeof factory === 'function') { + bind.api(factory({ config, inputs })); + } else { + bind.api(factory); + } + }, + }); +} diff --git a/packages/frontend-plugin-api/src/extensions/index.ts b/packages/frontend-plugin-api/src/extensions/index.ts index d2a18fe977..fa6e60894f 100644 --- a/packages/frontend-plugin-api/src/extensions/index.ts +++ b/packages/frontend-plugin-api/src/extensions/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ +export { createApiExtension } from './createApiExtension'; export { createPageExtension } from './createPageExtension'; diff --git a/packages/frontend-plugin-api/src/types.ts b/packages/frontend-plugin-api/src/types.ts index 2affa4ae42..2c9f515d9d 100644 --- a/packages/frontend-plugin-api/src/types.ts +++ b/packages/frontend-plugin-api/src/types.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { AnyApiFactory } from '@backstage/core-plugin-api'; import { ComponentType } from 'react'; import { PortableSchema } from './createSchemaFromZod'; @@ -34,6 +35,7 @@ export function createExtensionDataRef(id: string): ExtensionDataRef { export const coreExtensionData = { reactComponent: createExtensionDataRef('core.reactComponent'), routePath: createExtensionDataRef('core.routing.path'), + apiFactory: createExtensionDataRef('core.api.factory'), }; /** @public */ diff --git a/yarn.lock b/yarn.lock index 22fcc1a57d..275104023a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4298,6 +4298,7 @@ __metadata: resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api" dependencies: "@backstage/cli": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" "@backstage/frontend-app-api": "workspace:^" "@backstage/types": "workspace:^" "@testing-library/jest-dom": ^5.10.1