From 6aa0cda977d59212e1a2ad44418b878d67adc671 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 5 Dec 2023 10:58:17 +0100 Subject: [PATCH 01/11] add api docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../architecture-utility-apis.drawio.svg | 314 ++++++++++++++++++ .../architecture/06-utility-apis.md | 39 ++- docs/frontend-system/utility-apis/01-index.md | 37 +++ .../utility-apis/02-creating.md | 92 +++++ .../utility-apis/03-consuming.md | 67 ++++ .../utility-apis/04-configuring.md | 25 ++ .../utility-apis/05-migrating.md | 9 + 7 files changed, 582 insertions(+), 1 deletion(-) create mode 100644 docs/assets/frontend-system/architecture-utility-apis.drawio.svg create mode 100644 docs/frontend-system/utility-apis/01-index.md create mode 100644 docs/frontend-system/utility-apis/02-creating.md create mode 100644 docs/frontend-system/utility-apis/03-consuming.md create mode 100644 docs/frontend-system/utility-apis/04-configuring.md create mode 100644 docs/frontend-system/utility-apis/05-migrating.md diff --git a/docs/assets/frontend-system/architecture-utility-apis.drawio.svg b/docs/assets/frontend-system/architecture-utility-apis.drawio.svg new file mode 100644 index 0000000000..a7008b1126 --- /dev/null +++ b/docs/assets/frontend-system/architecture-utility-apis.drawio.svg @@ -0,0 +1,314 @@ + + + + + + + +
+
+
+ App +
+
+
+
+ + App + +
+
+ + + + + +
+
+
+ provides +
+
+
+
+ + provides + +
+
+ + + + +
+
+
+ Catalog Plugin +
+ + plugin:catalog + +
+
+
+
+ + Catalog Plugin... + +
+
+ + + + + +
+
+
+ + replaces +
+ implementation +
+
+
+
+
+
+ + replaces... + +
+
+ + + + +
+
+
+ Extension Overrides +
+
+
+
+ + Extension Overr... + +
+
+ + + + + +
+
+
+ provides +
+
+
+
+ + provides + +
+
+ + + + +
+
+
+ Todo Plugin +
+ + plugin:todo + +
+
+
+
+ + Todo Plugin... + +
+
+ + + + +
+
+
+ Backstage Frontend Framework +
+
+
+
+ + Backstage Frontend Framework + +
+
+ + + + + + +
+
+
+ + Catalog Client Utility API +
+ + api:plugin.catalog.service + +
+
+
+
+
+ + Catalog Client Utility API... + +
+
+ + + + +
+
+
+ + Todo Utility API +
+ + api:plugin.todo.api + +
+
+
+
+
+ + Todo Utility API... + +
+
+ + + + + + + + + +
+
+
+ + installs + +
+
+
+
+ + installs + +
+
+ + + + +
+
+
+ + Fetch Utility API +
+ + api:core.fetch + +
+
+
+
+
+ + Fetch Utility API... + +
+
+ + + + + +
+
+
+ provides +
+
+
+
+ + provides + +
+
+ + + + + +
+
+
+ depends on +
+
+
+
+ + depends on + +
+
+ + + + + +
+
+
+ depends on +
+
+
+
+ + depends on + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/docs/frontend-system/architecture/06-utility-apis.md b/docs/frontend-system/architecture/06-utility-apis.md index 135f60744a..4fbbf1af94 100644 --- a/docs/frontend-system/architecture/06-utility-apis.md +++ b/docs/frontend-system/architecture/06-utility-apis.md @@ -8,4 +8,41 @@ description: Utility APIs > **NOTE: The new frontend system is in a highly experimental phase** -See [Utility APIs docs](../../api/utility-apis.md). +## Overview + +Utility APIs are pieces of standalone functionality, interfaces that can be +requested by plugins to use. They are defined by a TypeScript interface as well +as a reference (an "API ref") used to access its implementation. They can be +provided both by plugins and the core framework, and are themselves +[extensions](../architecture/03-extensions.md) that can have inputs, be +replaced, and be declaratively configured in your app-config. + +A common example of a utility API is a client interface to interact with the +backend part of a plugin, such as the catalog client. Any frontend plugin can +then request an implementation of that interface to make requests through. + +The following diagram shows a hypothetical application, which depends on two +plugins and also provides some extra overrides. Note that both the plugins and +the core framework exposed utility APIs, and that they depended on each other. +The app also chose to use its overrides mechanism to supply a replacement +implementation of one API, which took precedence over the default one. Thus, all +consumers of that API will be sure to get that new implementation provided to +them. + +![frontend system utility apis diagram](../../assets/frontend-system/architecture-utility-apis.drawio.svg) + +## Extension structure + +All utility APIs implement the `coreExtensionData.apiFactory` output data type, +and must attach exclusively to the `core` extension's `apis` input no matter who +provided them. These defaults are provided out of the box by the +`createApiExtension` framework function. + +Since utility APIs are extensions, they can also have inputs in advanced use +cases. This is occasionally useful for complex APIs that can themselves be +extended with additional programmatic functionality by adopters. + +## Links + +- The [Using Utility APIs section](../building-plugins/03-utility-apis.md) of the plugin docs +- The legacy docs on [utility APIs](../../api/utility-apis.md) diff --git a/docs/frontend-system/utility-apis/01-index.md b/docs/frontend-system/utility-apis/01-index.md new file mode 100644 index 0000000000..353ad3ffe4 --- /dev/null +++ b/docs/frontend-system/utility-apis/01-index.md @@ -0,0 +1,37 @@ +--- +id: index +title: Utility APIs +sidebar_label: Overview +# prettier-ignore +description: Working with Utility APIs in the New Frontend System +--- + +As described [in the architecture section](../architecture/06-utility-apis.md), utility APIs are pieces of shared functionality - interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can accept inputs, be declaratively configured in your app-config, or transparently be replaced entirely with custom implementations that fulfill the same contract. + +## Creating Utility APIs + +> For details, [see the main article](./02-creating.md). + +Backstage apps, the core Backstage framework, and plugins can all expose utility APIs for general use. + +Some are available out of the box, such as the API for reading app configuration. Some are provided by third party plugins, such as the catalog client API that both the catalog itself and your own code can leverage to talk to the catalog backend. Some, you may create yourself and make available inside your Backstage instance for use within your private ecosystem of plugins. + +[The main article](./02-creating.md) describes the process of creating and exposing utility APIs of your own, for sharing functionality or configurability across plugins and apps. + +## Consuming Utility APIs + +> For details, [see the main article](./03-consuming.md). + +Once utility APIs are created, there are a few ways that they can be accessed to be consumed. + +Some utility APIs in turn depend on other utility APIs. This powerful composability lets you leverage already-written reusable pieces. In particular, you may want to rely on Backstage's framework-provided APIs e.g. for reading app configuration and many other use cases. Sometimes you request utility APIs inside your React components, e.g. for accessing i18n strings, or emitting analytics events. + +These are described in detail in [the main article](./03-consuming.md) + +## Configuring Utility APIs + +> For details, [see the main article](./04-configuring.md). + +Most utility APIs are usable directly without any configuration. But they are proper extensions, and can therefore have their implementations entirely swapped out by your app for advanced use cases. They can also be built with the ability to configured in your app-config, or to have inputs that extend their functionality. + +These cases are all described in [the main article](./04-configuring.md). diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md new file mode 100644 index 0000000000..58655a24f5 --- /dev/null +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -0,0 +1,92 @@ +--- +id: creating +title: Creating Utility APIs +sidebar_label: Creating APIs +# prettier-ignore +description: Creating new utility APIs in your plugins and app +--- + +## When to make a utility API + +> TODO + +## Creating the Utility API contract + +The first step toward exposing a utility API is to define its TypeScript contract, and an API ref for consumers to find it via. This should be done in [your plugin's `-react` package](../../architecture-decisions/adr011-plugin-package-structure.md), so that it can be imported by other plugins. In this example, we have an Example plugin that wants to expose a utility API for performing some type of work. + +```tsx title="in @internal/plugin-example-react" +import { createApiRef } from '@backstage/frontend-plugin-api'; + +/** + * Performs some work. + * @oublic + */ +export interface WorkApi { + doWork(): Promise; +} + +/** + * The work interface for the Example plugin. + * @public + */ +export const workApiRef = createApiRef({ + id: 'plugin.example.work', +}); +``` + +Both of these are properly exported publicly from the package, so that consumers can reach them. + +The plugin itself now wants to provide this API and its default implementation, in the form of an API extension. Doing so means that when users install the Example plugin, an instance of the Work utility API will also be automatically available in their apps - both to the Example plugin itself, and to others. We do this in the main plugin package, not the `-react` package. + +```tsx title="in @internal/plugin-example" +import { + createApiExtension, + createApiFactory, + createPlugin, + configApiRef, + ConfigApi, +} from '@backstage/frontend-plugin-api'; +import { WorkApi, workApiRef } from '@internal/plugin-example-react'; + +class WorkImpl implements WorkApi { + constructor(options: { configApi: ConfigApi }) { + /* TODO */ + } + async doWork() { + /* TODO */ + } +} + +const workApi = createApiExtension({ + api: workApiRef, + factory: () => + createApiFactory({ + api: workApiRef, + deps: { + configApi: configApiRef, + }, + factory: ({ configApi }) => { + return new WorkImpl({ configApi }); + }, + }), +}); + +/** + * The Example plugin. + * @public + */ +export default createPlugin({ + id: 'example', + extensions: [workApi], +}); +``` + +For illustration we make a skeleton implementation class and the API extension and factory for it, in the same file. These are not exported to the public surface of the plugin package; only the plugin is, as the default export. + +The code also illustrates how the API factory declares a dependency on another utility API - the core config API in this case. An instance of that utility API is then provided to the factory function. + +The resulting extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, in this case ending up as `api:plugin.example.work`. You can now use this ID to refer to the API in app-config and elsewhere. + +## Next steps + +See [the Consuming section](./03-consuming.md) to see how to consume this new utility API in various ways. If you wish to learn how to add configurability and inputs to it, check out [the Configuring section](./04-configuring.md). diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md new file mode 100644 index 0000000000..7c339f1bf9 --- /dev/null +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -0,0 +1,67 @@ +--- +id: consuming +title: Consuming Utility APIs +sidebar_label: Consuming APIs +# prettier-ignore +description: Consuming utility APIs +--- + +All of the utility API extensions that were passed into your app through installed plugins, get instantiated and configured in the right order by the framework, and are then made available for consumption. You can interact with these instances in the following ways. + +## Via React hooks + +The most common consumption pattern for utility APIs is to call the `useApi` hook inside React components to get an implementation via its API ref. This applies whether it was originally provided from the core framework or from a plugin. + +```tsx +import { useApi, configApiRef } from '@backstage/frontend-plugin-api'; + +const MyComponent = () => { + const configApi = useApi(configApiRef); + const baseUrl = configApi.getString('backend.baseUrl'); + // ... +}; +``` + +The `useApi` hook always returns a value, or throws an exception if the API ref could not be resolved to a registered implementation. For advanced use cases, where you explicitly want to optionally request a utility API that may or may not have been provided at runtime, you can use the underlying `useApiHolder` hook instead. + +```tsx +import { useApiHolder, configApiRef } from '@backstage/frontend-plugin-api'; + +const MyComponent = () => { + const apis = useApiHolder(); + const configApi = apis.get(configApiRef); // may return undefined + if (configApi) { + const baseUrl = configApi.getString('backend.baseUrl'); + // ... + } +}; +``` + +## Via dependencies + +Your utility APIs can depend on other utility APIs in their factories. You do this by declaring `deps` on your `createApiFactory`, and reading the outcome in your `factory`. + +```tsx +import { + createApiExtension, + createApiFactory, + configApiRef, + discoveryApiRef, +} from '@backstage/frontend-plugin-api'; +import { MyApiImpl } from './MyApiImpl'; + +const myApi = createApiExtension({ + factory: createApiFactory({ + api: myApiRef, + deps: { + configApi: configApiRef, + discoveryApi: discoveryApiRef, + }, + factory: ({ configApi, discoveryApi }) => { + return new MyApiImpl({ configApi, discoveryApi }); + }, + }), +}); +``` + +Note how the `deps` section essentially assigns free-form names that you choose, to API refs. In this example we for example map `configApiRef` to the name `configApi`, but that's just a convention. The framework will ensure that all of those deps get instantiated and passed into your `factory` function with the same set of names as your `deps`. At that point, `configApi` refers to an actual functioning instance of that API ref. diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md new file mode 100644 index 0000000000..d1de1aa070 --- /dev/null +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -0,0 +1,25 @@ +--- +id: configuring +title: Configuring Utility APIs +sidebar_label: Configuring +# prettier-ignore +description: Configuring, extending, and overriding utility APIs +--- + +## Adding configurability + +> TODO + +## Configuring + +> TODO + +## Adding extension inputs + +> TODO + +## Adding data to extension inputs + +> TODO + +## Replacing a utility API implementation diff --git a/docs/frontend-system/utility-apis/05-migrating.md b/docs/frontend-system/utility-apis/05-migrating.md new file mode 100644 index 0000000000..0334e80f0b --- /dev/null +++ b/docs/frontend-system/utility-apis/05-migrating.md @@ -0,0 +1,9 @@ +--- +id: migrating +title: Migrating Utility APIs from the old frontend system +sidebar_label: Migrating +# prettier-ignore +description: Migrating Utility APIs from the old frontend system +--- + +> TODO From 716061d7bad05c3e59e20e629a6daaf3dc06a359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 12 Dec 2023 12:59:19 +0100 Subject: [PATCH 02/11] fix links MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/backend-system/architecture/01-index.md | 2 +- .../architecture/06-utility-apis.md | 30 ++++--------------- docs/frontend-system/utility-apis/01-index.md | 2 +- docs/getting-started/configuration.md | 6 ++-- 4 files changed, 11 insertions(+), 29 deletions(-) diff --git a/docs/backend-system/architecture/01-index.md b/docs/backend-system/architecture/01-index.md index 0e780039b6..6f1a3dd4d1 100644 --- a/docs/backend-system/architecture/01-index.md +++ b/docs/backend-system/architecture/01-index.md @@ -59,7 +59,7 @@ Just like plugins, modules also have access to services and can depend on their A detailed explanation of the package architecture can be found in the [Backstage Architecture -Overview](../../overview/architecture-overview/#package-architecture). The +Overview](../../overview/architecture-overview.md#package-architecture). The most important packages to consider for this system are the following: - `plugin--backend` houses the implementation of the backend plugins diff --git a/docs/frontend-system/architecture/06-utility-apis.md b/docs/frontend-system/architecture/06-utility-apis.md index 4fbbf1af94..bacdcefce2 100644 --- a/docs/frontend-system/architecture/06-utility-apis.md +++ b/docs/frontend-system/architecture/06-utility-apis.md @@ -10,39 +10,21 @@ description: Utility APIs ## Overview -Utility APIs are pieces of standalone functionality, interfaces that can be -requested by plugins to use. They are defined by a TypeScript interface as well -as a reference (an "API ref") used to access its implementation. They can be -provided both by plugins and the core framework, and are themselves -[extensions](../architecture/03-extensions.md) that can have inputs, be -replaced, and be declaratively configured in your app-config. +Utility APIs are pieces of standalone functionality, interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can have inputs, be replaced, and be declaratively configured in your app-config. -A common example of a utility API is a client interface to interact with the -backend part of a plugin, such as the catalog client. Any frontend plugin can -then request an implementation of that interface to make requests through. +A common example of a utility API is a client interface to interact with the backend part of a plugin, such as the catalog client. Any frontend plugin can then request an implementation of that interface to make requests through. -The following diagram shows a hypothetical application, which depends on two -plugins and also provides some extra overrides. Note that both the plugins and -the core framework exposed utility APIs, and that they depended on each other. -The app also chose to use its overrides mechanism to supply a replacement -implementation of one API, which took precedence over the default one. Thus, all -consumers of that API will be sure to get that new implementation provided to -them. +The following diagram shows a hypothetical application, which depends on two plugins and also provides some extra overrides. Note that both the plugins and the core framework exposed utility APIs, and that they depended on each other. The app also chose to use its overrides mechanism to supply a replacement implementation of one API, which took precedence over the default one. Thus, all consumers of that API will be sure to get that new implementation provided to them. ![frontend system utility apis diagram](../../assets/frontend-system/architecture-utility-apis.drawio.svg) ## Extension structure -All utility APIs implement the `coreExtensionData.apiFactory` output data type, -and must attach exclusively to the `core` extension's `apis` input no matter who -provided them. These defaults are provided out of the box by the -`createApiExtension` framework function. +All utility APIs implement the `coreExtensionData.apiFactory` output data type, and must attach exclusively to the `core` extension's `apis` input no matter who provided them. These defaults are provided out of the box by the `createApiExtension` framework function. -Since utility APIs are extensions, they can also have inputs in advanced use -cases. This is occasionally useful for complex APIs that can themselves be -extended with additional programmatic functionality by adopters. +Since utility APIs are extensions, they can also have inputs in advanced use cases. This is occasionally useful for complex APIs that can themselves be extended with additional programmatic functionality by adopters. ## Links -- The [Using Utility APIs section](../building-plugins/03-utility-apis.md) of the plugin docs +- The [Utility APIs section](../utility-apis/01-index.md) of the plugin docs - The legacy docs on [utility APIs](../../api/utility-apis.md) diff --git a/docs/frontend-system/utility-apis/01-index.md b/docs/frontend-system/utility-apis/01-index.md index 353ad3ffe4..b6eb22706d 100644 --- a/docs/frontend-system/utility-apis/01-index.md +++ b/docs/frontend-system/utility-apis/01-index.md @@ -12,7 +12,7 @@ As described [in the architecture section](../architecture/06-utility-apis.md), > For details, [see the main article](./02-creating.md). -Backstage apps, the core Backstage framework, and plugins can all expose utility APIs for general use. +Backstage apps, plugins, and the core Backstage framework can all expose utility APIs for general use. Some are available out of the box, such as the API for reading app configuration. Some are provided by third party plugins, such as the catalog client API that both the catalog itself and your own code can leverage to talk to the catalog backend. Some, you may create yourself and make available inside your Backstage instance for use within your private ecosystem of plugins. diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index b7bf7d448d..019ca254bb 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -134,7 +134,7 @@ links: There are multiple authentication providers available for you to use with Backstage, feel free to follow -[the instructions for adding authentication](../auth/). +[the instructions for adding authentication](../auth/index.md). For this tutorial we choose to use GitHub, a free service most of you might be familiar with. For other options, see @@ -205,7 +205,7 @@ Restart Backstage from the terminal, by stopping it with `Control-C`, and starti To learn more about Authentication in Backstage, here are some docs you could read: -- [Authentication in Backstage](../auth/) +- [Authentication in Backstage](../auth/index.md) - [Using organizational data from GitHub](../integrations/github/org.md) ### Setting up a GitHub Integration @@ -254,7 +254,7 @@ integrations: Some helpful links, for if you want to learn more about: -- [Other available integrations](../integrations/) +- [Other available integrations](../integrations/index.md) - [Using GitHub Apps instead of a Personal Access Token](../integrations/github/github-apps.md#docsNav) ### Explore what we've done so far From 34ebdb5f199dc0d304d953ef3a90666da108130b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 12 Dec 2023 16:30:24 +0100 Subject: [PATCH 03/11] flesh out all but the configuration section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- docs/frontend-system/utility-apis/01-index.md | 10 +- .../utility-apis/02-creating.md | 10 +- .../utility-apis/03-consuming.md | 2 +- .../utility-apis/04-configuring.md | 2 + .../utility-apis/05-migrating.md | 131 +++++++++++++++++- 5 files changed, 143 insertions(+), 12 deletions(-) diff --git a/docs/frontend-system/utility-apis/01-index.md b/docs/frontend-system/utility-apis/01-index.md index b6eb22706d..a6c9f6a784 100644 --- a/docs/frontend-system/utility-apis/01-index.md +++ b/docs/frontend-system/utility-apis/01-index.md @@ -8,7 +8,7 @@ description: Working with Utility APIs in the New Frontend System As described [in the architecture section](../architecture/06-utility-apis.md), utility APIs are pieces of shared functionality - interfaces that can be requested by plugins to use. They are defined by a TypeScript interface as well as a reference (an "API ref") used to access its implementation. They can be provided both by plugins and the core framework, and are themselves [extensions](../architecture/03-extensions.md) that can accept inputs, be declaratively configured in your app-config, or transparently be replaced entirely with custom implementations that fulfill the same contract. -## Creating Utility APIs +## Creating utility APIs > For details, [see the main article](./02-creating.md). @@ -18,7 +18,7 @@ Some are available out of the box, such as the API for reading app configuration [The main article](./02-creating.md) describes the process of creating and exposing utility APIs of your own, for sharing functionality or configurability across plugins and apps. -## Consuming Utility APIs +## Consuming utility APIs > For details, [see the main article](./03-consuming.md). @@ -28,10 +28,14 @@ Some utility APIs in turn depend on other utility APIs. This powerful composabil These are described in detail in [the main article](./03-consuming.md) -## Configuring Utility APIs +## Configuring utility APIs > For details, [see the main article](./04-configuring.md). Most utility APIs are usable directly without any configuration. But they are proper extensions, and can therefore have their implementations entirely swapped out by your app for advanced use cases. They can also be built with the ability to configured in your app-config, or to have inputs that extend their functionality. These cases are all described in [the main article](./04-configuring.md). + +## Migrating from the old frontend system + +If you want to learn how to migrate your own utility APIs from the old frontend system to the new one, that's described in [a dedicated migration guide](./05-migrating.md). diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index 58655a24f5..92bd623744 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -62,12 +62,8 @@ const workApi = createApiExtension({ factory: () => createApiFactory({ api: workApiRef, - deps: { - configApi: configApiRef, - }, - factory: ({ configApi }) => { - return new WorkImpl({ configApi }); - }, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => new WorkImpl({ configApi }), }), }); @@ -81,7 +77,7 @@ export default createPlugin({ }); ``` -For illustration we make a skeleton implementation class and the API extension and factory for it, in the same file. These are not exported to the public surface of the plugin package; only the plugin is, as the default export. +For illustration we make a skeleton implementation class and the API extension and factory for it, in the same file. These are not exported to the public surface of the plugin package; only the plugin is, as the default export. Users who install the plugin will now get the utility API automatically as well. The code also illustrates how the API factory declares a dependency on another utility API - the core config API in this case. An instance of that utility API is then provided to the factory function. diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md index 7c339f1bf9..b24489a21d 100644 --- a/docs/frontend-system/utility-apis/03-consuming.md +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -43,9 +43,9 @@ Your utility APIs can depend on other utility APIs in their factories. You do th ```tsx import { + configApiRef, createApiExtension, createApiFactory, - configApiRef, discoveryApiRef, } from '@backstage/frontend-plugin-api'; import { MyApiImpl } from './MyApiImpl'; diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index d1de1aa070..b56cd10c5c 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -23,3 +23,5 @@ description: Configuring, extending, and overriding utility APIs > TODO ## Replacing a utility API implementation + +> TODO diff --git a/docs/frontend-system/utility-apis/05-migrating.md b/docs/frontend-system/utility-apis/05-migrating.md index 0334e80f0b..f3e85ceaf4 100644 --- a/docs/frontend-system/utility-apis/05-migrating.md +++ b/docs/frontend-system/utility-apis/05-migrating.md @@ -6,4 +6,133 @@ sidebar_label: Migrating description: Migrating Utility APIs from the old frontend system --- -> TODO +If you are migrating your plugins or app over from the old frontend system, there are a few things to keep in mind in regards to utility APIs. + +## Overview + +- Migrate your repo overall to the latest release of Backstage +- Follow the plugin migration guide +- Change your package dependencies from `core-*-api` to `frontend-*-api` +- Change the imports in your code from `core-*-api` to `frontend-*-api` +- Keep the TypeScript interface and API ref exported as they were, except possibly reconsidering the choice of ID of the latter +- Wrap the old API factory call in an extension using `createApiExtension` +- Make sure that this extension is referenced by your migrated plugin + +## Prerequisites + +This guide assumes that you first [upgrade your repo](../../getting-started/keeping-backstage-updated.md) to the latest release of Backstage. This ensures that you do not have to fight several types of incompatibilities and updates at the same time. + +## Dependency changes + +In this article we will discuss some interfaces that you used to import from the `@backstage/core-plugin-api` package. Those are now generally moved over to `@backstage/frontend-plugin-api`, so you will want to update both your `package.json` and your source code. This applies both in your `-react` package and your main plugin package. + +First `package.json`. The following commands are examples - note that they refer to `plugins/example`, which you'll have to update to the actual folder name that your package to migrate is in. + +```bash title="from your repo root" +yarn --cwd plugins/example remove @backstage/core-plugin-api ; +yarn --cwd plugins/example add @backstage/frontend-plugin-api +``` + +Now in all of the code files in that package: + +```tsx title="in your source code" +/* highlight-remove-next-line */ +import { createApiRef } from '@backstage/core-plugin-api'; +/* highlight-add-next-line */ +import { createApiRef } from '@backstage/frontend-plugin-api'; +``` + +These can typically be search-and-replaced wholesale - the interfaces in the new package are mostly identical to the old one. The `createApiRef` is just an example, and the same replacement makes sense for all of the other symbols from the core package as well. + +## React package interface and ref changes + +Let's begin with [your `-react` package](../../architecture-decisions/adr011-plugin-package-structure.md). The act of exporting TypeScript interfaces and API refs have not changed from the old system. You can typically keep those as-is. For illustrative purposes, this is an example of an interface and its API ref: + +```tsx title="in @internal/plugin-example-react" +import { createApiRef } from '@backstage/frontend-plugin-api'; + +/** + * Performs some work. + * @oublic + */ +export interface WorkApi { + doWork(): Promise; +} + +/** + * The work interface for the Example plugin. + * @public + */ +export const workApiRef = createApiRef({ + id: 'plugin.example.work', +}); +``` + +In this example, the plugin ID already follows the common naming convention. If it doesn't, you may want to consider renaming that ID at this point. Don't worry, this won't hurt consumers in the old frontend system since the ID is mostly used for debugging purposes there. In the new system, it's much more important and appears in app-config files and similar. + +Note at the top of the file that it uses the updated import from `@backstage/frontend-plugin-api` that we migrated in the previous section, instead of the old `@backstage/core-plugin-api`. + +## Plugin package changes + +Now let's turn to the main plugin package where the plugin itself is exported. You will probably already have a `createPlugin` call in here. Before we changed the `core-plugin-api` imports it'll have looked somewhat similar to the following: + +```tsx title="in @internal/plugin-example, NOTE THIS IS LEGACY CODE" +import { + configApiRef, + createPlugin, + createApiFactory, +} from '@backstage/core-plugin-api'; +import { workApiRef } from '@internal/plugin-example-react'; +import { WorkImpl } from './WorkImpl'; + +const workApi = createApiFactory({ + api: workApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => new WorkImpl({ configApi }), +}); + +/** @public */ +export const catalogPlugin = createPlugin({ + id: 'example', + apis: [workApi], +}); +``` + +The major changes we'll make are + +- Change the import to the new package as per the top section of this guide +- Wrap the existing API factory in a `createApiExtension` +- Change to the new version of `createPlugin` which exports this extension +- Change the plugin export to be the default instead + +```tsx title="in @internal/plugin-example" +import { + configApiRef, + createPlugin, + createApiFactory, + createApiExtension, +} from '@backstage/frontend-plugin-api'; +import { workApiRef } from '@internal/plugin-example-react'; +import { WorkImpl } from './WorkImpl'; + +const workApi = createApiExtension({ + api: workApiRef, + factory: () => + // The factory itself is unchanged + createApiFactory({ + api: workApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => new WorkImpl({ configApi }), + }), +}); + +/** @public */ +export default createPlugin({ + id: 'example', + extensions: [workApi], +}); +``` + +## Further work + +Since utility APIs are now complete extensions, you may want to take a bigger look at how they used to be used, and what the new frontend system offers. You may for example consider [adding configurability or inputs](./04-configuring.md) to your API, if that makes sense for your current application. From 8568feabdf633b71a131b5a0af2e0abd8a1f7530 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 13 Dec 2023 16:04:17 +0100 Subject: [PATCH 04/11] begin on the configuring section MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../utility-apis/02-creating.md | 12 ++-- .../utility-apis/03-consuming.md | 2 +- .../utility-apis/04-configuring.md | 57 ++++++++++++++++++- .../utility-apis/05-migrating.md | 12 ++-- 4 files changed, 69 insertions(+), 14 deletions(-) diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index 92bd623744..9d71d3ba14 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -43,13 +43,13 @@ import { createApiExtension, createApiFactory, createPlugin, - configApiRef, - ConfigApi, + storageApiRef, + StorageApi, } from '@backstage/frontend-plugin-api'; import { WorkApi, workApiRef } from '@internal/plugin-example-react'; class WorkImpl implements WorkApi { - constructor(options: { configApi: ConfigApi }) { + constructor(options: { storageApiRef: StorageApi }) { /* TODO */ } async doWork() { @@ -62,8 +62,8 @@ const workApi = createApiExtension({ factory: () => createApiFactory({ api: workApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => new WorkImpl({ configApi }), + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), }), }); @@ -79,7 +79,7 @@ export default createPlugin({ For illustration we make a skeleton implementation class and the API extension and factory for it, in the same file. These are not exported to the public surface of the plugin package; only the plugin is, as the default export. Users who install the plugin will now get the utility API automatically as well. -The code also illustrates how the API factory declares a dependency on another utility API - the core config API in this case. An instance of that utility API is then provided to the factory function. +The code also illustrates how the API factory declares a dependency on another utility API - the core storage API in this case. An instance of that utility API is then provided to the factory function. The resulting extension ID of the work API will be the kind `api:` followed by the plugin ID as the namespace, in this case ending up as `api:plugin.example.work`. You can now use this ID to refer to the API in app-config and elsewhere. diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md index b24489a21d..006f3b8fab 100644 --- a/docs/frontend-system/utility-apis/03-consuming.md +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -64,4 +64,4 @@ const myApi = createApiExtension({ }); ``` -Note how the `deps` section essentially assigns free-form names that you choose, to API refs. In this example we for example map `configApiRef` to the name `configApi`, but that's just a convention. The framework will ensure that all of those deps get instantiated and passed into your `factory` function with the same set of names as your `deps`. At that point, `configApi` refers to an actual functioning instance of that API ref. +Note how the `deps` section essentially assigns free-form names that you choose, to API refs. Here we for example map `configApiRef` to the name `configApi`, but that's just a convention. The framework will ensure that all of those deps get instantiated and passed into your `factory` function with the same set of names as your `deps`. At that point, `configApi` refers to an actual functioning instance of that API ref. diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index b56cd10c5c..d6b6167ca2 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -6,9 +6,64 @@ sidebar_label: Configuring description: Configuring, extending, and overriding utility APIs --- +Utility APIs are extensions and can therefore optionally be amended with configurability, as well as inputs that other extensions attach themselves to. This section describes how to add those capabilities to your own utility APIs, and how to make use of them as a consumer of such utility APIs. + ## Adding configurability -> TODO +Here we will describe how to amend a utility API with the capability of being configured in app-config. You do this by giving a config schema to your API extension factory function. + +Let's make the required additions to [our original work example](./02-creating.md) API. + +```tsx title="in @internal/plugin-example" +import { + createApiExtension, + createApiFactory, + createPlugin, + /* highlight-add-next-line */ + createSchemaFromZod, + storageApiRef, + StorageApi, +} from '@backstage/frontend-plugin-api'; +import { WorkApi, workApiRef } from '@internal/plugin-example-react'; + +/* highlight-add-start */ +interface WorkApiConfig { + goSlow: boolean; +} +/* highlight-add-end */ + +const workApi = createApiExtension({ + api: workApiRef, + /* highlight-add-start */ + configSchema: createSchemaFromZod(z => + z.object({ + goSlow: z.boolean().default(false), + }), + ), + /* highlight-add-end */ + /* highlight-remove-next-line */ + factory: () => + /* highlight-add-next-line */ + factory: ({ config }) => + createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => { + /* highlight-add-start */ + if (config.goSlow) { + /* ... */ + } + /* highlight-add-end */ + }, + }), +}); +``` + +We wanted users to be able to configure a `goSlow` parameter for our API instances. So we added another interface type for holding our various options, and passed in a `configSchema` to `createApiExtension` which matches that interface. This example builds it using [the zod library](https://zod.dev/). The actual config values will then be passed in to the `factory` callback, where we can do what we wish with them. + +Note that while we use the word "config" here, it's _not_ the same thing as the `configApi` which gives you access to the full app-config. The config discussed here is instead the particular configuration settings given to your utility API instance. This is discussed more [in the section below](#configuring). + +Note also that the config schema contained a default value fo the `goSlow` field. This is an important consideration. You want users of your API to be able to make maximum use of it, without having to dive deep into how to configure it. For that reason you generally want to provide as many sane defaults as possible, while letting users override them rarely but with purpose, only when called for. If you have a config schema without defaults, the framework will refuse to instantiate the utility API on startup unless the user had configured those values explicitly. Since it had a default value, the TypeScript code and interfaces also don't have to defensively allow `undefined` - we know that it'll have either the default value or an overridden value when we start consuming the config data. ## Configuring diff --git a/docs/frontend-system/utility-apis/05-migrating.md b/docs/frontend-system/utility-apis/05-migrating.md index f3e85ceaf4..b4b456e65f 100644 --- a/docs/frontend-system/utility-apis/05-migrating.md +++ b/docs/frontend-system/utility-apis/05-migrating.md @@ -78,7 +78,7 @@ Now let's turn to the main plugin package where the plugin itself is exported. Y ```tsx title="in @internal/plugin-example, NOTE THIS IS LEGACY CODE" import { - configApiRef, + storageApiRef, createPlugin, createApiFactory, } from '@backstage/core-plugin-api'; @@ -87,8 +87,8 @@ import { WorkImpl } from './WorkImpl'; const workApi = createApiFactory({ api: workApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => new WorkImpl({ configApi }), + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), }); /** @public */ @@ -107,7 +107,7 @@ The major changes we'll make are ```tsx title="in @internal/plugin-example" import { - configApiRef, + storageApiRef, createPlugin, createApiFactory, createApiExtension, @@ -121,8 +121,8 @@ const workApi = createApiExtension({ // The factory itself is unchanged createApiFactory({ api: workApiRef, - deps: { configApi: configApiRef }, - factory: ({ configApi }) => new WorkImpl({ configApi }), + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), }), }); From 7da59f36bd3bef0009fb9f8143813a6549bee057 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 15 Dec 2023 14:55:40 +0100 Subject: [PATCH 05/11] Update docs/frontend-system/architecture/06-utility-apis.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Signed-off-by: Fredrik Adelöw --- docs/frontend-system/architecture/06-utility-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/architecture/06-utility-apis.md b/docs/frontend-system/architecture/06-utility-apis.md index bacdcefce2..9b380ae592 100644 --- a/docs/frontend-system/architecture/06-utility-apis.md +++ b/docs/frontend-system/architecture/06-utility-apis.md @@ -14,7 +14,7 @@ Utility APIs are pieces of standalone functionality, interfaces that can be requ A common example of a utility API is a client interface to interact with the backend part of a plugin, such as the catalog client. Any frontend plugin can then request an implementation of that interface to make requests through. -The following diagram shows a hypothetical application, which depends on two plugins and also provides some extra overrides. Note that both the plugins and the core framework exposed utility APIs, and that they depended on each other. The app also chose to use its overrides mechanism to supply a replacement implementation of one API, which took precedence over the default one. Thus, all consumers of that API will be sure to get that new implementation provided to them. +The following diagram shows a hypothetical application, which depends on two plugins and also provides some extra overrides. Note that both the plugins and the core framework provide utility APIs, and that they depended on each other. The app also choses to use its overrides mechanism to supply a replacement implementation of one API, which takes precedence over the default one. Thus, all consumers of that API will be sure to get that new implementation provided to them. ![frontend system utility apis diagram](../../assets/frontend-system/architecture-utility-apis.drawio.svg) From 812fc5440512e04a459c647ead9894d42226165d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 15 Dec 2023 14:56:17 +0100 Subject: [PATCH 06/11] Update docs/frontend-system/utility-apis/02-creating.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Signed-off-by: Fredrik Adelöw --- docs/frontend-system/utility-apis/02-creating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index 9d71d3ba14..5e940ca678 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -12,7 +12,7 @@ description: Creating new utility APIs in your plugins and app ## Creating the Utility API contract -The first step toward exposing a utility API is to define its TypeScript contract, and an API ref for consumers to find it via. This should be done in [your plugin's `-react` package](../../architecture-decisions/adr011-plugin-package-structure.md), so that it can be imported by other plugins. In this example, we have an Example plugin that wants to expose a utility API for performing some type of work. +The first step toward exposing a utility API is to define its TypeScript contract, as well as an API reference for consumers use to access the implementation. If you want your API to be accessible by other plugins this should be done in [your plugin's `-react` package](../../architecture-decisions/adr011-plugin-package-structure.md), so that it can be imported separately. If you just want to use the API within your own plugin it is fine to place the definition within the plugin itself. In this example, we have an Example plugin that wants to expose a utility API for performing some type of work. ```tsx title="in @internal/plugin-example-react" import { createApiRef } from '@backstage/frontend-plugin-api'; From 9bc836cc579b950aec67ccfa537244f710919b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 15 Dec 2023 14:56:41 +0100 Subject: [PATCH 07/11] Update docs/frontend-system/architecture/06-utility-apis.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Signed-off-by: Fredrik Adelöw --- docs/frontend-system/architecture/06-utility-apis.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/architecture/06-utility-apis.md b/docs/frontend-system/architecture/06-utility-apis.md index 9b380ae592..ff106e2ea6 100644 --- a/docs/frontend-system/architecture/06-utility-apis.md +++ b/docs/frontend-system/architecture/06-utility-apis.md @@ -20,7 +20,7 @@ The following diagram shows a hypothetical application, which depends on two plu ## Extension structure -All utility APIs implement the `coreExtensionData.apiFactory` output data type, and must attach exclusively to the `core` extension's `apis` input no matter who provided them. These defaults are provided out of the box by the `createApiExtension` framework function. +All utility APIs implement the `createApiExtension.factoryDataRef` output data type, and must attach exclusively to the `core` extension's `apis` input no matter who provided them. These defaults are provided out of the box by the `createApiExtension` framework function. Since utility APIs are extensions, they can also have inputs in advanced use cases. This is occasionally useful for complex APIs that can themselves be extended with additional programmatic functionality by adopters. From 67b15262f101e3f74923edee892e52604f9e1e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 15 Dec 2023 14:57:44 +0100 Subject: [PATCH 08/11] Update docs/frontend-system/utility-apis/02-creating.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Signed-off-by: Fredrik Adelöw --- docs/frontend-system/utility-apis/02-creating.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index 5e940ca678..e56220841c 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -57,14 +57,12 @@ class WorkImpl implements WorkApi { } } -const workApi = createApiExtension({ - api: workApiRef, - factory: () => - createApiFactory({ - api: workApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => new WorkImpl({ storageApi }), - }), +const exampleWorkApi = createApiExtension({ + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), + }), }); /** From 8d469f8991b9487c1f44bfc09d4422a67e4f6c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 15 Dec 2023 14:58:42 +0100 Subject: [PATCH 09/11] Update docs/frontend-system/utility-apis/02-creating.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Signed-off-by: Fredrik Adelöw --- docs/frontend-system/utility-apis/02-creating.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index e56220841c..49fce0b307 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -18,15 +18,19 @@ The first step toward exposing a utility API is to define its TypeScript contrac import { createApiRef } from '@backstage/frontend-plugin-api'; /** - * Performs some work. - * @oublic + * The work interface for the Example plugin. + * @public */ export interface WorkApi { + /** + * Performs some work. + * @public + */ doWork(): Promise; } /** - * The work interface for the Example plugin. + * API Reference for {@link WorkApi}. * @public */ export const workApiRef = createApiRef({ From 2c0ef3f119ad7e81fde233e1dff901866a88d173 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 15 Dec 2023 14:59:01 +0100 Subject: [PATCH 10/11] Update docs/frontend-system/utility-apis/02-creating.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Patrik Oldsberg Signed-off-by: Fredrik Adelöw --- docs/frontend-system/utility-apis/02-creating.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/frontend-system/utility-apis/02-creating.md b/docs/frontend-system/utility-apis/02-creating.md index 49fce0b307..4f6c329df7 100644 --- a/docs/frontend-system/utility-apis/02-creating.md +++ b/docs/frontend-system/utility-apis/02-creating.md @@ -75,7 +75,7 @@ const exampleWorkApi = createApiExtension({ */ export default createPlugin({ id: 'example', - extensions: [workApi], + extensions: [exampleWorkApi], }); ``` From cc6ccb5e11c61a7f4eeaff4ee90638793850f338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 15 Dec 2023 15:41:28 +0100 Subject: [PATCH 11/11] review fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../architecture/06-utility-apis.md | 2 +- .../utility-apis/03-consuming.md | 4 +-- .../utility-apis/04-configuring.md | 29 ++++++++-------- .../utility-apis/05-migrating.md | 34 +++++-------------- 4 files changed, 26 insertions(+), 43 deletions(-) diff --git a/docs/frontend-system/architecture/06-utility-apis.md b/docs/frontend-system/architecture/06-utility-apis.md index ff106e2ea6..c0b65a840b 100644 --- a/docs/frontend-system/architecture/06-utility-apis.md +++ b/docs/frontend-system/architecture/06-utility-apis.md @@ -14,7 +14,7 @@ Utility APIs are pieces of standalone functionality, interfaces that can be requ A common example of a utility API is a client interface to interact with the backend part of a plugin, such as the catalog client. Any frontend plugin can then request an implementation of that interface to make requests through. -The following diagram shows a hypothetical application, which depends on two plugins and also provides some extra overrides. Note that both the plugins and the core framework provide utility APIs, and that they depended on each other. The app also choses to use its overrides mechanism to supply a replacement implementation of one API, which takes precedence over the default one. Thus, all consumers of that API will be sure to get that new implementation provided to them. +The following diagram shows a hypothetical application, which depends on two plugins and also provides some extra overrides. Note that both the plugins and the core framework provide utility APIs, and that they depended on each other. The app also chooses to use its overrides mechanism to supply a replacement implementation of one API, which takes precedence over the default one. Thus, all consumers of that API will be sure to get that new implementation provided to them. ![frontend system utility apis diagram](../../assets/frontend-system/architecture-utility-apis.drawio.svg) diff --git a/docs/frontend-system/utility-apis/03-consuming.md b/docs/frontend-system/utility-apis/03-consuming.md index 006f3b8fab..4748cc786a 100644 --- a/docs/frontend-system/utility-apis/03-consuming.md +++ b/docs/frontend-system/utility-apis/03-consuming.md @@ -17,7 +17,7 @@ import { useApi, configApiRef } from '@backstage/frontend-plugin-api'; const MyComponent = () => { const configApi = useApi(configApiRef); - const baseUrl = configApi.getString('backend.baseUrl'); + const title = configApi.getString('app.title'); // ... }; ``` @@ -31,7 +31,7 @@ const MyComponent = () => { const apis = useApiHolder(); const configApi = apis.get(configApiRef); // may return undefined if (configApi) { - const baseUrl = configApi.getString('backend.baseUrl'); + const title = configApi.getString('app.title'); // ... } }; diff --git a/docs/frontend-system/utility-apis/04-configuring.md b/docs/frontend-system/utility-apis/04-configuring.md index d6b6167ca2..334fb4d6b5 100644 --- a/docs/frontend-system/utility-apis/04-configuring.md +++ b/docs/frontend-system/utility-apis/04-configuring.md @@ -33,8 +33,8 @@ interface WorkApiConfig { /* highlight-add-end */ const workApi = createApiExtension({ - api: workApiRef, /* highlight-add-start */ + api: workApiRef, configSchema: createSchemaFromZod(z => z.object({ goSlow: z.boolean().default(false), @@ -42,24 +42,23 @@ const workApi = createApiExtension({ ), /* highlight-add-end */ /* highlight-remove-next-line */ - factory: () => + factory: createApiFactory({ /* highlight-add-next-line */ - factory: ({ config }) => - createApiFactory({ - api: workApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => { - /* highlight-add-start */ - if (config.goSlow) { - /* ... */ - } - /* highlight-add-end */ - }, - }), + factory: ({ config }) => createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => { + /* highlight-add-start */ + if (config.goSlow) { + /* ... */ + } + /* highlight-add-end */ + }, + }), }); ``` -We wanted users to be able to configure a `goSlow` parameter for our API instances. So we added another interface type for holding our various options, and passed in a `configSchema` to `createApiExtension` which matches that interface. This example builds it using [the zod library](https://zod.dev/). The actual config values will then be passed in to the `factory` callback, where we can do what we wish with them. +We wanted users to be able to configure a `goSlow` parameter for our API instances. So we added another interface type for holding our various options, and passed in a `configSchema` to `createApiExtension` which matches that interface. This example builds it using [the zod library](https://zod.dev/). The actual config values will then be passed in to the `factory` which is now a callback, wherein we can do what we wish with them. When changing to the callback form, we also had to add a top level `api: workApiRef` under `createApiExtension`. Note that while we use the word "config" here, it's _not_ the same thing as the `configApi` which gives you access to the full app-config. The config discussed here is instead the particular configuration settings given to your utility API instance. This is discussed more [in the section below](#configuring). diff --git a/docs/frontend-system/utility-apis/05-migrating.md b/docs/frontend-system/utility-apis/05-migrating.md index b4b456e65f..b8cb50c224 100644 --- a/docs/frontend-system/utility-apis/05-migrating.md +++ b/docs/frontend-system/utility-apis/05-migrating.md @@ -12,8 +12,7 @@ If you are migrating your plugins or app over from the old frontend system, ther - Migrate your repo overall to the latest release of Backstage - Follow the plugin migration guide -- Change your package dependencies from `core-*-api` to `frontend-*-api` -- Change the imports in your code from `core-*-api` to `frontend-*-api` +- Optionally change your package dependencies and code from `core-*-api` to `frontend-*-api` - Keep the TypeScript interface and API ref exported as they were, except possibly reconsidering the choice of ID of the latter - Wrap the old API factory call in an extension using `createApiExtension` - Make sure that this extension is referenced by your migrated plugin @@ -24,26 +23,14 @@ This guide assumes that you first [upgrade your repo](../../getting-started/keep ## Dependency changes -In this article we will discuss some interfaces that you used to import from the `@backstage/core-plugin-api` package. Those are now generally moved over to `@backstage/frontend-plugin-api`, so you will want to update both your `package.json` and your source code. This applies both in your `-react` package and your main plugin package. +In this article we will discuss some old interfaces that you used to import from the `@backstage/core-plugin-api` package. Those are now generally lifted over to `@backstage/frontend-plugin-api`, next to the new interfaces that are specific to the new frontend system. If you want to, you can already update your `package.json` and code imports to only use the new plugin API, but for the time being you don't have to. The old core exports will continue to work for the foreseeable future. -First `package.json`. The following commands are examples - note that they refer to `plugins/example`, which you'll have to update to the actual folder name that your package to migrate is in. +To at least get access to the new interfaces, you'll need to run the following command. Note that it's just an example! It refers to `plugins/example`, which you'll have to change to the actual folder name that your package to migrate is in. ```bash title="from your repo root" -yarn --cwd plugins/example remove @backstage/core-plugin-api ; yarn --cwd plugins/example add @backstage/frontend-plugin-api ``` -Now in all of the code files in that package: - -```tsx title="in your source code" -/* highlight-remove-next-line */ -import { createApiRef } from '@backstage/core-plugin-api'; -/* highlight-add-next-line */ -import { createApiRef } from '@backstage/frontend-plugin-api'; -``` - -These can typically be search-and-replaced wholesale - the interfaces in the new package are mostly identical to the old one. The `createApiRef` is just an example, and the same replacement makes sense for all of the other symbols from the core package as well. - ## React package interface and ref changes Let's begin with [your `-react` package](../../architecture-decisions/adr011-plugin-package-structure.md). The act of exporting TypeScript interfaces and API refs have not changed from the old system. You can typically keep those as-is. For illustrative purposes, this is an example of an interface and its API ref: @@ -100,7 +87,7 @@ export const catalogPlugin = createPlugin({ The major changes we'll make are -- Change the import to the new package as per the top section of this guide +- Optionally change the old imports to the new package as per the top section of this guide - Wrap the existing API factory in a `createApiExtension` - Change to the new version of `createPlugin` which exports this extension - Change the plugin export to be the default instead @@ -116,14 +103,11 @@ import { workApiRef } from '@internal/plugin-example-react'; import { WorkImpl } from './WorkImpl'; const workApi = createApiExtension({ - api: workApiRef, - factory: () => - // The factory itself is unchanged - createApiFactory({ - api: workApiRef, - deps: { storageApi: storageApiRef }, - factory: ({ storageApi }) => new WorkImpl({ storageApi }), - }), + factory: createApiFactory({ + api: workApiRef, + deps: { storageApi: storageApiRef }, + factory: ({ storageApi }) => new WorkImpl({ storageApi }), + }), }); /** @public */