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] 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