From ebfeb40305ae72814d62ed01d5d27b06715c4798 Mon Sep 17 00:00:00 2001 From: Simon Stamm Date: Wed, 19 Jun 2024 17:53:00 +0200 Subject: [PATCH 1/2] feat(apidocs): add resolvers prop to AsyncApiDefinitionWidget Signed-off-by: Simon Stamm --- .changeset/plenty-mails-do.md | 5 ++ plugins/api-docs/README-alpha.md | 56 +++++++++++++++++ plugins/api-docs/README.md | 63 +++++++++++++++++-- plugins/api-docs/api-report.md | 9 +++ .../AsyncApiDefinition.tsx | 16 ++++- .../AsyncApiDefinitionWidget.tsx | 9 +++ .../AsyncApiDefinitionWidget/index.ts | 5 +- 7 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 .changeset/plenty-mails-do.md diff --git a/.changeset/plenty-mails-do.md b/.changeset/plenty-mails-do.md new file mode 100644 index 0000000000..6a9e815ebb --- /dev/null +++ b/.changeset/plenty-mails-do.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +Added `resolvers` prop to `AsyncApiDefinitionWidget`. This allows to override the default http/https resolvers, for example to add authentication to requests to internal schema registries. diff --git a/plugins/api-docs/README-alpha.md b/plugins/api-docs/README-alpha.md index 676a2c66c1..eb84ef8c27 100644 --- a/plugins/api-docs/README-alpha.md +++ b/plugins/api-docs/README-alpha.md @@ -50,6 +50,7 @@ To link that a component provides or consumes an API, see the [`providesApis`](h - [Custom Api Renderings](#custom-api-renderings) - [Adding Swagger UI Interceptor](#adding-requestinterceptor-to-swagger-ui) - [Providing Swagger UI Specific Supported Methods](#provide-specific-supported-methods-to-swagger-ui) + - [Custom Resolvers for AsyncApi](#custom-resolvers-for-asyncapi) - [Integrations](#integrations) - [Implementing OAuth 2 Authorization Code flow with Swagger UI](#implementing-oauth-2-authorization-code-flow-with-swagger-ui) @@ -1092,6 +1093,61 @@ export default createExtensionOverrides({ N.B. if you wish to disable the `Try It Out` feature for your API, you can provide an empty list to the `supportedSubmitMethods` parameter. +##### Custom Resolvers for AsyncApi + +You can override the default http/https resolvers, for example to add authentication to requests to internal schema registries by providing the `resolvers` prop to the `AsyncApiDefinitionWidget`. This is an example: + +```tsx +... +import { + AsyncApiDefinitionWidget, + apiDocsConfigRef, + defaultDefinitionWidgets, +} from '@backstage/plugin-api-docs'; +import { ApiEntity } from '@backstage/catalog-model'; + +export const apis: AnyApiFactory[] = [ +... + createApiFactory({ + api: apiDocsConfigRef, + deps: {}, + factory: () => { + const myCustomResolver = { + schema: 'https', + order: 1, + canRead: true, + async read(uri: any) { + const response = await fetch(request, { + headers: { + X-Custom: 'Custom', + }, + }); + return response.text(); + }, + }; + + const definitionWidgets = defaultDefinitionWidgets().map(obj => { + if (obj.type === 'asyncapi') { + return { + ...obj, + component: (definition) => ( + + ), + }; + } + return obj; + }); + + return { + getApiDefinitionWidget: (apiEntity: ApiEntity) => { + return definitionWidgets.find(d => d.type === apiEntity.spec.type); + }, + }; + } + }) +] +``` + ### Integrations #### Implementing OAuth 2 Authorization Code flow with Swagger UI diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index 2387752cc9..2738cc09ae 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -222,10 +222,6 @@ security: - [read_pets, write_pets] ``` -## Links - -- [The Backstage homepage](https://backstage.io) - ### Adding `requestInterceptor` to Swagger UI To configure a [`requestInterceptor` for Swagger UI](https://github.com/swagger-api/swagger-ui/tree/master/flavors/swagger-ui-react#requestinterceptor-proptypesfunc) you'll need to add the following to your `api.tsx`: @@ -319,3 +315,62 @@ export const apis: AnyApiFactory[] = [ N.B. if you wish to disable the `Try It Out` feature for your API, you can provide an empty list to the `supportedSubmitMethods` parameter. + +### Custom Resolvers for AsyncApi + +You can override the default http/https resolvers, for example to add authentication to requests to internal schema registries by providing the `resolvers` prop to the `AsyncApiDefinitionWidget`. This is an example: + +```tsx +... +import { + AsyncApiDefinitionWidget, + apiDocsConfigRef, + defaultDefinitionWidgets, +} from '@backstage/plugin-api-docs'; +import { ApiEntity } from '@backstage/catalog-model'; + +export const apis: AnyApiFactory[] = [ +... + createApiFactory({ + api: apiDocsConfigRef, + deps: {}, + factory: () => { + const myCustomResolver = { + schema: 'https', + order: 1, + canRead: true, + async read(uri: any) { + const response = await fetch(request, { + headers: { + X-Custom: 'Custom', + }, + }); + return response.text(); + }, + }; + + const definitionWidgets = defaultDefinitionWidgets().map(obj => { + if (obj.type === 'asyncapi') { + return { + ...obj, + component: (definition) => ( + + ), + }; + } + return obj; + }); + + return { + getApiDefinitionWidget: (apiEntity: ApiEntity) => { + return definitionWidgets.find(d => d.type === apiEntity.spec.type); + }, + }; + } + }) +] +``` + +## Links + +- [The Backstage homepage](https://backstage.io) diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index 2fc852777b..504a11151b 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -83,6 +83,15 @@ export const AsyncApiDefinitionWidget: ( // @public (undocumented) export type AsyncApiDefinitionWidgetProps = { definition: string; + resolvers?: AsyncApiResolver[]; +}; + +// @public (undocumented) +export type AsyncApiResolver = { + schema: string; + order: number; + canRead: boolean; + read(uri: any): Promise; }; // @public (undocumented) diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx index 85ce15132a..92052d9f6a 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx @@ -19,6 +19,7 @@ import '@asyncapi/react-component/styles/default.css'; import { makeStyles, alpha, darken } from '@material-ui/core/styles'; import React from 'react'; import { useTheme } from '@material-ui/core/styles'; +import { AsyncApiResolver } from './AsyncApiDefinitionWidget'; const useStyles = makeStyles(theme => ({ root: { @@ -143,7 +144,7 @@ const useStyles = makeStyles(theme => ({ }, })); -const httpsFetchResolver = { +const httpsFetchResolver: AsyncApiResolver = { schema: 'https', order: 1, canRead: true, @@ -153,7 +154,7 @@ const httpsFetchResolver = { }, }; -const httpFetchResolver = { +const httpFetchResolver: AsyncApiResolver = { schema: 'http', order: 1, canRead: true, @@ -175,15 +176,24 @@ const config = { type Props = { definition: string; + resolvers?: AsyncApiResolver[]; }; -export const AsyncApiDefinition = ({ definition }: Props): JSX.Element => { +export const AsyncApiDefinition = ({ + definition, + resolvers, +}: Props): JSX.Element => { const classes = useStyles(); const theme = useTheme(); const classNames = `${classes.root} ${ theme.palette.type === 'dark' ? classes.dark : '' }`; + // Overwrite default resolvers if custom ones are set + if (resolvers) { + config.parserOptions.__unstable.resolver.resolvers = resolvers; + } + return (
diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx index 2e8710b008..7089143610 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx @@ -25,9 +25,18 @@ const LazyAsyncApiDefinition = React.lazy(() => })), ); +/** @public */ +export type AsyncApiResolver = { + schema: string; + order: number; + canRead: boolean; + read(uri: any): Promise; +}; + /** @public */ export type AsyncApiDefinitionWidgetProps = { definition: string; + resolvers?: AsyncApiResolver[]; }; /** @public */ diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts index 7cc49e3138..116eb81295 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts @@ -15,4 +15,7 @@ */ export { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget'; -export type { AsyncApiDefinitionWidgetProps } from './AsyncApiDefinitionWidget'; +export type { + AsyncApiDefinitionWidgetProps, + AsyncApiResolver, +} from './AsyncApiDefinitionWidget'; From 89f63c520690c26c35ab913c27c2cf20444985f5 Mon Sep 17 00:00:00 2001 From: Simon Stamm Date: Mon, 1 Jul 2024 08:41:47 +0200 Subject: [PATCH 2/2] fix(apidocs): move type AsyncApiResolver and config to AsyncApiDefinition component Signed-off-by: Simon Stamm --- .../AsyncApiDefinition.tsx | 29 ++++++++++++------- .../AsyncApiDefinitionWidget.tsx | 9 +----- .../AsyncApiDefinitionWidget/index.ts | 6 ++-- 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx index 92052d9f6a..4817774182 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx @@ -19,7 +19,14 @@ import '@asyncapi/react-component/styles/default.css'; import { makeStyles, alpha, darken } from '@material-ui/core/styles'; import React from 'react'; import { useTheme } from '@material-ui/core/styles'; -import { AsyncApiResolver } from './AsyncApiDefinitionWidget'; + +/** @public */ +export type AsyncApiResolver = { + schema: string; + order: number; + canRead: boolean; + read(uri: any): Promise; +}; const useStyles = makeStyles(theme => ({ root: { @@ -164,16 +171,6 @@ const httpFetchResolver: AsyncApiResolver = { }, }; -const config = { - parserOptions: { - __unstable: { - resolver: { - resolvers: [httpsFetchResolver, httpFetchResolver], - }, - }, - }, -}; - type Props = { definition: string; resolvers?: AsyncApiResolver[]; @@ -189,6 +186,16 @@ export const AsyncApiDefinition = ({ theme.palette.type === 'dark' ? classes.dark : '' }`; + const config = { + parserOptions: { + __unstable: { + resolver: { + resolvers: [httpsFetchResolver, httpFetchResolver], + }, + }, + }, + }; + // Overwrite default resolvers if custom ones are set if (resolvers) { config.parserOptions.__unstable.resolver.resolvers = resolvers; diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx index 7089143610..104a55e576 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx @@ -16,6 +16,7 @@ import { Progress } from '@backstage/core-components'; import React, { Suspense } from 'react'; +import { AsyncApiResolver } from './AsyncApiDefinition'; // The asyncapi component and related CSS has a significant size, only load it // if the element is actually used. @@ -25,14 +26,6 @@ const LazyAsyncApiDefinition = React.lazy(() => })), ); -/** @public */ -export type AsyncApiResolver = { - schema: string; - order: number; - canRead: boolean; - read(uri: any): Promise; -}; - /** @public */ export type AsyncApiDefinitionWidgetProps = { definition: string; diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts index 116eb81295..3b07c32cfe 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts @@ -15,7 +15,5 @@ */ export { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget'; -export type { - AsyncApiDefinitionWidgetProps, - AsyncApiResolver, -} from './AsyncApiDefinitionWidget'; +export type { AsyncApiDefinitionWidgetProps } from './AsyncApiDefinitionWidget'; +export type { AsyncApiResolver } from './AsyncApiDefinition';