From 50449fc512e755a54eda8cf7acbdede5281b235a Mon Sep 17 00:00:00 2001 From: Konstantin Dichev Date: Thu, 8 Apr 2021 12:47:02 +0200 Subject: [PATCH 01/58] Fix Broken Link on home page --- microsite/pages/en/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js index 47f9568d9b..a761d6b6e2 100644 --- a/microsite/pages/en/index.js +++ b/microsite/pages/en/index.js @@ -292,7 +292,7 @@ class Index extends React.Component { Build your own software templates Contribute From 4a41e6e1632cbb9008e845dabc2f157aaed418fd Mon Sep 17 00:00:00 2001 From: Konstantin Dichev Date: Thu, 8 Apr 2021 12:47:02 +0200 Subject: [PATCH 02/58] Fix Broken Link on home page Signed-off-by: kdichev --- microsite/pages/en/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js index 47f9568d9b..a761d6b6e2 100644 --- a/microsite/pages/en/index.js +++ b/microsite/pages/en/index.js @@ -292,7 +292,7 @@ class Index extends React.Component { Build your own software templates Contribute From 3d9de0164c2c93ddef1576dca0c4e59500902783 Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Sun, 4 Apr 2021 16:35:05 +0100 Subject: [PATCH 03/58] tutorial: Using AWS Application Load Balancer with Azure Active Directory to authenticate requests Signed-off-by: Marco Crivellaro --- .../docs/tutorials/aws-alb-aad-oidc-auth.md | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 contrib/docs/tutorials/aws-alb-aad-oidc-auth.md diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md new file mode 100644 index 0000000000..9dab72fe66 --- /dev/null +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -0,0 +1,179 @@ +# Using AWS Application Load Balancer with Azure Active Directory to authenticate requests + +Backstage allows to offload the responsibility of authenticating users to AWS Application Loadbalancer (**ALB**), leveraging the authentication support on ALB. +This tutorial shows how to use authentication on an ALB sitting in front of Backstage. +Azure Active Directory (**AAD**) is used as identity provider but any identity provider supporting OpenID Connect (OIDC) can be used. + +It is assumed an ALB is already serving traffic in front of a Backstage instance configured to serve the frontend app from the backend. + +## Infrastructure setup + +### AAD App + +The AAD App is used to execute the authentication flow, serve and refresh the identity token. + +Create the AAD App following the steps outlined in `Create a Microsoft App Registration in Microsoft Portal` section from the tutorial [Monorepo App Setup With Authentication][monorepo-app-setup-with-auth]. + +Instead of `localhost` addresses, use the following values. + +- Identifier URI: `https://backstage.yourdomain.com` +- Redirect URI: `https://backstage.yourdomain.com/oauth2/idpresponse` + +`Application (client) Id`, `Directory (tenant) ID` and `client secret`values will be used while configuring the ALB. + +### ALB + +In AWS console configure ALB Authentication as described below: + +- Edit the ALB rule used to forward the traffic to Backstage and add a new `Authenticate` action. The action will have higher priority compared to the existing `Forward to`. +- Select `OIDC` under `Authenticate` +- Set `Issuer` to `https://login.microsoftonline.com/{TENANT_ID}/v2.0`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App. +- Set `Authorization endpoint` to `https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App. +- Set `Token endpoint` to `https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize`, replacing `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App. +- Set `User info endpoint` to `https://graph.microsoft.com/oidc/userinfo` +- Set `Client ID` to the AAD App `Application (client) Id` +- Set `Client secret` to the AAD APP `client secret` + +Use the following advanced settings: + +- `Session cookie name` = `AWSELBAuthSessionCookie` +- `Session timeout` = `604800` seconds +- `Scope` = `openid profile offline_access` +- `Action on unauthenticated request` = `Autenticate (client reattempt)` + +Once you've saved the action, you should see an authentication flow be triggered against AAD when visiting Backstage address at `https://backstage.yourdomain.com`. The flow will not complete successfully as Backstage app isn't yet configured properly. + +## Backstage changes + +### Frontend + +The Backstage App needs a SingInPage when authentication is required. +When using ALB authentication Backstage will only be loaded once the user has successfully authenticated; we won't need to display a SignIn page, however we will need to create a dummy SignIn component that can refresh the token. + +- edit `packages/app/src/App.tsx` +- import the following two additional definitions from `@backstage/core`: `useApi`, `configApiRef`; these will be used to check wether backstage is running locally or behind an ALB +- add the following definition just before the app is created (`const app = createApp`): + +```ts +const DummySignInComponent: any = (props: any) => { + const config = useApi(configApiRef); + const shouldAuth = !!config.getOptionalConfig('auth.providers.awsalb'); + if (shouldAuth) { + fetch(`${window.location.origin}/api/auth/awsalb/refresh`) + .then(data => data.json()) + .then(data => { + props.onResult({ + userId: data.backstageIdentity.id, + profile: data.profile, + }); + }); + } else { + // when running locally we default user identity to `Local User` + props.onResult({ + userId: 'local user', + profile: { + email: 'local.user@yourdomain.com', + displayName: 'Local User', + picture: '', + }, + }); + } + + return
; +}; +``` + +- use `DummySingInComponent` as `SignInPage` by changing `createApp` statement as follow: + +```ts +const app = createApp({ + apis, + plugins: Object.values(plugins), + components: { + SignInPage: DummySignInComponent, + }, + bindRoutes({ bind }) { + bind(catalogPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + }); + bind(apiDocsPlugin.externalRoutes, { + createComponent: scaffolderPlugin.routes.root, + }); + }, +}); +``` + +### Backend + +When using ALB auth it is not possible to leverage the built-in auth config discovery mechanism implemented in the app created by default: a bespoke logic needs to be implemented. + +- replace the content of `packages/backend/plugin/auth.ts` with the below + +```ts +import { + createRouter, + AuthResponse, + AuthProviderFactoryOptions, + defaultAuthProviderFactories, +} from '@backstage/plugin-auth-backend'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin({ + logger, + database, + config, + discovery, +}: PluginEnvironment) { + const identityResolver = (payload: any): Promise> => { + return Promise.resolve({ + providerInfo: {}, + profile: { + email: payload.email, + displayName: payload.name, + picture: payload.picture, + }, + backstageIdentity: { + id: payload.email, + }, + }); + }; + const providerFactories = { + awsalb: (options: AuthProviderFactoryOptions) => + defaultAuthProviderFactories.awsalb({ ...options, identityResolver }), + }; + return await createRouter({ + logger, + config, + database, + discovery, + providerFactories, + }); +} +``` + +### Configuration + +Use the following `auth` configuration when running Backstage on AWS: + +```yaml +auth: + providers: + awsalb: + issuer: + issuer: https://login.microsoftonline.com/{TENANT_ID}/v2.0 + region: { AWS_REGION } +``` + +Replace `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App and `{AWS_REGION}` with the AWS region identifier where the ALB is deployed (for example: `eu-central-1`). + +## Conclusion + +Once it's deployed, after going through the AAD authentication flow, Backstage should display the AAD user details. + +Special thanks to [@backjo][gh-backjo] for implementing the `auth-backend` provider for AWS ALB on [#4047][pr-4047] + + + +[monorepo-app-setup-with-auth-ms]: https://backstage.io/docs/tutorials/quickstart-app-auth#the-auth-configuration +[gh-backjo]: https://github.com/backjo +[pr-4047]: https://github.com/backstage/backstage/pull/4047 From 3074cfc0403d27fceb6904cc2e3610ff3c2e2e73 Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Mon, 5 Apr 2021 09:05:44 +0100 Subject: [PATCH 04/58] fix syntax, remove conclusion thanking (everybody should be thanked) Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 9dab72fe66..5041e60149 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -1,6 +1,6 @@ # Using AWS Application Load Balancer with Azure Active Directory to authenticate requests -Backstage allows to offload the responsibility of authenticating users to AWS Application Loadbalancer (**ALB**), leveraging the authentication support on ALB. +Backstage allows to offload the responsibility of authenticating users to AWS Application Load Balancer (**ALB**), leveraging the authentication support on ALB. This tutorial shows how to use authentication on an ALB sitting in front of Backstage. Azure Active Directory (**AAD**) is used as identity provider but any identity provider supporting OpenID Connect (OIDC) can be used. @@ -170,10 +170,6 @@ Replace `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App a Once it's deployed, after going through the AAD authentication flow, Backstage should display the AAD user details. -Special thanks to [@backjo][gh-backjo] for implementing the `auth-backend` provider for AWS ALB on [#4047][pr-4047] - [monorepo-app-setup-with-auth-ms]: https://backstage.io/docs/tutorials/quickstart-app-auth#the-auth-configuration -[gh-backjo]: https://github.com/backjo -[pr-4047]: https://github.com/backstage/backstage/pull/4047 From ffc6b81ffa50cff0034b155d1c584237c8b5797e Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 23:44:57 +0200 Subject: [PATCH 05/58] Update contrib/docs/tutorials/aws-alb-aad-oidc-auth.md Co-authored-by: Tim Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 5041e60149..4a29ce6758 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -47,7 +47,7 @@ Once you've saved the action, you should see an authentication flow be triggered ### Frontend -The Backstage App needs a SingInPage when authentication is required. +The Backstage App needs a SignInPage when authentication is required. When using ALB authentication Backstage will only be loaded once the user has successfully authenticated; we won't need to display a SignIn page, however we will need to create a dummy SignIn component that can refresh the token. - edit `packages/app/src/App.tsx` From ee9ecc99b4eb7096de97e1a693f626984852b2f6 Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 23:45:09 +0200 Subject: [PATCH 06/58] Update contrib/docs/tutorials/aws-alb-aad-oidc-auth.md Co-authored-by: Tim Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 4a29ce6758..7af2a12f5f 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -1,6 +1,6 @@ # Using AWS Application Load Balancer with Azure Active Directory to authenticate requests -Backstage allows to offload the responsibility of authenticating users to AWS Application Load Balancer (**ALB**), leveraging the authentication support on ALB. +Backstage allows offloading the responsibility of authenticating users to an AWS Application Load Balancer (**ALB**), leveraging the authentication support on ALB. This tutorial shows how to use authentication on an ALB sitting in front of Backstage. Azure Active Directory (**AAD**) is used as identity provider but any identity provider supporting OpenID Connect (OIDC) can be used. From 529c968413a16528b97571a4a580e2726c7cb4ed Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 23:45:23 +0200 Subject: [PATCH 07/58] Update contrib/docs/tutorials/aws-alb-aad-oidc-auth.md Co-authored-by: Tim Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 7af2a12f5f..6c1436cf09 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -23,7 +23,7 @@ Instead of `localhost` addresses, use the following values. ### ALB -In AWS console configure ALB Authentication as described below: +In the AWS console, configure ALB Authentication: - Edit the ALB rule used to forward the traffic to Backstage and add a new `Authenticate` action. The action will have higher priority compared to the existing `Forward to`. - Select `OIDC` under `Authenticate` From c3a659599917f9b7b6812e850ff865fa424d7e0e Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 23:46:06 +0200 Subject: [PATCH 08/58] Update contrib/docs/tutorials/aws-alb-aad-oidc-auth.md Co-authored-by: Tim Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 6c1436cf09..158368f72a 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -41,7 +41,7 @@ Use the following advanced settings: - `Scope` = `openid profile offline_access` - `Action on unauthenticated request` = `Autenticate (client reattempt)` -Once you've saved the action, you should see an authentication flow be triggered against AAD when visiting Backstage address at `https://backstage.yourdomain.com`. The flow will not complete successfully as Backstage app isn't yet configured properly. +Once you've saved the action, you should see an authentication flow be triggered against AAD when visiting Backstage address at `https://backstage.yourdomain.com`. The flow will not complete successfully as the Backstage app isn't yet configured properly. ## Backstage changes From 68717560ba2ce103c76340e6235e8d6f99d35298 Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 23:46:19 +0200 Subject: [PATCH 09/58] Update contrib/docs/tutorials/aws-alb-aad-oidc-auth.md Co-authored-by: Tim Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 158368f72a..ebaf8cb3ba 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -51,7 +51,7 @@ The Backstage App needs a SignInPage when authentication is required. When using ALB authentication Backstage will only be loaded once the user has successfully authenticated; we won't need to display a SignIn page, however we will need to create a dummy SignIn component that can refresh the token. - edit `packages/app/src/App.tsx` -- import the following two additional definitions from `@backstage/core`: `useApi`, `configApiRef`; these will be used to check wether backstage is running locally or behind an ALB +- import the following two additional definitions from `@backstage/core`: `useApi`, `configApiRef`; these will be used to check whether Backstage is running locally or behind an ALB - add the following definition just before the app is created (`const app = createApp`): ```ts From c59c13190561385326971962b41a878ee171430b Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 23:46:28 +0200 Subject: [PATCH 10/58] Update contrib/docs/tutorials/aws-alb-aad-oidc-auth.md Co-authored-by: Tim Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index ebaf8cb3ba..a36eb18833 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -83,7 +83,7 @@ const DummySignInComponent: any = (props: any) => { }; ``` -- use `DummySingInComponent` as `SignInPage` by changing `createApp` statement as follow: +- use `DummySignInComponent` as `SignInPage` by changing the `createApp` call: ```ts const app = createApp({ From 3eb5825045472f5e98a8825620a192e37800e035 Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 23:46:44 +0200 Subject: [PATCH 11/58] Update contrib/docs/tutorials/aws-alb-aad-oidc-auth.md Co-authored-by: Tim Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index a36eb18833..4eb52aa53c 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -105,7 +105,7 @@ const app = createApp({ ### Backend -When using ALB auth it is not possible to leverage the built-in auth config discovery mechanism implemented in the app created by default: a bespoke logic needs to be implemented. +When using ALB auth it is not possible to leverage the built-in auth config discovery mechanism implemented in the app created by default; bespoke logic needs to be implemented. - replace the content of `packages/backend/plugin/auth.ts` with the below From 0a0e1481500b66dd1f69a1266a37fdeb900c3f2d Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 22:51:36 +0100 Subject: [PATCH 12/58] using guest user convention Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 4eb52aa53c..4d90079046 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -70,10 +70,10 @@ const DummySignInComponent: any = (props: any) => { } else { // when running locally we default user identity to `Local User` props.onResult({ - userId: 'local user', + userId: 'guest', profile: { - email: 'local.user@yourdomain.com', - displayName: 'Local User', + email: 'guest@example.com', + displayName: 'Guest', picture: '', }, }); From b76b5ef9873cad5c2dfefdbcd3beeafc7a3f57be Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 22:55:44 +0100 Subject: [PATCH 13/58] remove details of createApp call Signed-off-by: Marco Crivellaro --- contrib/docs/tutorials/aws-alb-aad-oidc-auth.md | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 4d90079046..2503c31756 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -68,7 +68,6 @@ const DummySignInComponent: any = (props: any) => { }); }); } else { - // when running locally we default user identity to `Local User` props.onResult({ userId: 'guest', profile: { @@ -83,23 +82,16 @@ const DummySignInComponent: any = (props: any) => { }; ``` -- use `DummySignInComponent` as `SignInPage` by changing the `createApp` call: +- add `DummySignInComponent` as `SignInPage`: ```ts const app = createApp({ - apis, - plugins: Object.values(plugins), + ... components: { SignInPage: DummySignInComponent, + ... }, - bindRoutes({ bind }) { - bind(catalogPlugin.externalRoutes, { - createComponent: scaffolderPlugin.routes.root, - }); - bind(apiDocsPlugin.externalRoutes, { - createComponent: scaffolderPlugin.routes.root, - }); - }, + ... }); ``` From 703f47042a6bd24f7dc44796e4b30da4f06246ae Mon Sep 17 00:00:00 2001 From: Marco Crivellaro Date: Thu, 8 Apr 2021 23:15:05 +0100 Subject: [PATCH 14/58] handling error by outputing the message to the div Signed-off-by: Marco Crivellaro --- .../docs/tutorials/aws-alb-aad-oidc-auth.md | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 2503c31756..ba0aef89ea 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -56,29 +56,32 @@ When using ALB authentication Backstage will only be loaded once the user has su ```ts const DummySignInComponent: any = (props: any) => { - const config = useApi(configApiRef); - const shouldAuth = !!config.getOptionalConfig('auth.providers.awsalb'); - if (shouldAuth) { - fetch(`${window.location.origin}/api/auth/awsalb/refresh`) - .then(data => data.json()) - .then(data => { - props.onResult({ - userId: data.backstageIdentity.id, - profile: data.profile, + try { + const config = useApi(configApiRef); + const shouldAuth = !!config.getOptionalConfig('auth.providers.awsalb'); + if (shouldAuth) { + fetch(`${window.location.origin}/api/auth/awsalb/refresh`) + .then(data => data.json()) + .then(data => { + props.onResult({ + userId: data.backstageIdentity.id, + profile: data.profile, + }); }); + } else { + props.onResult({ + userId: 'guest', + profile: { + email: 'guest@example.com', + displayName: 'Guest', + picture: '', + }, }); - } else { - props.onResult({ - userId: 'guest', - profile: { - email: 'guest@example.com', - displayName: 'Guest', - picture: '', - }, - }); + } + return
; + } catch (err) { + return
{err.message}
; } - - return
; }; ``` From 3ecc89665f3e8e512036c52382d08fb289fa174b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Apr 2021 04:19:46 +0000 Subject: [PATCH 15/58] chore(deps): bump @asyncapi/react-component from 0.19.2 to 0.22.3 Bumps [@asyncapi/react-component](https://github.com/asyncapi/asyncapi-react) from 0.19.2 to 0.22.3. - [Release notes](https://github.com/asyncapi/asyncapi-react/releases) - [Commits](https://github.com/asyncapi/asyncapi-react/compare/v0.19.2...v0.22.3) Signed-off-by: dependabot[bot] --- plugins/api-docs/package.json | 2 +- yarn.lock | 31 ++++++++++++++++--------------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index d7f5f3754b..5f56c32f23 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@asyncapi/react-component": "^0.19.2", + "@asyncapi/react-component": "^0.22.3", "@backstage/catalog-model": "^0.7.5", "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.4", diff --git a/yarn.lock b/yarn.lock index 0b74a64a59..6a44b598d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -81,28 +81,29 @@ dependencies: "@openapi-contrib/openapi-schema-to-json-schema" "^3.0.0" -"@asyncapi/parser@^1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@asyncapi/parser/-/parser-1.4.0.tgz#a0b0e3cb1803400d93b15f7c354fe95824a36e2c" - integrity sha512-rI0UF9niaqUxZqFS2+iBvkekxhI3cXOv/O+i+w/1EhpPppLicPVnazQbBqQ1iO37sio+CPejJbeoBgj8m+ylPw== +"@asyncapi/parser@^1.4.4": + version "1.4.4" + resolved "https://registry.npmjs.org/@asyncapi/parser/-/parser-1.4.4.tgz#66f2642e3f9ae4166cdea2480b665250b1edbd59" + integrity sha512-HEYEDM0BzfCxXNAv/pIS5yZWe11xB8fQo9G/SmsbpJavOpcF0sVZVIELva/NxHVz/ZUKPMKCa4Gqz7DF/lMqpw== dependencies: "@apidevtools/json-schema-ref-parser" "^9.0.6" - "@asyncapi/specs" "^2.7.6" + "@asyncapi/specs" "^2.7.7" "@fmvilas/pseudo-yaml-ast" "^0.3.1" ajv "^6.10.1" js-yaml "^3.13.1" json-to-ast "^2.1.0" + lodash.clonedeep "^4.5.0" node-fetch "^2.6.0" tiny-merge-patch "^0.1.2" -"@asyncapi/react-component@^0.19.2": - version "0.19.2" - resolved "https://registry.npmjs.org/@asyncapi/react-component/-/react-component-0.19.2.tgz#364a1d839e6f49a454fcb44b544f9496d1501b93" - integrity sha512-NXLBVaJdXqsIX98tF8fKxjTDf6cE+ym3Fkp3IQhMQh/6o1X2y/YVp+XkjfLO2C/Ccb/Cys1C5OnqipfwEY1oqA== +"@asyncapi/react-component@^0.22.3": + version "0.22.3" + resolved "https://registry.npmjs.org/@asyncapi/react-component/-/react-component-0.22.3.tgz#6ea7fb1044308e6d46f8455218920389becf2b22" + integrity sha512-f47sboqEQ0jNp0z2A+WGzBYYMS3ASmTwAXG/q6SwLuHBW15bSjaIYOVK3E8bmftBl+wVcEgAqUc6RKGtD9iVJg== dependencies: "@asyncapi/avro-schema-parser" "^0.2.0" "@asyncapi/openapi-schema-parser" "^2.0.0" - "@asyncapi/parser" "^1.4.0" + "@asyncapi/parser" "^1.4.4" constate "^1.2.0" dompurify "^2.1.1" markdown-it "^11.0.1" @@ -110,10 +111,10 @@ openapi-sampler "^1.0.0-beta.15" react-use "^12.2.0" -"@asyncapi/specs@^2.7.6": - version "2.7.6" - resolved "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.7.6.tgz#d04ad015a148cd222f7939b9561360ab903de632" - integrity sha512-2IYlLA02beYQKVJVCAqV+G5tXRI4s8yeKYiszuoxS178psdAMKxgXtng8hUYPOqSvU6X4lc+Jabk1zSQFHifVg== +"@asyncapi/specs@^2.7.7": + version "2.7.7" + resolved "https://registry.npmjs.org/@asyncapi/specs/-/specs-2.7.7.tgz#10f72c95153a3cc10039f6ba9c3a6f7c3b7fecfc" + integrity sha512-z8kj4GDJ640DU4msRsWprvmuC9n7vIeJW+D7Tp1xdefoLX5ZJrK7+4Xruna513wV0fSLpFzCmGz7McEP6CtKDg== "@azure/abort-controller@^1.0.0": version "1.0.2" @@ -4236,7 +4237,7 @@ dependencies: "@octokit/openapi-types" "^5.3.2" -"@octokit/types@^6.13.0": +"@octokit/types@^6.13.0", "@octokit/types@^6.8.2": version "6.13.0" resolved "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0" integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA== From 017192ee84850454325e205e138d554b533ef8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathias=20A=CC=8Ahsberg?= Date: Thu, 8 Apr 2021 18:47:02 +0000 Subject: [PATCH 16/58] Support multiline LDAP filter queries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mathias Åhsberg --- .changeset/green-otters-grin.md | 5 +++ .../ingestion/processors/ldap/config.test.ts | 31 +++++++++++++++++++ .../src/ingestion/processors/ldap/config.ts | 7 ++++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 .changeset/green-otters-grin.md diff --git a/.changeset/green-otters-grin.md b/.changeset/green-otters-grin.md new file mode 100644 index 0000000000..abfd332a2b --- /dev/null +++ b/.changeset/green-otters-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Add support for configure an LDAP query filter on multiple lines. diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/config.test.ts b/plugins/catalog-backend/src/ingestion/processors/ldap/config.test.ts index 5e293766aa..aac9a091b8 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/config.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/ldap/config.test.ts @@ -172,4 +172,35 @@ describe('readLdapConfig', () => { ]; expect(actual).toEqual(expected); }); + + it('supports multiline ldap query filter', () => { + const config = { + providers: [ + { + target: 'target', + users: { + dn: 'udn', + options: { + filter: ` + (| + (cn=foo bar) + (cn=bar) + ) + `, + }, + }, + groups: { + dn: 'gdn', + options: { + filter: 'f', + }, + }, + }, + ], + }; + const actual = readLdapConfig(new ConfigReader(config)); + + const expected = '(|(cn=foo bar)(cn=bar))'; + expect(actual[0].users.options.filter).toEqual(expected); + }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/ldap/config.ts b/plugins/catalog-backend/src/ingestion/processors/ldap/config.ts index 7608f247d4..625282ca05 100644 --- a/plugins/catalog-backend/src/ingestion/processors/ldap/config.ts +++ b/plugins/catalog-backend/src/ingestion/processors/ldap/config.ts @@ -182,7 +182,7 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { } return { scope: c.getOptionalString('scope') as SearchOptions['scope'], - filter: c.getOptionalString('filter'), + filter: formatFilter(c.getOptionalString('filter')), attributes: c.getOptionalStringArray('attributes'), paged: c.getOptionalBoolean('paged'), }; @@ -260,6 +260,11 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { }; } + function formatFilter(filter?: string): string | undefined { + // Remove extra whitespaces between blocks to support multiline filters from the configuration + return filter?.replace(/\s*(\(|\))/g, '$1')?.trim(); + } + const providerConfigs = config.getOptionalConfigArray('providers') ?? []; return providerConfigs.map(c => { const newConfig = { From 934275ddbd7f07457f79c4a1321e60cd4681ad11 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 5 Apr 2021 15:52:10 +0200 Subject: [PATCH 17/58] plugins: added config-schema plugin Signed-off-by: Patrik Oldsberg --- plugins/config-schema/.eslintrc.js | 3 + plugins/config-schema/README.md | 13 +++ plugins/config-schema/dev/index.tsx | 26 +++++ plugins/config-schema/package.json | 47 ++++++++ .../ExampleComponent.test.tsx | 44 ++++++++ .../ExampleComponent/ExampleComponent.tsx | 53 +++++++++ .../src/components/ExampleComponent/index.ts | 16 +++ .../ExampleFetchComponent.test.tsx | 40 +++++++ .../ExampleFetchComponent.tsx | 105 ++++++++++++++++++ .../components/ExampleFetchComponent/index.ts | 16 +++ plugins/config-schema/src/index.ts | 16 +++ plugins/config-schema/src/plugin.test.ts | 22 ++++ plugins/config-schema/src/plugin.ts | 33 ++++++ plugins/config-schema/src/routes.ts | 20 ++++ plugins/config-schema/src/setupTests.ts | 17 +++ 15 files changed, 471 insertions(+) create mode 100644 plugins/config-schema/.eslintrc.js create mode 100644 plugins/config-schema/README.md create mode 100644 plugins/config-schema/dev/index.tsx create mode 100644 plugins/config-schema/package.json create mode 100644 plugins/config-schema/src/components/ExampleComponent/ExampleComponent.test.tsx create mode 100644 plugins/config-schema/src/components/ExampleComponent/ExampleComponent.tsx create mode 100644 plugins/config-schema/src/components/ExampleComponent/index.ts create mode 100644 plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx create mode 100644 plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx create mode 100644 plugins/config-schema/src/components/ExampleFetchComponent/index.ts create mode 100644 plugins/config-schema/src/index.ts create mode 100644 plugins/config-schema/src/plugin.test.ts create mode 100644 plugins/config-schema/src/plugin.ts create mode 100644 plugins/config-schema/src/routes.ts create mode 100644 plugins/config-schema/src/setupTests.ts diff --git a/plugins/config-schema/.eslintrc.js b/plugins/config-schema/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/config-schema/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/config-schema/README.md b/plugins/config-schema/README.md new file mode 100644 index 0000000000..4d5d2c588d --- /dev/null +++ b/plugins/config-schema/README.md @@ -0,0 +1,13 @@ +# config-schema + +Welcome to the config-schema plugin! + +_This plugin was created through the Backstage CLI_ + +## Getting started + +Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/config-schema](http://localhost:3000/config-schema). + +You can also serve the plugin in isolation by running `yarn start` in the plugin directory. +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. +It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. diff --git a/plugins/config-schema/dev/index.tsx b/plugins/config-schema/dev/index.tsx new file mode 100644 index 0000000000..1a20bfc62a --- /dev/null +++ b/plugins/config-schema/dev/index.tsx @@ -0,0 +1,26 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React from 'react'; +import { createDevApp } from '@backstage/dev-utils'; +import { configSchemaPlugin, ConfigSchemaPage } from '../src/plugin'; + +createDevApp() + .registerPlugin(configSchemaPlugin) + .addPage({ + element: , + title: 'Root Page', + }) + .render(); diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json new file mode 100644 index 0000000000..291a7078be --- /dev/null +++ b/plugins/config-schema/package.json @@ -0,0 +1,47 @@ +{ + "name": "@backstage/plugin-config-schema", + "version": "0.1.1", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/core": "^0.7.3", + "@backstage/theme": "^0.2.5", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-use": "^15.3.3" + }, + "devDependencies": { + "@backstage/cli": "^0.6.6", + "@backstage/dev-utils": "^0.1.13", + "@backstage/test-utils": "^0.1.9", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^11.2.5", + "@testing-library/user-event": "^12.0.7", + "@types/jest": "^26.0.7", + "@types/node": "^14.14.32", + "msw": "^0.21.2", + "cross-fetch": "^3.0.6" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.test.tsx b/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.test.tsx new file mode 100644 index 0000000000..030495be33 --- /dev/null +++ b/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.test.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React from 'react'; +import { ExampleComponent } from './ExampleComponent'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { msw, renderInTestApp } from '@backstage/test-utils'; + +describe('ExampleComponent', () => { + const server = setupServer(); + // Enable sane handlers for network requests + msw.setupDefaultHandlers(server); + + // setup mock response + beforeEach(() => { + server.use( + rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))), + ); + }); + + it('should render', async () => { + const rendered = await renderInTestApp( + + + , + ); + expect(rendered.getByText('Welcome to config-schema!')).toBeInTheDocument(); + }); +}); diff --git a/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.tsx b/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.tsx new file mode 100644 index 0000000000..f3d5c86be3 --- /dev/null +++ b/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.tsx @@ -0,0 +1,53 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React from 'react'; +import { Typography, Grid } from '@material-ui/core'; +import { + InfoCard, + Header, + Page, + Content, + ContentHeader, + HeaderLabel, + SupportButton, +} from '@backstage/core'; +import { ExampleFetchComponent } from '../ExampleFetchComponent'; + +export const ExampleComponent = () => ( + +
+ + +
+ + + A description of your plugin goes here. + + + + + + All content should be wrapped in a card like this. + + + + + + + + +
+); diff --git a/plugins/config-schema/src/components/ExampleComponent/index.ts b/plugins/config-schema/src/components/ExampleComponent/index.ts new file mode 100644 index 0000000000..337d24d5c5 --- /dev/null +++ b/plugins/config-schema/src/components/ExampleComponent/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { ExampleComponent } from './ExampleComponent'; diff --git a/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx b/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx new file mode 100644 index 0000000000..6a5c0351d6 --- /dev/null +++ b/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React from 'react'; +import { render } from '@testing-library/react'; +import { ExampleFetchComponent } from './ExampleFetchComponent'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { msw } from '@backstage/test-utils'; + +describe('ExampleFetchComponent', () => { + const server = setupServer(); + // Enable sane handlers for network requests + msw.setupDefaultHandlers(server); + + // setup mock response + beforeEach(() => { + server.use( + rest.get('https://randomuser.me/*', (_, res, ctx) => + res(ctx.status(200), ctx.delay(2000), ctx.json({})), + ), + ); + }); + it('should render', async () => { + const rendered = render(); + expect(await rendered.findByTestId('progress')).toBeInTheDocument(); + }); +}); diff --git a/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx b/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx new file mode 100644 index 0000000000..1390c8950f --- /dev/null +++ b/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx @@ -0,0 +1,105 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import { Table, TableColumn, Progress } from '@backstage/core'; +import Alert from '@material-ui/lab/Alert'; +import { useAsync } from 'react-use'; + +const useStyles = makeStyles({ + avatar: { + height: 32, + width: 32, + borderRadius: '50%', + }, +}); + +type User = { + gender: string; // "male" + name: { + title: string; // "Mr", + first: string; // "Duane", + last: string; // "Reed" + }; + location: object; // {street: {number: 5060, name: "Hickory Creek Dr"}, city: "Albany", state: "New South Wales",…} + email: string; // "duane.reed@example.com" + login: object; // {uuid: "4b785022-9a23-4ab9-8a23-cb3fb43969a9", username: "blackdog796", password: "patch",…} + dob: object; // {date: "1983-06-22T12:30:23.016Z", age: 37} + registered: object; // {date: "2006-06-13T18:48:28.037Z", age: 14} + phone: string; // "07-2154-5651" + cell: string; // "0405-592-879" + id: { + name: string; // "TFN", + value: string; // "796260432" + }; + picture: { medium: string }; // {medium: "https://randomuser.me/api/portraits/men/95.jpg",…} + nat: string; // "AU" +}; + +type DenseTableProps = { + users: User[]; +}; + +export const DenseTable = ({ users }: DenseTableProps) => { + const classes = useStyles(); + + const columns: TableColumn[] = [ + { title: 'Avatar', field: 'avatar' }, + { title: 'Name', field: 'name' }, + { title: 'Email', field: 'email' }, + { title: 'Nationality', field: 'nationality' }, + ]; + + const data = users.map(user => { + return { + avatar: ( + {user.name.first} + ), + name: `${user.name.first} ${user.name.last}`, + email: user.email, + nationality: user.nat, + }; + }); + + return ( + + ); +}; + +export const ExampleFetchComponent = () => { + const { value, loading, error } = useAsync(async (): Promise => { + const response = await fetch('https://randomuser.me/api/?results=20'); + const data = await response.json(); + return data.results; + }, []); + + if (loading) { + return ; + } else if (error) { + return {error.message}; + } + + return ; +}; diff --git a/plugins/config-schema/src/components/ExampleFetchComponent/index.ts b/plugins/config-schema/src/components/ExampleFetchComponent/index.ts new file mode 100644 index 0000000000..e7c8364039 --- /dev/null +++ b/plugins/config-schema/src/components/ExampleFetchComponent/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { ExampleFetchComponent } from './ExampleFetchComponent'; diff --git a/plugins/config-schema/src/index.ts b/plugins/config-schema/src/index.ts new file mode 100644 index 0000000000..0254d6a36c --- /dev/null +++ b/plugins/config-schema/src/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { configSchemaPlugin, ConfigSchemaPage } from './plugin'; diff --git a/plugins/config-schema/src/plugin.test.ts b/plugins/config-schema/src/plugin.test.ts new file mode 100644 index 0000000000..0f71d800d4 --- /dev/null +++ b/plugins/config-schema/src/plugin.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { configSchemaPlugin } from './plugin'; + +describe('config-schema', () => { + it('should export plugin', () => { + expect(configSchemaPlugin).toBeDefined(); + }); +}); diff --git a/plugins/config-schema/src/plugin.ts b/plugins/config-schema/src/plugin.ts new file mode 100644 index 0000000000..8850e02639 --- /dev/null +++ b/plugins/config-schema/src/plugin.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { createPlugin, createRoutableExtension } from '@backstage/core'; + +import { rootRouteRef } from './routes'; + +export const configSchemaPlugin = createPlugin({ + id: 'config-schema', + routes: { + root: rootRouteRef, + }, +}); + +export const ConfigSchemaPage = configSchemaPlugin.provide( + createRoutableExtension({ + component: () => + import('./components/ExampleComponent').then(m => m.ExampleComponent), + mountPoint: rootRouteRef, + }), +); diff --git a/plugins/config-schema/src/routes.ts b/plugins/config-schema/src/routes.ts new file mode 100644 index 0000000000..8128e1f49f --- /dev/null +++ b/plugins/config-schema/src/routes.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { createRouteRef } from '@backstage/core'; + +export const rootRouteRef = createRouteRef({ + title: 'config-schema', +}); diff --git a/plugins/config-schema/src/setupTests.ts b/plugins/config-schema/src/setupTests.ts new file mode 100644 index 0000000000..0cec5b395d --- /dev/null +++ b/plugins/config-schema/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; From e56c49aa2eb99f5006a9df324776fc61a328ac0f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 5 Apr 2021 15:58:48 +0200 Subject: [PATCH 18/58] config-schema: added initial API types Signed-off-by: Patrik Oldsberg --- plugins/config-schema/package.json | 1 + plugins/config-schema/src/api/index.ts | 18 ++++++++++++++++ plugins/config-schema/src/api/types.ts | 30 ++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) create mode 100644 plugins/config-schema/src/api/index.ts create mode 100644 plugins/config-schema/src/api/types.ts diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 291a7078be..85c2ada157 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -20,6 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/config": "^0.1.4", "@backstage/core": "^0.7.3", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", diff --git a/plugins/config-schema/src/api/index.ts b/plugins/config-schema/src/api/index.ts new file mode 100644 index 0000000000..ed5e40c268 --- /dev/null +++ b/plugins/config-schema/src/api/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { configSchemaApiRef } from './types'; +export type { ConfigSchemaApi } from './types'; diff --git a/plugins/config-schema/src/api/types.ts b/plugins/config-schema/src/api/types.ts new file mode 100644 index 0000000000..310817e68f --- /dev/null +++ b/plugins/config-schema/src/api/types.ts @@ -0,0 +1,30 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { JsonObject } from '@backstage/config'; +import { createApiRef, Observable } from '@backstage/core'; + +export interface ConfigSchemaResult { + schema?: JsonObject; +} + +export interface ConfigSchemaApi { + schema$(): Observable; +} + +export const configSchemaApiRef = createApiRef({ + id: 'plugin.config-schema', +}); From 02f5bc8d01323314e6a07380410d13982b179e63 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 5 Apr 2021 16:04:03 +0200 Subject: [PATCH 19/58] config-schema: basic schema render page Signed-off-by: Patrik Oldsberg --- .../ConfigSchemaPage/ConfigSchemaPage.tsx | 62 +++++++++++ .../index.ts | 2 +- .../ExampleComponent.test.tsx | 44 -------- .../ExampleComponent/ExampleComponent.tsx | 53 --------- .../ExampleFetchComponent.test.tsx | 40 ------- .../ExampleFetchComponent.tsx | 105 ------------------ .../components/ExampleFetchComponent/index.ts | 16 --- plugins/config-schema/src/plugin.ts | 2 +- 8 files changed, 64 insertions(+), 260 deletions(-) create mode 100644 plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx rename plugins/config-schema/src/components/{ExampleComponent => ConfigSchemaPage}/index.ts (91%) delete mode 100644 plugins/config-schema/src/components/ExampleComponent/ExampleComponent.test.tsx delete mode 100644 plugins/config-schema/src/components/ExampleComponent/ExampleComponent.tsx delete mode 100644 plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx delete mode 100644 plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx delete mode 100644 plugins/config-schema/src/components/ExampleFetchComponent/index.ts diff --git a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx new file mode 100644 index 0000000000..67cff7a596 --- /dev/null +++ b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React, { useMemo } from 'react'; +import { Grid } from '@material-ui/core'; +import { + Header, + Page, + Content, + ContentHeader, + HeaderLabel, + SupportButton, + CodeSnippet, + useApi, +} from '@backstage/core'; +import { useObservable } from 'react-use'; +import { configSchemaApiRef } from '../../api'; + +export const ConfigSchemaPage = () => { + const configSchemaApi = useApi(configSchemaApiRef); + const schema = useObservable( + useMemo(() => configSchemaApi.schema$(), [configSchemaApi]), + ); + + return ( + +
+ + +
+ + + A description of your plugin goes here. + + + + {schema ? ( + + ) : ( + 'No schema available' + )} + + + +
+ ); +}; diff --git a/plugins/config-schema/src/components/ExampleComponent/index.ts b/plugins/config-schema/src/components/ConfigSchemaPage/index.ts similarity index 91% rename from plugins/config-schema/src/components/ExampleComponent/index.ts rename to plugins/config-schema/src/components/ConfigSchemaPage/index.ts index 337d24d5c5..e373ae71c6 100644 --- a/plugins/config-schema/src/components/ExampleComponent/index.ts +++ b/plugins/config-schema/src/components/ConfigSchemaPage/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { ExampleComponent } from './ExampleComponent'; +export { ConfigSchemaPage } from './ConfigSchemaPage'; diff --git a/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.test.tsx b/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.test.tsx deleted file mode 100644 index 030495be33..0000000000 --- a/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.test.tsx +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2021 Spotify AB - * - * 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 React from 'react'; -import { ExampleComponent } from './ExampleComponent'; -import { ThemeProvider } from '@material-ui/core'; -import { lightTheme } from '@backstage/theme'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; -import { msw, renderInTestApp } from '@backstage/test-utils'; - -describe('ExampleComponent', () => { - const server = setupServer(); - // Enable sane handlers for network requests - msw.setupDefaultHandlers(server); - - // setup mock response - beforeEach(() => { - server.use( - rest.get('/*', (_, res, ctx) => res(ctx.status(200), ctx.json({}))), - ); - }); - - it('should render', async () => { - const rendered = await renderInTestApp( - - - , - ); - expect(rendered.getByText('Welcome to config-schema!')).toBeInTheDocument(); - }); -}); diff --git a/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.tsx b/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.tsx deleted file mode 100644 index f3d5c86be3..0000000000 --- a/plugins/config-schema/src/components/ExampleComponent/ExampleComponent.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2021 Spotify AB - * - * 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 React from 'react'; -import { Typography, Grid } from '@material-ui/core'; -import { - InfoCard, - Header, - Page, - Content, - ContentHeader, - HeaderLabel, - SupportButton, -} from '@backstage/core'; -import { ExampleFetchComponent } from '../ExampleFetchComponent'; - -export const ExampleComponent = () => ( - -
- - -
- - - A description of your plugin goes here. - - - - - - All content should be wrapped in a card like this. - - - - - - - - -
-); diff --git a/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx b/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx deleted file mode 100644 index 6a5c0351d6..0000000000 --- a/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2021 Spotify AB - * - * 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 React from 'react'; -import { render } from '@testing-library/react'; -import { ExampleFetchComponent } from './ExampleFetchComponent'; -import { rest } from 'msw'; -import { setupServer } from 'msw/node'; -import { msw } from '@backstage/test-utils'; - -describe('ExampleFetchComponent', () => { - const server = setupServer(); - // Enable sane handlers for network requests - msw.setupDefaultHandlers(server); - - // setup mock response - beforeEach(() => { - server.use( - rest.get('https://randomuser.me/*', (_, res, ctx) => - res(ctx.status(200), ctx.delay(2000), ctx.json({})), - ), - ); - }); - it('should render', async () => { - const rendered = render(); - expect(await rendered.findByTestId('progress')).toBeInTheDocument(); - }); -}); diff --git a/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx b/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx deleted file mode 100644 index 1390c8950f..0000000000 --- a/plugins/config-schema/src/components/ExampleFetchComponent/ExampleFetchComponent.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2021 Spotify AB - * - * 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 React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { Table, TableColumn, Progress } from '@backstage/core'; -import Alert from '@material-ui/lab/Alert'; -import { useAsync } from 'react-use'; - -const useStyles = makeStyles({ - avatar: { - height: 32, - width: 32, - borderRadius: '50%', - }, -}); - -type User = { - gender: string; // "male" - name: { - title: string; // "Mr", - first: string; // "Duane", - last: string; // "Reed" - }; - location: object; // {street: {number: 5060, name: "Hickory Creek Dr"}, city: "Albany", state: "New South Wales",…} - email: string; // "duane.reed@example.com" - login: object; // {uuid: "4b785022-9a23-4ab9-8a23-cb3fb43969a9", username: "blackdog796", password: "patch",…} - dob: object; // {date: "1983-06-22T12:30:23.016Z", age: 37} - registered: object; // {date: "2006-06-13T18:48:28.037Z", age: 14} - phone: string; // "07-2154-5651" - cell: string; // "0405-592-879" - id: { - name: string; // "TFN", - value: string; // "796260432" - }; - picture: { medium: string }; // {medium: "https://randomuser.me/api/portraits/men/95.jpg",…} - nat: string; // "AU" -}; - -type DenseTableProps = { - users: User[]; -}; - -export const DenseTable = ({ users }: DenseTableProps) => { - const classes = useStyles(); - - const columns: TableColumn[] = [ - { title: 'Avatar', field: 'avatar' }, - { title: 'Name', field: 'name' }, - { title: 'Email', field: 'email' }, - { title: 'Nationality', field: 'nationality' }, - ]; - - const data = users.map(user => { - return { - avatar: ( - {user.name.first} - ), - name: `${user.name.first} ${user.name.last}`, - email: user.email, - nationality: user.nat, - }; - }); - - return ( -
- ); -}; - -export const ExampleFetchComponent = () => { - const { value, loading, error } = useAsync(async (): Promise => { - const response = await fetch('https://randomuser.me/api/?results=20'); - const data = await response.json(); - return data.results; - }, []); - - if (loading) { - return ; - } else if (error) { - return {error.message}; - } - - return ; -}; diff --git a/plugins/config-schema/src/components/ExampleFetchComponent/index.ts b/plugins/config-schema/src/components/ExampleFetchComponent/index.ts deleted file mode 100644 index e7c8364039..0000000000 --- a/plugins/config-schema/src/components/ExampleFetchComponent/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2021 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { ExampleFetchComponent } from './ExampleFetchComponent'; diff --git a/plugins/config-schema/src/plugin.ts b/plugins/config-schema/src/plugin.ts index 8850e02639..841f9f7f04 100644 --- a/plugins/config-schema/src/plugin.ts +++ b/plugins/config-schema/src/plugin.ts @@ -27,7 +27,7 @@ export const configSchemaPlugin = createPlugin({ export const ConfigSchemaPage = configSchemaPlugin.provide( createRoutableExtension({ component: () => - import('./components/ExampleComponent').then(m => m.ExampleComponent), + import('./components/ConfigSchemaPage').then(m => m.ConfigSchemaPage), mountPoint: rootRouteRef, }), ); From e75795ac0fe2d644fb306e4d01b31b9c438ed4dc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 5 Apr 2021 16:09:48 +0200 Subject: [PATCH 20/58] config-schema: dev setup with example schema Signed-off-by: Patrik Oldsberg --- plugins/config-schema/dev/example-schema.json | 1659 +++++++++++++++++ plugins/config-schema/dev/index.tsx | 19 +- plugins/config-schema/package.json | 1 + 3 files changed, 1677 insertions(+), 2 deletions(-) create mode 100644 plugins/config-schema/dev/example-schema.json diff --git a/plugins/config-schema/dev/example-schema.json b/plugins/config-schema/dev/example-schema.json new file mode 100644 index 0000000000..d1e577ff6f --- /dev/null +++ b/plugins/config-schema/dev/example-schema.json @@ -0,0 +1,1659 @@ +{ + "$schema": "https://backstage.io/schema/config-v1", + "title": "Application Configuration Schema", + "type": "object", + "required": ["app", "backend", "costInsights", "sentry", "techdocs"], + "properties": { + "app": { + "type": "object", + "required": ["baseUrl"], + "description": "Generic frontend configuration.", + "properties": { + "baseUrl": { + "type": "string", + "visibility": "frontend", + "description": "The public absolute root URL that the frontend." + }, + "title": { + "type": "string", + "visibility": "frontend", + "description": "The title of the app." + }, + "googleAnalyticsTrackingId": { + "type": "string", + "visibility": "frontend", + "description": "Tracking ID for Google Analytics", + "examples": ["UA-000000-0"] + }, + "listen": { + "type": "object", + "description": "Listening configuration for local development", + "properties": { + "host": { + "type": "string", + "visibility": "frontend", + "description": "The host that the frontend should be bound to. Only used for local development." + }, + "port": { + "type": "number", + "visibility": "frontend", + "description": "The port that the frontend should be bound to. Only used for local development." + } + } + }, + "support": { + "description": "Information about support of this Backstage instance and how to contact the integrator team.", + "type": "object", + "required": ["items", "url"], + "properties": { + "url": { + "description": "The primary support url.", + "visibility": "frontend", + "type": "string" + }, + "items": { + "description": "A list of categorized support item groupings.", + "type": "array", + "items": { + "type": "object", + "required": ["links", "title"], + "properties": { + "title": { + "description": "The title of the support item grouping.", + "visibility": "frontend", + "type": "string" + }, + "icon": { + "description": "An optional icon for the support item grouping.", + "visibility": "frontend", + "type": "string" + }, + "links": { + "description": "A list of support links for the Backstage instance.", + "type": "array", + "items": { + "type": "object", + "required": ["url"], + "properties": { + "url": { + "visibility": "frontend", + "type": "string" + }, + "title": { + "visibility": "frontend", + "type": "string" + } + } + } + } + } + } + } + } + } + } + }, + "lighthouse": { + "type": "object", + "properties": { + "baseUrl": { + "type": "string", + "visibility": "frontend" + } + } + }, + "auth": { + "type": "object", + "description": "Configuration that provides information on available authentication providers configured for app", + "properties": { + "providers": { + "type": "object", + "description": "The available auth-provider options and attributes", + "additionalProperties": { + "type": "object", + "visibility": "frontend" + }, + "properties": { + "google": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "github": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "gitlab": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "saml": { + "type": "object", + "required": ["entryPoint", "issuer"], + "properties": { + "entryPoint": { + "type": "string" + }, + "logoutUrl": { + "type": "string" + }, + "issuer": { + "type": "string" + }, + "cert": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "decryptionPvk": { + "type": "string" + }, + "signatureAlgorithm": { + "enum": ["sha256", "sha512"], + "type": "string" + }, + "digestAlgorithm": { + "type": "string" + } + } + }, + "okta": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "oauth2": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": [ + "authorizationUrl", + "clientId", + "clientSecret", + "tokenUrl" + ], + "properties": { + "clientId": { + "type": "string" + }, + "clientSecret": { + "type": "string" + }, + "authorizationUrl": { + "type": "string" + }, + "tokenUrl": { + "type": "string" + }, + "scope": { + "type": "string" + } + } + } + }, + "oidc": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "auth0": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "microsoft": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "onelogin": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "awsalb": { + "type": "object", + "required": ["region"], + "properties": { + "issuer": { + "type": "string" + }, + "region": { + "type": "string" + } + } + } + } + }, + "environment": { + "description": "The 'environment' attribute added as an optional parameter to have configurable environment value for `auth.providers`.\ndefault value: 'development'\noptional values: 'development' | 'production'", + "visibility": "frontend", + "type": "string" + }, + "session": { + "type": "object", + "properties": { + "secret": { + "description": "The secret attribute of session object.", + "visibility": "secret", + "type": "string" + } + } + } + } + }, + "backend": { + "type": "object", + "required": ["baseUrl", "database", "listen"], + "description": "Generic backend configuration.", + "properties": { + "baseUrl": { + "type": "string", + "description": "The public absolute root URL that the backend is reachable at.", + "visibility": "frontend" + }, + "listen": { + "description": "Address that the backend should listen to.", + "anyOf": [ + { + "type": "object", + "properties": { + "address": { + "description": "Address of the interface that the backend should bind to.", + "type": "string" + }, + "port": { + "description": "Port that the backend should listen to.", + "type": ["string", "number"] + } + } + }, + { + "type": "string" + } + ] + }, + "https": { + "description": "HTTPS configuration for the backend. If omitted the backend will serve HTTP.\n\nSetting this to `true` will cause self-signed certificates to be generated, which\ncan be useful for local development or other non-production scenarios.", + "anyOf": [ + { + "type": "object", + "properties": { + "certificate": { + "description": "Certificate configuration", + "type": "object", + "required": ["cert", "key"], + "properties": { + "cert": { + "description": "PEM encoded certificate. Use $file to load in a file", + "type": "string" + }, + "key": { + "description": "PEM encoded certificate key. Use $file to load in a file.", + "visibility": "secret", + "type": "string" + } + } + } + } + }, + { + "enum": [true], + "type": "boolean" + } + ] + }, + "database": { + "description": "Database connection configuration, select database type using the `client` field", + "anyOf": [ + { + "type": "object", + "properties": { + "client": { + "type": "string", + "enum": ["sqlite3"] + }, + "connection": { + "type": "string" + } + }, + "required": ["client", "connection"] + }, + { + "type": "object", + "properties": { + "client": { + "type": "string", + "enum": ["pg"] + }, + "connection": { + "description": "PostgreSQL connection string or knex configuration object.", + "anyOf": [ + { + "type": "object", + "properties": {}, + "additionalProperties": true + }, + { + "type": "string" + } + ] + } + }, + "required": ["client", "connection"] + } + ] + }, + "cors": { + "type": "object", + "properties": { + "origin": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "methods": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "allowedHeaders": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "exposedHeaders": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "credentials": { + "type": "boolean" + }, + "maxAge": { + "type": "number" + }, + "preflightContinue": { + "type": "boolean" + }, + "optionsSuccessStatus": { + "type": "number" + } + } + }, + "reading": { + "description": "Configuration related to URL reading, used for example for reading catalog info\nfiles, scaffolder templates, and techdocs content.", + "type": "object", + "properties": { + "allow": { + "description": "A list of targets to allow outgoing requests to. Users will be able to make\nrequests on behalf of the backend to the targets that are allowed by this list.", + "type": "array", + "items": { + "type": "object", + "required": ["host"], + "properties": { + "host": { + "description": "A host to allow outgoing requests to, being either a full host or\na subdomain wildcard pattern with a leading `*`. For example `example.com`\nand `*.example.com` are valid values, `prod.*.example.com` is not.\nThe host may also contain a port, for example `example.com:8080`.", + "type": "string" + } + } + } + } + } + }, + "csp": { + "description": "Content Security Policy options.\n\nThe keys are the plain policy ID, e.g. \"upgrade-insecure-requests\". The\nvalues are on the format that the helmet library expects them, as an\narray of strings. There is also the special value false, which means to\nremove the default value that Backstage puts in place for that policy.", + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "enum": [false], + "type": "boolean" + } + ] + } + } + } + }, + "organization": { + "description": "Configuration that provides information about the organization that the app is for.", + "type": "object", + "properties": { + "name": { + "description": "The name of the organization that the app belongs to.", + "visibility": "frontend", + "type": "string" + } + } + }, + "homepage": { + "type": "object", + "properties": { + "clocks": { + "type": "array", + "items": { + "type": "object", + "required": ["label", "timezone"], + "properties": { + "label": { + "visibility": "frontend", + "type": "string" + }, + "timezone": { + "visibility": "frontend", + "type": "string" + } + } + } + } + } + }, + "integrations": { + "description": "Configuration for integrations towards various external repository provider systems", + "type": "object", + "properties": { + "azure": { + "description": "Integration configuration for Azure", + "type": "array", + "items": { + "type": "object", + "required": ["host"], + "properties": { + "host": { + "description": "The hostname of the given Azure instance", + "visibility": "frontend", + "type": "string" + }, + "token": { + "description": "Token used to authenticate requests.", + "visibility": "secret", + "type": "string" + } + } + } + }, + "bitbucket": { + "description": "Integration configuration for Bitbucket", + "type": "array", + "items": { + "type": "object", + "required": ["host"], + "properties": { + "host": { + "description": "The hostname of the given Bitbucket instance", + "visibility": "frontend", + "type": "string" + }, + "token": { + "description": "Token used to authenticate requests.", + "visibility": "secret", + "type": "string" + }, + "apiBaseUrl": { + "description": "The base url for the Bitbucket API, for example https://api.bitbucket.org/2.0", + "visibility": "frontend", + "type": "string" + }, + "username": { + "description": "The username to use for authenticated requests.", + "visibility": "secret", + "type": "string" + }, + "appPassword": { + "description": "Bitbucket app password used to authenticate requests.", + "visibility": "secret", + "type": "string" + } + } + } + }, + "github": { + "description": "Integration configuration for GitHub", + "type": "array", + "items": { + "type": "object", + "required": ["host"], + "properties": { + "host": { + "description": "The hostname of the given GitHub instance", + "visibility": "frontend", + "type": "string" + }, + "token": { + "description": "Token used to authenticate requests.", + "visibility": "secret", + "type": "string" + }, + "apiBaseUrl": { + "description": "The base url for the GitHub API, for example https://api.github.com", + "visibility": "frontend", + "type": "string" + }, + "rawBaseUrl": { + "description": "The base url for GitHub raw resources, for example https://raw.githubusercontent.com", + "visibility": "frontend", + "type": "string" + }, + "apps": { + "description": "GitHub Apps configuration", + "visibility": "backend", + "type": "array", + "items": { + "type": "object", + "required": [ + "appId", + "clientId", + "clientSecret", + "privateKey", + "webhookSecret" + ], + "properties": { + "appId": { + "description": "The numeric GitHub App ID", + "type": "number" + }, + "privateKey": { + "description": "The private key to use for auth against the app", + "visibility": "secret", + "type": "string" + }, + "webhookSecret": { + "description": "The secret used for webhooks", + "visibility": "secret", + "type": "string" + }, + "clientId": { + "description": "The client ID to use", + "type": "string" + }, + "clientSecret": { + "description": "The client secret to use", + "visibility": "secret", + "type": "string" + } + } + } + } + } + } + }, + "gitlab": { + "description": "Integration configuration for GitLab", + "type": "array", + "items": { + "type": "object", + "required": ["host"], + "properties": { + "host": { + "description": "The host of the target that this matches on, e.g. \"gitlab.com\".", + "visibility": "frontend", + "type": "string" + }, + "apiBaseUrl": { + "description": "The base URL of the API of this provider, e.g.\n\"https://gitlab.com/api/v4\", with no trailing slash.\n\nMay be omitted specifically for public GitLab; then it will be deduced.", + "visibility": "frontend", + "type": "string" + }, + "token": { + "description": "The authorization token to use for requests to this provider.\n\nIf no token is specified, anonymous access is used.", + "visibility": "secret", + "type": "string" + }, + "baseUrl": { + "description": "The baseUrl of this provider, e.g. \"https://gitlab.com\", which is\npassed into the GitLab client.\n\nIf no baseUrl is provided, it will default to https://${host}.", + "visibility": "frontend", + "type": "string" + } + } + } + } + } + }, + "catalog": { + "description": "Configuration options for the catalog plugin.", + "type": "object", + "properties": { + "rules": { + "description": "Rules to apply to all catalog entities, from any location.\n\nAn undefined list of matchers means match all, an empty list of\nmatchers means match none.\n\nThis is commonly used to put in what amounts to a whitelist of kinds\nthat regular users of Backstage are permitted to register locations\nfor. This can be used to stop them from registering yaml files\ndescribing for example a Group entity called \"admin\" that they make\nthemselves members of, or similar.", + "type": "array", + "items": { + "type": "object", + "required": ["allow"], + "properties": { + "allow": { + "description": "Allow entities of these particular kinds.\n\nE.g. [\"Component\", \"API\", \"Template\", \"Location\"]", + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "readonly": { + "description": "Readonly defines whether the catalog allows writes after startup.\n\nSetting 'readonly=false' allows users to register their own components.\nThis is the default value.\n\nSetting 'readonly=true' configures catalog to only allow reads. This can\nbe used in combination with static locations to only serve operator\nprovided locations. Effectively this removes the ability to register new\ncomponents to a running backstage instance.", + "type": "boolean" + }, + "locations": { + "description": "A set of static locations that the catalog shall always keep itself\nup-to-date with. This is commonly used for large, permanent integrations\nthat are defined by the Backstage operators at an organization, rather\nthan individual things that users register dynamically.\n\nThese have (optional) rules of their own. These override what the global\nrules above specify. This way, you can prevent everybody from register\ne.g. User and Group entities, except for one or a few static locations\nthat have those two kinds explicitly allowed.\n\nFor example:\n\n```yaml\nrules:\n - allow: [Component, API, Template, Location]\nlocations:\n - type: url\n target: https://github.com/org/repo/blob/master/users.yaml\n rules:\n - allow: [User, Group]\n - type: url\n target: https://github.com/org/repo/blob/master/systems.yaml\n rules:\n - allow: [System]\n```", + "type": "array", + "items": { + "type": "object", + "required": ["target", "type"], + "properties": { + "type": { + "description": "The type of location, e.g. \"url\".", + "type": "string" + }, + "target": { + "description": "The target URL of the location, e.g.\n\"https://github.com/org/repo/blob/master/users.yaml\".", + "type": "string" + }, + "rules": { + "description": "Optional extra rules that apply to this particular location.\n\nThese override the global rules above.", + "type": "array", + "items": { + "type": "object", + "required": ["allow"], + "properties": { + "allow": { + "description": "Allow entities of these particular kinds.\n\nE.g. [\"Group\", \"User\"]", + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + } + } + }, + "processors": { + "description": "List of processor-specific options and attributes", + "type": "object", + "properties": { + "githubOrg": { + "description": "GithubOrgReaderProcessor configuration", + "type": "object", + "required": ["providers"], + "properties": { + "providers": { + "description": "The configuration parameters for each single GitHub org provider.", + "type": "array", + "items": { + "type": "object", + "required": ["target"], + "properties": { + "target": { + "description": "The prefix of the target that this matches on, e.g.\n\"https://github.com\", with no trailing slash.", + "type": "string" + }, + "apiBaseUrl": { + "description": "The base URL of the API of this provider, e.g.\n\"https://api.github.com\", with no trailing slash.\n\nMay be omitted specifically for GitHub; then it will be deduced.", + "type": "string" + }, + "token": { + "description": "The authorization token to use for requests to this provider.\n\nIf no token is specified, anonymous access is used.", + "visibility": "secret", + "type": "string" + } + } + } + } + } + }, + "ldapOrg": { + "description": "LdapOrgReaderProcessor configuration", + "type": "object", + "required": ["providers"], + "properties": { + "providers": { + "description": "The configuration parameters for each single LDAP provider.", + "type": "array", + "items": { + "type": "object", + "required": ["groups", "target", "users"], + "properties": { + "target": { + "description": "The prefix of the target that this matches on, e.g.\n\"ldaps://ds.example.net\", with no trailing slash.", + "type": "string" + }, + "bind": { + "description": "The settings to use for the bind command. If none are specified,\nthe bind command is not issued.", + "type": "object", + "required": ["dn", "secret"], + "properties": { + "dn": { + "description": "The DN of the user to auth as.\n\nE.g. \"uid=ldap-robot,ou=robots,ou=example,dc=example,dc=net\"", + "type": "string" + }, + "secret": { + "description": "The secret of the user to auth as (its password).", + "visibility": "secret", + "type": "string" + } + } + }, + "users": { + "description": "The settings that govern the reading and interpretation of users.", + "type": "object", + "required": ["dn", "options"], + "properties": { + "dn": { + "description": "The DN under which users are stored.\n\nE.g. \"ou=people,ou=example,dc=example,dc=net\"", + "type": "string" + }, + "options": { + "description": "The search options to use. The default is scope \"one\" and\nattributes \"*\" and \"+\".\n\nIt is common to want to specify a filter, to narrow down the set\nof matching items.", + "type": "object", + "properties": { + "scope": { + "enum": ["base", "one", "sub"], + "type": "string" + }, + "filter": { + "type": "string" + }, + "attributes": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "paged": { + "anyOf": [ + { + "type": "object", + "properties": { + "pageSize": { + "type": "number" + }, + "pagePause": { + "type": "boolean" + } + } + }, + { + "type": "boolean" + } + ] + } + } + }, + "set": { + "description": "JSON paths (on a.b.c form) and hard coded values to set on those\npaths.\n\nThis can be useful for example if you want to hard code a\nnamespace or similar on the generated entities.", + "type": "object" + }, + "map": { + "description": "Mappings from well known entity fields, to LDAP attribute names", + "type": "object", + "properties": { + "rdn": { + "description": "The name of the attribute that holds the relative\ndistinguished name of each entry. Defaults to \"uid\".", + "type": "string" + }, + "name": { + "description": "The name of the attribute that shall be used for the value of\nthe metadata.name field of the entity. Defaults to \"uid\".", + "type": "string" + }, + "description": { + "description": "The name of the attribute that shall be used for the value of\nthe metadata.description field of the entity.", + "type": "string" + }, + "displayName": { + "description": "The name of the attribute that shall be used for the value of\nthe spec.profile.displayName field of the entity. Defaults to\n\"cn\".", + "type": "string" + }, + "email": { + "description": "The name of the attribute that shall be used for the value of\nthe spec.profile.email field of the entity. Defaults to\n\"mail\".", + "type": "string" + }, + "picture": { + "description": "The name of the attribute that shall be used for the value of\nthe spec.profile.picture field of the entity.", + "type": "string" + }, + "memberOf": { + "description": "The name of the attribute that shall be used for the values of\nthe spec.memberOf field of the entity. Defaults to \"memberOf\".", + "type": "string" + } + } + } + } + }, + "groups": { + "description": "The settings that govern the reading and interpretation of groups.", + "type": "object", + "required": ["dn", "options"], + "properties": { + "dn": { + "description": "The DN under which groups are stored.\n\nE.g. \"ou=people,ou=example,dc=example,dc=net\"", + "type": "string" + }, + "options": { + "description": "The search options to use. The default is scope \"one\" and\nattributes \"*\" and \"+\".\n\nIt is common to want to specify a filter, to narrow down the set\nof matching items.", + "type": "object", + "properties": { + "scope": { + "enum": ["base", "one", "sub"], + "type": "string" + }, + "filter": { + "type": "string" + }, + "attributes": { + "anyOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, + "paged": { + "anyOf": [ + { + "type": "object", + "properties": { + "pageSize": { + "type": "number" + }, + "pagePause": { + "type": "boolean" + } + } + }, + { + "type": "boolean" + } + ] + } + } + }, + "set": { + "description": "JSON paths (on a.b.c form) and hard coded values to set on those\npaths.\n\nThis can be useful for example if you want to hard code a\nnamespace or similar on the generated entities.", + "type": "object" + }, + "map": { + "description": "Mappings from well known entity fields, to LDAP attribute names", + "type": "object", + "properties": { + "rdn": { + "description": "The name of the attribute that holds the relative\ndistinguished name of each entry. Defaults to \"cn\".", + "type": "string" + }, + "name": { + "description": "The name of the attribute that shall be used for the value of\nthe metadata.name field of the entity. Defaults to \"cn\".", + "type": "string" + }, + "description": { + "description": "The name of the attribute that shall be used for the value of\nthe metadata.description field of the entity. Defaults to\n\"description\".", + "type": "string" + }, + "type": { + "description": "The name of the attribute that shall be used for the value of\nthe spec.type field of the entity. Defaults to \"groupType\".", + "type": "string" + }, + "displayName": { + "description": "The name of the attribute that shall be used for the value of\nthe spec.profile.displayName field of the entity. Defaults to\n\"cn\".", + "type": "string" + }, + "email": { + "description": "The name of the attribute that shall be used for the value of\nthe spec.profile.email field of the entity.", + "type": "string" + }, + "picture": { + "description": "The name of the attribute that shall be used for the value of\nthe spec.profile.picture field of the entity.", + "type": "string" + }, + "memberOf": { + "description": "The name of the attribute that shall be used for the values of\nthe spec.parent field of the entity. Defaults to \"memberOf\".", + "type": "string" + }, + "members": { + "description": "The name of the attribute that shall be used for the values of\nthe spec.children field of the entity. Defaults to \"member\".", + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "awsOrganization": { + "description": "AwsOrganizationCloudAccountProcessor configuration", + "type": "object", + "required": ["provider"], + "properties": { + "provider": { + "type": "object", + "properties": { + "roleArn": { + "description": "The role to be assumed by this processor", + "type": "string" + } + } + } + } + }, + "microsoftGraphOrg": { + "description": "MicrosoftGraphOrgReaderProcessor configuration", + "type": "object", + "required": ["providers"], + "properties": { + "providers": { + "description": "The configuration parameters for each single Microsoft Graph provider.", + "type": "array", + "items": { + "type": "object", + "required": [ + "clientId", + "clientSecret", + "target", + "tenantId" + ], + "properties": { + "target": { + "description": "The prefix of the target that this matches on, e.g.\n\"https://graph.microsoft.com/v1.0\", with no trailing slash.", + "type": "string" + }, + "authority": { + "description": "The auth authority used.\n\nDefault value \"https://login.microsoftonline.com\"", + "type": "string" + }, + "tenantId": { + "description": "The tenant whose org data we are interested in.", + "type": "string" + }, + "clientId": { + "description": "The OAuth client ID to use for authenticating requests.", + "type": "string" + }, + "clientSecret": { + "description": "The OAuth client secret to use for authenticating requests.", + "visibility": "secret", + "type": "string" + }, + "userFilter": { + "description": "The filter to apply to extract users.\n\nE.g. \"accountEnabled eq true and userType eq 'member'\"", + "type": "string" + }, + "groupFilter": { + "description": "The filter to apply to extract groups.\n\nE.g. \"securityEnabled eq false and mailEnabled eq true\"", + "type": "string" + } + } + } + } + } + } + } + } + } + }, + "costInsights": { + "type": "object", + "required": ["engineerCost", "products"], + "properties": { + "engineerCost": { + "visibility": "frontend", + "type": "number" + }, + "products": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "visibility": "frontend", + "type": "string" + }, + "icon": { + "visibility": "frontend", + "enum": [ + "compute", + "data", + "database", + "ml", + "search", + "storage" + ], + "type": "string" + } + } + } + }, + "metrics": { + "type": "object", + "additionalProperties": { + "type": "object", + "required": ["name"], + "properties": { + "name": { + "visibility": "frontend", + "type": "string" + }, + "default": { + "visibility": "frontend", + "type": "boolean" + } + } + } + } + } + }, + "fossa": { + "type": "object", + "required": ["organizationId"], + "properties": { + "organizationId": { + "description": "The organization id in fossa.", + "visibility": "frontend", + "type": "string" + } + } + }, + "kubernetes": { + "type": "object", + "required": ["clusterLocatorMethods", "serviceLocatorMethod"], + "properties": { + "serviceLocatorMethod": { + "type": "object", + "required": ["type"], + "visibility": "frontend", + "properties": { + "type": { + "type": "string", + "enum": ["multiTenant"], + "visibility": "frontend" + } + } + }, + "clusterLocatorMethods": { + "type": "array", + "visibility": "frontend" + }, + "customResources": { + "type": "array", + "visibility": "frontend" + } + } + }, + "kafka": { + "type": "object", + "required": ["clientId", "clusters"], + "properties": { + "clientId": { + "description": "Client ID used to Backstage uses to identify when connecting to the Kafka cluster.", + "type": "string" + }, + "clusters": { + "type": "array", + "items": { + "type": "object", + "required": ["brokers", "name"], + "properties": { + "name": { + "type": "string" + }, + "brokers": { + "description": "List of brokers in the Kafka cluster to connect to.", + "type": "array", + "items": { + "type": "string" + } + }, + "ssl": { + "description": "Optional SSL connection parameters to connect to the cluster. Passed directly to Node tls.connect.\nSee https://nodejs.org/dist/latest-v8.x/docs/api/tls.html#tls_tls_createsecurecontext_options", + "anyOf": [ + { + "type": "object", + "properties": { + "ca": { + "type": "array", + "items": { + "type": "string" + } + }, + "key": { + "visibility": "secret", + "type": "string" + }, + "cert": { + "type": "string" + } + }, + "required": ["ca", "cert", "key"] + }, + { + "type": "boolean" + } + ] + }, + "sasl": { + "description": "Optional SASL connection parameters.", + "type": "object", + "required": ["mechanism", "password", "username"], + "properties": { + "mechanism": { + "enum": ["plain", "scram-sha-256", "scram-sha-512"], + "type": "string" + }, + "username": { + "type": "string" + }, + "password": { + "visibility": "secret", + "type": "string" + } + } + } + } + } + } + } + }, + "rollbar": { + "description": "Configuration options for the rollbar-backend plugin", + "type": "object", + "required": ["accountToken"], + "properties": { + "accountToken": { + "description": "The autentication token for accessing the Rollbar API", + "type": "string" + }, + "organization": { + "description": "The Rollbar organization name. This can be omitted by using the `rollbar.com/project-slug` annotation.", + "visibility": "frontend", + "type": "string" + } + } + }, + "proxy": { + "description": "A list of forwarding-proxies. Each key is a route to match,\nbelow the prefix that the proxy plugin is mounted on. It must\nstart with a '/'.", + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "properties": { + "target": { + "description": "Target of the proxy. Url string to be parsed with the url module.", + "type": "string" + }, + "headers": { + "description": "Object with extra headers to be added to target requests.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "changeOrigin": { + "description": "Changes the origin of the host header to the target URL. Default: true.", + "type": "boolean" + }, + "pathRewrite": { + "description": "Rewrite target's url path. Object-keys will be used as RegExp to match paths.\nIf pathRewrite is not specified, it is set to a single rewrite that removes the entire prefix and route.", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "allowedMethods": { + "description": "Limit the forwarded HTTP methods, for example allowedMethods: ['GET'] to enforce read-only access.", + "type": "array", + "items": { + "type": "string" + } + }, + "allowedHeaders": { + "description": "Limit the forwarded HTTP methods. By default, only the headers that are considered safe for CORS\nand headers that are set by the proxy will be forwarded.", + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": ["target"] + }, + { + "type": "string" + } + ] + } + }, + "scaffolder": { + "description": "Configuration options for the scaffolder plugin", + "type": "object", + "properties": { + "github": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "properties": { + "visiblity": { + "description": "The visibility to set on created repositories.", + "enum": ["internal", "private", "public"], + "type": "string" + } + } + }, + "gitlab": { + "type": "object", + "required": ["api"], + "properties": { + "api": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "visiblity": { + "description": "The visibility to set on created repositories.", + "enum": ["internal", "private", "public"], + "type": "string" + } + } + }, + "azure": { + "type": "object", + "required": ["api", "baseUrl"], + "properties": { + "baseUrl": { + "type": "string" + }, + "api": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "bitbucket": { + "type": "object", + "required": ["api"], + "properties": { + "api": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "visiblity": { + "description": "The visibility to set on created repositories.", + "enum": ["private", "public"], + "type": "string" + } + } + } + } + }, + "sonarQube": { + "description": "Optional configurations for the SonarQube plugin", + "type": "object", + "required": ["baseUrl"], + "properties": { + "baseUrl": { + "description": "The base url of the sonarqube installation. Defaults to https://sonarcloud.io.", + "visibility": "frontend", + "type": "string" + } + } + }, + "sentry": { + "description": "Configuration options for the sentry plugin", + "type": "object", + "required": ["organization"], + "properties": { + "organization": { + "description": "The 'organization' attribute", + "visibility": "frontend", + "type": "string" + } + } + }, + "techdocs": { + "description": "Configuration options for the techdocs-backend plugin", + "type": "object", + "required": ["builder"], + "properties": { + "builder": { + "description": "Documentation building process depends on the builder attr", + "visibility": "frontend", + "enum": ["external", "local"], + "type": "string" + }, + "generators": { + "description": "Techdocs generator information", + "type": "object", + "required": ["techdocs"], + "properties": { + "techdocs": { + "enum": ["docker", "local"], + "type": "string" + } + } + }, + "publisher": { + "description": "Techdocs publisher information", + "anyOf": [ + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["local"] + } + }, + "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["awsS3"] + }, + "awsS3": { + "description": "Required when 'type' is set to awsS3", + "type": "object", + "required": ["bucketName"], + "properties": { + "credentials": { + "description": "(Optional) Credentials used to access a storage bucket.\nIf not set, environment variables or aws config file will be used to authenticate.", + "visibility": "secret", + "type": "object", + "required": ["accessKeyId", "secretAccessKey"], + "properties": { + "accessKeyId": { + "description": "User access key id", + "visibility": "secret", + "type": "string" + }, + "secretAccessKey": { + "description": "User secret access key", + "visibility": "secret", + "type": "string" + }, + "roleArn": { + "description": "ARN of role to be assumed", + "visibility": "backend", + "type": "string" + } + } + }, + "bucketName": { + "description": "(Required) Cloud Storage Bucket Name", + "visibility": "backend", + "type": "string" + }, + "region": { + "description": "(Optional) AWS Region.\nIf not set, AWS_REGION environment variable or aws config file will be used.", + "visibility": "secret", + "type": "string" + }, + "endpoint": { + "description": "(Optional) AWS Endpoint.\nThe endpoint URI to send requests to. The default endpoint is built from the configured region.", + "visibility": "secret", + "type": "string" + } + } + } + }, + "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["openStackSwift"] + }, + "openStackSwift": { + "description": "Required when 'type' is set to openStackSwift", + "type": "object", + "required": [ + "authUrl", + "containerName", + "credentials", + "domainId", + "domainName", + "keystoneAuthVersion", + "region" + ], + "properties": { + "credentials": { + "description": "(Required) Credentials used to access a storage bucket.", + "visibility": "secret", + "type": "object", + "required": ["password", "username"], + "properties": { + "username": { + "description": "(Required) Root user name", + "visibility": "secret", + "type": "string" + }, + "password": { + "description": "(Required) Root user password", + "visibility": "secret", + "type": "string" + } + } + }, + "containerName": { + "description": "(Required) Cloud Storage Container Name", + "visibility": "backend", + "type": "string" + }, + "authUrl": { + "description": "(Required) Auth url sometimes OpenStack uses different port check your OpenStack apis.", + "visibility": "backend", + "type": "string" + }, + "keystoneAuthVersion": { + "description": "(Optional) Auth version\nIf not set, 'v2.0' will be used.", + "visibility": "backend", + "type": "string" + }, + "domainId": { + "description": "(Required) Domain Id", + "visibility": "backend", + "type": "string" + }, + "domainName": { + "description": "(Required) Domain Name", + "visibility": "backend", + "type": "string" + }, + "region": { + "description": "(Required) Region", + "visibility": "backend", + "type": "string" + } + } + } + }, + "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["azureBlobStorage"] + }, + "azureBlobStorage": { + "description": "Required when 'type' is set to azureBlobStorage", + "type": "object", + "required": ["containerName", "credentials"], + "properties": { + "credentials": { + "description": "(Required) Credentials used to access a storage container.", + "visibility": "secret", + "type": "object", + "required": ["accountName"], + "properties": { + "accountName": { + "description": "Account access name", + "visibility": "secret", + "type": "string" + }, + "accountKey": { + "description": "(Optional) Account secret primary key\nIf not set, environment variables will be used to authenticate.", + "visibility": "secret", + "type": "string" + } + } + }, + "containerName": { + "description": "(Required) Cloud Storage Container Name", + "visibility": "backend", + "type": "string" + } + } + } + }, + "required": ["type"] + }, + { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["googleGcs"] + }, + "googleGcs": { + "description": "Required when 'type' is set to googleGcs", + "type": "object", + "required": ["bucketName"], + "properties": { + "bucketName": { + "description": "(Required) Cloud Storage Bucket Name", + "visibility": "backend", + "type": "string" + }, + "credentials": { + "description": "(Optional) API key used to write to a storage bucket.\nIf not set, environment variables will be used to authenticate.", + "visibility": "secret", + "type": "string" + } + } + } + }, + "required": ["type"] + } + ] + }, + "requestUrl": { + "visibility": "frontend", + "type": "string" + }, + "storageUrl": { + "type": "string" + } + } + }, + "travisci": { + "description": "Configuration options for the travisci plugin", + "type": "object", + "properties": { + "baseUrl": { + "description": "The 'baseUrl' attribute. It should point to the address of the travis portal.\nIf not provided, frontend plugin will use 'https://travis-ci.com/'", + "visibility": "frontend", + "type": "string" + } + } + } + }, + "description": "This is the schema describing the structure of the app-config.yaml configuration file." +} diff --git a/plugins/config-schema/dev/index.tsx b/plugins/config-schema/dev/index.tsx index 1a20bfc62a..03aa8e172a 100644 --- a/plugins/config-schema/dev/index.tsx +++ b/plugins/config-schema/dev/index.tsx @@ -13,12 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; + import { createDevApp } from '@backstage/dev-utils'; -import { configSchemaPlugin, ConfigSchemaPage } from '../src/plugin'; +import React from 'react'; +import Observable from 'zen-observable'; +import { configSchemaApiRef } from '../src/api'; +import { ConfigSchemaResult } from '../src/api/types'; +import { ConfigSchemaPage, configSchemaPlugin } from '../src/plugin'; +import exampleSchema from './example-schema.json'; createDevApp() .registerPlugin(configSchemaPlugin) + .registerApi({ + api: configSchemaApiRef, + deps: {}, + factory: () => ({ + schema$: () => + new Observable(sub => + sub.next({ schema: exampleSchema }), + ), + }), + }) .addPage({ element: , title: 'Root Page', diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 85c2ada157..3ca14a8162 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -26,6 +26,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", + "zen-observable": "^0.8.15", "react": "^16.13.1", "react-dom": "^16.13.1", "react-use": "^15.3.3" From 079b3e025bbe0f98d5aa109825ef7768c3a974d8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 5 Apr 2021 18:27:59 +0200 Subject: [PATCH 21/58] config-schema: switch schema type and separate out SchemaViewer Signed-off-by: Patrik Oldsberg --- plugins/config-schema/dev/index.tsx | 3 ++- plugins/config-schema/package.json | 1 + plugins/config-schema/src/api/types.ts | 4 +-- .../ConfigSchemaPage/ConfigSchemaPage.tsx | 13 +++------- .../components/SchemaViewer/SchemaViewer.tsx | 26 +++++++++++++++++++ .../src/components/SchemaViewer/index.ts | 17 ++++++++++++ 6 files changed, 51 insertions(+), 13 deletions(-) create mode 100644 plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx create mode 100644 plugins/config-schema/src/components/SchemaViewer/index.ts diff --git a/plugins/config-schema/dev/index.tsx b/plugins/config-schema/dev/index.tsx index 03aa8e172a..412f0d283e 100644 --- a/plugins/config-schema/dev/index.tsx +++ b/plugins/config-schema/dev/index.tsx @@ -15,6 +15,7 @@ */ import { createDevApp } from '@backstage/dev-utils'; +import { Schema } from 'jsonschema'; import React from 'react'; import Observable from 'zen-observable'; import { configSchemaApiRef } from '../src/api'; @@ -30,7 +31,7 @@ createDevApp() factory: () => ({ schema$: () => new Observable(sub => - sub.next({ schema: exampleSchema }), + sub.next({ schema: (exampleSchema as unknown) as Schema }), ), }), }) diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 3ca14a8162..67dcfa5976 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -26,6 +26,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", + "jsonschema": "^1.2.6", "zen-observable": "^0.8.15", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/plugins/config-schema/src/api/types.ts b/plugins/config-schema/src/api/types.ts index 310817e68f..025ef76f0c 100644 --- a/plugins/config-schema/src/api/types.ts +++ b/plugins/config-schema/src/api/types.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { JsonObject } from '@backstage/config'; import { createApiRef, Observable } from '@backstage/core'; +import { Schema } from 'jsonschema'; export interface ConfigSchemaResult { - schema?: JsonObject; + schema?: Schema; } export interface ConfigSchemaApi { diff --git a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx index 67cff7a596..8811a3e0bd 100644 --- a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx +++ b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx @@ -22,17 +22,17 @@ import { ContentHeader, HeaderLabel, SupportButton, - CodeSnippet, useApi, } from '@backstage/core'; import { useObservable } from 'react-use'; import { configSchemaApiRef } from '../../api'; +import { SchemaViewer } from '../SchemaViewer'; export const ConfigSchemaPage = () => { const configSchemaApi = useApi(configSchemaApiRef); const schema = useObservable( useMemo(() => configSchemaApi.schema$(), [configSchemaApi]), - ); + )?.schema; return ( @@ -46,14 +46,7 @@ export const ConfigSchemaPage = () => { - {schema ? ( - - ) : ( - 'No schema available' - )} + {schema ? : 'No schema available'} diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx new file mode 100644 index 0000000000..3f4c11fc31 --- /dev/null +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -0,0 +1,26 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React from 'react'; +import { CodeSnippet } from '@backstage/core'; +import { Schema } from 'jsonschema'; + +export interface SchemaViewerProps { + schema: Schema; +} + +export const SchemaViewer = ({ schema }: SchemaViewerProps) => { + return ; +}; diff --git a/plugins/config-schema/src/components/SchemaViewer/index.ts b/plugins/config-schema/src/components/SchemaViewer/index.ts new file mode 100644 index 0000000000..4852c4e21d --- /dev/null +++ b/plugins/config-schema/src/components/SchemaViewer/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { SchemaViewer } from './SchemaViewer'; From 5c63bc44abbd7f0aa7ed439393f29d7cd67b5196 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 5 Apr 2021 21:16:20 +0200 Subject: [PATCH 22/58] config-schema: initial propper schema viewer Signed-off-by: Patrik Oldsberg --- .../components/SchemaViewer/SchemaViewer.tsx | 261 +++++++++++++++++- 1 file changed, 259 insertions(+), 2 deletions(-) diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index 3f4c11fc31..db29e5acbe 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -14,13 +14,270 @@ * limitations under the License. */ import React from 'react'; -import { CodeSnippet } from '@backstage/core'; import { Schema } from 'jsonschema'; +import { + Box, + Chip, + Divider, + Paper, + Table, + TableBody, + TableCell, + TableRow, + Typography, +} from '@material-ui/core'; +import { JsonValue } from '@backstage/config'; + +interface SchemaViewProps { + path: string; + depth: number; + schema: Schema; +} + +export interface MetadataViewRowProps { + label: string; + text?: string; + data?: JsonValue; +} + +export function MetadataViewRow({ label, text, data }: MetadataViewRowProps) { + if (text === undefined && data === undefined) { + return null; + } + return ( + + + + {label} + + + + + {data ? JSON.stringify(data) : text} + + + + ); +} + +export function MetadataView({ schema }: { schema: Schema }) { + return ( + +
+ + + + + + + + + + + + + + + + + + +
+ + ); +} + +export function ScalarView({ schema }: SchemaViewProps) { + return ( + <> + {schema.description && ( + + {schema.description} + + )} + + + ); +} + +function isRequired(name: string, required?: boolean | string[]) { + if (required === true) { + return true; + } + if (Array.isArray(required)) { + return required.includes(name); + } + return false; +} + +function titleVariant(depth: number) { + if (depth <= 1) { + return 'h2'; + } else if (depth === 2) { + return 'h3'; + } else if (depth === 3) { + return 'h4'; + } else if (depth === 4) { + return 'h5'; + } + return 'h6'; +} + +export function VisibilityView({ schema }: { schema: Schema }) { + const { visibility } = schema as { visibility?: string }; + if (visibility === 'frontend') { + return ( + } + /> + ); + } else if (visibility === 'secret') { + return ( + } + /> + ); + } + return null; +} + +export function ArrayView({ path, depth, schema }: SchemaViewProps) { + const itemDepth = depth + 1; + const itemPath = path ? `${path}[]` : '[]'; + const itemSchema = schema.items; + + return ( + <> + + {schema.description && ( + + {schema.description} + + )} + + + Items + + + + + + {itemPath} + + + {itemSchema && ( + + )} + + + + ); +} + +export function ObjectView({ path, depth, schema }: SchemaViewProps) { + const properties = Object.entries(schema.properties ?? {}); + return ( + <> + {depth > 0 && ( + + {schema.description && ( + + {schema.description} + + )} + + + )} + {properties.length > 0 && ( + <> + {depth > 0 && Properties} + {properties.map(([name, propSchema], index) => { + const propDepth = depth + 1; + const propPath = path ? `${path}.${name}` : name; + + return ( + + + + + + {propPath} + + {isRequired(name, schema.required) && ( + + + required + + + )} + + + + + + ); + })} + + )} + + ); +} + +export function SchemaView(props: SchemaViewProps) { + // TODO(Rugvip): allOf, anyOf, oneOf + switch (props.schema.type) { + case 'array': + return ; + case 'object': + case undefined: + return ; + default: + return ; + } +} export interface SchemaViewerProps { schema: Schema; } export const SchemaViewer = ({ schema }: SchemaViewerProps) => { - return ; + return ; }; From 888f0b52bd50b25654cccf7921f6d07e4c9d2435 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 6 Apr 2021 01:22:13 +0200 Subject: [PATCH 23/58] config-schema: refactor out PropertyTitle and make required a chip Signed-off-by: Patrik Oldsberg --- .../components/SchemaViewer/SchemaViewer.tsx | 96 +++++++++++++++---- 1 file changed, 76 insertions(+), 20 deletions(-) diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index db29e5acbe..5db522b9a8 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -19,6 +19,7 @@ import { Box, Chip, Divider, + makeStyles, Paper, Table, TableBody, @@ -46,8 +47,8 @@ export function MetadataViewRow({ label, text, data }: MetadataViewRowProps) { } return ( - - + + {label} @@ -167,6 +168,68 @@ export function VisibilityView({ schema }: { schema: Schema }) { return null; } +const usePropertyTitleStyles = makeStyles(theme => ({ + title: { + marginBottom: 0, + }, + chip: { + marginLeft: theme.spacing(1), + marginRight: 0, + marginBottom: 0, + }, +})); + +export function PropertyTitle({ + path, + depth, + schema, + required, +}: { + path: string; + depth: number; + schema?: Schema; + required?: boolean; +}) { + const classes = usePropertyTitleStyles(); + const chips = new Array(); + const chipProps = { size: 'small' as const, classes: { root: classes.chip } }; + + if (required) { + chips.push( + , + ); + } + + const visibility = (schema as { visibility?: string })?.visibility; + if (visibility === 'frontend') { + chips.push( + , + ); + } else if (visibility === 'secret') { + chips.push( + , + ); + } + + return ( + + + {path} + + {chips.length > 0 && } + {chips} + + ); +} + export function ArrayView({ path, depth, schema }: SchemaViewProps) { const itemDepth = depth + 1; const itemPath = path ? `${path}[]` : '[]'; @@ -186,11 +249,11 @@ export function ArrayView({ path, depth, schema }: SchemaViewProps) { - - - {itemPath} - - + {itemSchema && ( - - - {propPath} - - {isRequired(name, schema.required) && ( - - - required - - - )} - - + Date: Tue, 6 Apr 2021 01:59:48 +0200 Subject: [PATCH 24/58] config-schema: refactor PropertyTitle into ChildView and add MatchView Signed-off-by: Patrik Oldsberg --- .../components/SchemaViewer/SchemaViewer.tsx | 160 +++++++++++------- 1 file changed, 97 insertions(+), 63 deletions(-) diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index 5db522b9a8..b49651ae7b 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -168,7 +168,7 @@ export function VisibilityView({ schema }: { schema: Schema }) { return null; } -const usePropertyTitleStyles = makeStyles(theme => ({ +const useChildViewStyles = makeStyles(theme => ({ title: { marginBottom: 0, }, @@ -179,18 +179,20 @@ const usePropertyTitleStyles = makeStyles(theme => ({ }, })); -export function PropertyTitle({ +export function ChildView({ path, depth, schema, required, + lastChild, }: { path: string; depth: number; schema?: Schema; required?: boolean; + lastChild?: boolean; }) { - const classes = usePropertyTitleStyles(); + const classes = useChildViewStyles(); const chips = new Array(); const chipProps = { size: 'small' as const, classes: { root: classes.chip } }; @@ -212,20 +214,28 @@ export function PropertyTitle({ } return ( - - - {path} - - {chips.length > 0 && } - {chips} + + + + + + {path} + + {chips.length > 0 && } + {chips} + + {schema && ( + + )} + ); } @@ -246,23 +256,12 @@ export function ArrayView({ path, depth, schema }: SchemaViewProps) { Items - - - - - {itemSchema && ( - - )} - - + ); } @@ -284,42 +283,77 @@ export function ObjectView({ path, depth, schema }: SchemaViewProps) { {properties.length > 0 && ( <> {depth > 0 && Properties} - {properties.map(([name, propSchema], index) => { - const propDepth = depth + 1; - const propPath = path ? `${path}.${name}` : name; - - return ( - - - - - - - - ); - })} + {properties.map(([name, propSchema], index) => ( + + ))} )} ); } +export function MatchView({ + path, + depth, + schema, + label, +}: { + path: string; + depth: number; + schema: Schema[]; + label: string; +}) { + return ( + <> + {label} + {schema.map((optionSchema, index) => ( + + ))} + + ); +} + export function SchemaView(props: SchemaViewProps) { - // TODO(Rugvip): allOf, anyOf, oneOf - switch (props.schema.type) { + const { schema } = props; + if (schema.anyOf) { + return ( + + ); + } + if (schema.oneOf) { + return ( + + ); + } + if (schema.allOf) { + return ( + + ); + } + switch (schema.type) { case 'array': return ; case 'object': From d1fb5135d463a5c320028d613fea416d496f5e65 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 6 Apr 2021 02:09:36 +0200 Subject: [PATCH 25/58] config-schema: add support for additional and pattern props and items Signed-off-by: Patrik Oldsberg --- .../components/SchemaViewer/SchemaViewer.tsx | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index b49651ae7b..5e6c1ec94c 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -68,6 +68,12 @@ export function MetadataView({ schema }: { schema: Schema }) { + {schema.additionalProperties === true && ( + + )} + {schema.additionalItems === true && ( + + )} + {schema.additionalItems && schema.additionalItems !== true && ( + <> + Additional Items + + + )} ); } export function ObjectView({ path, depth, schema }: SchemaViewProps) { const properties = Object.entries(schema.properties ?? {}); + const patternProperties = Object.entries(schema.patternProperties ?? {}); + return ( <> {depth > 0 && ( @@ -294,6 +313,33 @@ export function ObjectView({ path, depth, schema }: SchemaViewProps) { ))} )} + {patternProperties.length > 0 && ( + <> + {depth > 0 && ( + Pattern Properties + )} + {patternProperties.map(([name, propSchema], index) => ( + ` : name} + depth={depth + 1} + schema={propSchema} + lastChild={index === patternProperties.length - 1} + required={isRequired(name, schema.required)} + /> + ))} + + )} + {schema.additionalProperties && schema.additionalProperties !== true && ( + <> + Additional Properties + + + )} ); } From 2bfc5466f70725d9d1d15d22a7ff9c078fd46a02 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 6 Apr 2021 10:38:24 +0200 Subject: [PATCH 26/58] config-schema: add tree view schema browser Signed-off-by: Patrik Oldsberg --- .../ConfigSchemaPage/ConfigSchemaPage.tsx | 11 +- .../components/SchemaViewer/SchemaViewer.tsx | 187 +++++++++++++++++- 2 files changed, 188 insertions(+), 10 deletions(-) diff --git a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx index 8811a3e0bd..689ac4379f 100644 --- a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx +++ b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ import React, { useMemo } from 'react'; -import { Grid } from '@material-ui/core'; import { Header, Page, @@ -36,19 +35,15 @@ export const ConfigSchemaPage = () => { return ( -
+
- + A description of your plugin goes here. - - - {schema ? : 'No schema available'} - - + {schema ? : 'No schema available'} ); diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index 5e6c1ec94c..874fea03a9 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { ReactNode, useMemo } from 'react'; import { Schema } from 'jsonschema'; import { Box, Chip, + createStyles, Divider, + fade, makeStyles, Paper, Table, @@ -26,8 +28,12 @@ import { TableCell, TableRow, Typography, + withStyles, } from '@material-ui/core'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import { JsonValue } from '@backstage/config'; +import { TreeItem, TreeItemProps, TreeView } from '@material-ui/lab'; interface SchemaViewProps { path: string; @@ -414,6 +420,183 @@ export interface SchemaViewerProps { schema: Schema; } +const StyledTreeItem = withStyles(theme => + createStyles({ + label: { + userSelect: 'none', + }, + group: { + marginLeft: 7, + paddingLeft: theme.spacing(1), + borderLeft: `1px solid ${fade(theme.palette.text.primary, 0.15)}`, + }, + }), +)((props: TreeItemProps) => ); + +export function createSchemaBrowserItems( + expanded: string[], + schema: Schema, + path: string = '', + depth: number = 0, +): ReactNode { + let matchArr; + if (schema.anyOf) { + matchArr = schema.anyOf; + } else if (schema.oneOf) { + matchArr = schema.oneOf; + } else if (schema.allOf) { + matchArr = schema.allOf; + } + if (matchArr) { + return matchArr.map((childSchema, index) => { + const childPath = `${path}/${index}`; + if (depth > 0) expanded.push(childPath); + return ( + `} + > + {createSchemaBrowserItems( + expanded, + childSchema, + childPath, + depth + 1, + )} + + ); + }); + } + + switch (schema.type) { + case 'array': { + const childPath = `${path}[]`; + if (depth > 0) expanded.push(childPath); + return ( + + {schema.items && + createSchemaBrowserItems( + expanded, + schema.items as Schema, + childPath, + depth + 1, + )} + + ); + } + case 'object': + case undefined: { + const children = []; + + if (schema.properties) { + children.push( + ...Object.entries(schema.properties).map(([name, childSchema]) => { + const childPath = path ? `${path}/${name}` : name; + if (depth > 0) expanded.push(childPath); + return ( + + {createSchemaBrowserItems( + expanded, + childSchema, + childPath, + depth + 1, + )} + + ); + }), + ); + } + + if (schema.patternProperties) { + children.push( + ...Object.entries(schema.patternProperties).map( + ([name, childSchema]) => { + const childPath = `${path}/<${name}>`; + if (depth > 0) expanded.push(childPath); + return ( + `} + > + {createSchemaBrowserItems( + expanded, + childSchema, + childPath, + depth + 1, + )} + + ); + }, + ), + ); + } + + if (schema.additionalProperties && schema.additionalProperties !== true) { + const childPath = `${path}/*`; + if (depth > 0) expanded.push(childPath); + children.push( + + {createSchemaBrowserItems( + expanded, + schema.additionalProperties, + childPath, + depth + 1, + )} + , + ); + } + + return <>{children}; + } + + default: + return null; + } +} + +export function SchemaBrowser({ schema }: { schema: Schema }) { + const data = useMemo(() => { + const expanded = new Array(); + + const items = createSchemaBrowserItems(expanded, schema); + + return { items, expanded }; + }, [schema]); + + return ( + } + defaultExpandIcon={} + > + {data.items} + + ); +} + export const SchemaViewer = ({ schema }: SchemaViewerProps) => { - return ; + return ( + + + + + + + + + + + + + + ); }; From a8d31306396f941034580714549d0024f1c72a94 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 9 Apr 2021 17:49:11 +0200 Subject: [PATCH 27/58] config-schema: scrolling schema view Signed-off-by: Patrik Oldsberg --- .../components/SchemaViewer/SchemaViewer.tsx | 80 ++++++++++++++++--- 1 file changed, 68 insertions(+), 12 deletions(-) diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index 874fea03a9..6b8eab67e7 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -13,7 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { ReactNode, useMemo } from 'react'; +import React, { + createContext, + ReactNode, + useContext, + useEffect, + useMemo, + useRef, +} from 'react'; import { Schema } from 'jsonschema'; import { Box, @@ -35,6 +42,26 @@ import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import { JsonValue } from '@backstage/config'; import { TreeItem, TreeItemProps, TreeView } from '@material-ui/lab'; +class ScrollForwarder { + private readonly listeners = new Map void>(); + + setScrollListener(id: string, listener: () => void): () => void { + this.listeners.set(id, listener); + + return () => { + if (this.listeners.get(id) === listener) { + this.listeners.delete(id); + } + }; + } + + scrollTo(id: string) { + this.listeners.get(id)?.(); + } +} + +const ScrollContext = createContext(undefined); + interface SchemaViewProps { path: string; depth: number; @@ -205,6 +232,15 @@ export function ChildView({ lastChild?: boolean; }) { const classes = useChildViewStyles(); + const titleRef = useRef(null); + const scroll = useContext(ScrollContext); + + useEffect(() => { + return scroll?.setScrollListener(path, () => { + titleRef.current?.scrollIntoView({ behavior: 'smooth' }); + }); + }, [scroll, path]); + const chips = new Array(); const chipProps = { size: 'small' as const, classes: { root: classes.chip } }; @@ -236,6 +272,7 @@ export function ChildView({ alignItems="center" > @@ -449,7 +486,7 @@ export function createSchemaBrowserItems( } if (matchArr) { return matchArr.map((childSchema, index) => { - const childPath = `${path}/${index}`; + const childPath = `${path}.${index}`; if (depth > 0) expanded.push(childPath); return ( { - const childPath = path ? `${path}/${name}` : name; + const childPath = path ? `${path}.${name}` : name; if (depth > 0) expanded.push(childPath); return ( @@ -511,7 +548,7 @@ export function createSchemaBrowserItems( children.push( ...Object.entries(schema.patternProperties).map( ([name, childSchema]) => { - const childPath = `${path}/<${name}>`; + const childPath = `${path}.<${name}>`; if (depth > 0) expanded.push(childPath); return ( 0) expanded.push(childPath); children.push( @@ -556,6 +593,8 @@ export function createSchemaBrowserItems( } export function SchemaBrowser({ schema }: { schema: Schema }) { + const scroll = useContext(ScrollContext); + const expandedRef = useRef([]); const data = useMemo(() => { const expanded = new Array(); @@ -564,12 +603,27 @@ export function SchemaBrowser({ schema }: { schema: Schema }) { return { items, expanded }; }, [schema]); + if (!scroll) { + throw new Error('No scroll handler available'); + } + + const handleToggle = (_event: unknown, expanded: string[]) => { + expandedRef.current = expanded; + }; + + const handleSelect = (_event: unknown, nodeId: string) => { + if (expandedRef.current.includes(nodeId)) { + scroll.scrollTo(nodeId); + } + }; + return ( } defaultExpandIcon={} + onNodeToggle={handleToggle} + onNodeSelect={handleSelect} > {data.items} @@ -588,13 +642,15 @@ export const SchemaViewer = ({ schema }: SchemaViewerProps) => { maxHeight="100%" > - - - + + + + - - - + + + + From 2c27b4f4fd264dd597326b6e6ec62678e53c4fb9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Apr 2021 12:02:41 +0200 Subject: [PATCH 28/58] config-schema: clean up main page Signed-off-by: Patrik Oldsberg --- .../ConfigSchemaPage/ConfigSchemaPage.tsx | 18 ++---------------- .../components/SchemaViewer/SchemaViewer.tsx | 4 ++-- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx index 689ac4379f..add916e997 100644 --- a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx +++ b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx @@ -14,15 +14,7 @@ * limitations under the License. */ import React, { useMemo } from 'react'; -import { - Header, - Page, - Content, - ContentHeader, - HeaderLabel, - SupportButton, - useApi, -} from '@backstage/core'; +import { Header, Page, Content, useApi } from '@backstage/core'; import { useObservable } from 'react-use'; import { configSchemaApiRef } from '../../api'; import { SchemaViewer } from '../SchemaViewer'; @@ -35,14 +27,8 @@ export const ConfigSchemaPage = () => { return ( -
- - -
+
- - A description of your plugin goes here. - {schema ? : 'No schema available'} diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index 6b8eab67e7..e34755a39e 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -403,7 +403,7 @@ export function MatchView({ {label} {schema.map((optionSchema, index) => ( { - const childPath = `${path}.${index}`; + const childPath = `${path}/${index + 1}`; if (depth > 0) expanded.push(childPath); return ( Date: Sat, 10 Apr 2021 12:25:49 +0200 Subject: [PATCH 29/58] config-schema: split out schema view components Signed-off-by: Patrik Oldsberg --- .../src/components/SchemaView/ArrayView.tsx | 59 +++ .../src/components/SchemaView/ChildView.tsx | 123 +++++ .../src/components/SchemaView/MatchView.tsx | 45 ++ .../components/SchemaView/MetadataView.tsx | 110 +++++ .../src/components/SchemaView/ObjectView.tsx | 92 ++++ .../src/components/SchemaView/ScalarView.tsx | 33 ++ .../src/components/SchemaView/SchemaView.tsx | 62 +++ .../src/components/SchemaView/index.ts | 17 + .../src/components/SchemaView/types.ts | 23 + .../components/SchemaViewer/SchemaViewer.tsx | 450 +----------------- .../SchemaViewer/ScrollTargetsContext.tsx | 52 ++ 11 files changed, 626 insertions(+), 440 deletions(-) create mode 100644 plugins/config-schema/src/components/SchemaView/ArrayView.tsx create mode 100644 plugins/config-schema/src/components/SchemaView/ChildView.tsx create mode 100644 plugins/config-schema/src/components/SchemaView/MatchView.tsx create mode 100644 plugins/config-schema/src/components/SchemaView/MetadataView.tsx create mode 100644 plugins/config-schema/src/components/SchemaView/ObjectView.tsx create mode 100644 plugins/config-schema/src/components/SchemaView/ScalarView.tsx create mode 100644 plugins/config-schema/src/components/SchemaView/SchemaView.tsx create mode 100644 plugins/config-schema/src/components/SchemaView/index.ts create mode 100644 plugins/config-schema/src/components/SchemaView/types.ts create mode 100644 plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx diff --git a/plugins/config-schema/src/components/SchemaView/ArrayView.tsx b/plugins/config-schema/src/components/SchemaView/ArrayView.tsx new file mode 100644 index 0000000000..15f4dd8800 --- /dev/null +++ b/plugins/config-schema/src/components/SchemaView/ArrayView.tsx @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Box, Typography } from '@material-ui/core'; +import { Schema } from 'jsonschema'; +import React from 'react'; +import { ChildView } from './ChildView'; +import { MetadataView } from './MetadataView'; +import { SchemaViewProps } from './types'; + +export function ArrayView({ path, depth, schema }: SchemaViewProps) { + const itemDepth = depth + 1; + const itemPath = path ? `${path}[]` : '[]'; + const itemSchema = schema.items; + + return ( + <> + + {schema.description && ( + + {schema.description} + + )} + + + Items + + {schema.additionalItems && schema.additionalItems !== true && ( + <> + Additional Items + + + )} + + ); +} diff --git a/plugins/config-schema/src/components/SchemaView/ChildView.tsx b/plugins/config-schema/src/components/SchemaView/ChildView.tsx new file mode 100644 index 0000000000..bc4f3bb63e --- /dev/null +++ b/plugins/config-schema/src/components/SchemaView/ChildView.tsx @@ -0,0 +1,123 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { JsonValue } from '@backstage/config'; +import { Box, Chip, Divider, makeStyles, Typography } from '@material-ui/core'; +import { Schema } from 'jsonschema'; +import React, { useEffect, useRef } from 'react'; +import { useScrollTargets } from '../SchemaViewer/ScrollContext'; +import { SchemaView } from './SchemaView'; + +export interface MetadataViewRowProps { + label: string; + text?: string; + data?: JsonValue; +} + +function titleVariant(depth: number) { + if (depth <= 1) { + return 'h2'; + } else if (depth === 2) { + return 'h3'; + } else if (depth === 3) { + return 'h4'; + } else if (depth === 4) { + return 'h5'; + } + return 'h6'; +} + +const useChildViewStyles = makeStyles(theme => ({ + title: { + marginBottom: 0, + }, + chip: { + marginLeft: theme.spacing(1), + marginRight: 0, + marginBottom: 0, + }, +})); + +export function ChildView({ + path, + depth, + schema, + required, + lastChild, +}: { + path: string; + depth: number; + schema?: Schema; + required?: boolean; + lastChild?: boolean; +}) { + const classes = useChildViewStyles(); + const titleRef = useRef(null); + const scroll = useScrollTargets(); + + useEffect(() => { + return scroll?.setScrollListener(path, () => { + titleRef.current?.scrollIntoView({ behavior: 'smooth' }); + }); + }, [scroll, path]); + + const chips = new Array(); + const chipProps = { size: 'small' as const, classes: { root: classes.chip } }; + + if (required) { + chips.push( + , + ); + } + + const visibility = (schema as { visibility?: string })?.visibility; + if (visibility === 'frontend') { + chips.push( + , + ); + } else if (visibility === 'secret') { + chips.push( + , + ); + } + + return ( + + + + + + {path} + + {chips.length > 0 && } + {chips} + + {schema && ( + + )} + + + ); +} diff --git a/plugins/config-schema/src/components/SchemaView/MatchView.tsx b/plugins/config-schema/src/components/SchemaView/MatchView.tsx new file mode 100644 index 0000000000..df0dbba8ef --- /dev/null +++ b/plugins/config-schema/src/components/SchemaView/MatchView.tsx @@ -0,0 +1,45 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Typography } from '@material-ui/core'; +import { Schema } from 'jsonschema'; +import React from 'react'; +import { ChildView } from './ChildView'; + +export function MatchView({ + path, + depth, + schema, + label, +}: { + path: string; + depth: number; + schema: Schema[]; + label: string; +}) { + return ( + <> + {label} + {schema.map((optionSchema, index) => ( + + ))} + + ); +} diff --git a/plugins/config-schema/src/components/SchemaView/MetadataView.tsx b/plugins/config-schema/src/components/SchemaView/MetadataView.tsx new file mode 100644 index 0000000000..41d48149ad --- /dev/null +++ b/plugins/config-schema/src/components/SchemaView/MetadataView.tsx @@ -0,0 +1,110 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { JsonValue } from '@backstage/config'; +import { + Paper, + Table, + TableBody, + TableCell, + TableRow, + Typography, +} from '@material-ui/core'; +import { Schema } from 'jsonschema'; +import React from 'react'; + +export interface MetadataViewRowProps { + label: string; + text?: string; + data?: JsonValue; +} + +export function MetadataViewRow({ label, text, data }: MetadataViewRowProps) { + if (text === undefined && data === undefined) { + return null; + } + return ( + + + + {label} + + + + + {data ? JSON.stringify(data) : text} + + + + ); +} + +export function MetadataView({ schema }: { schema: Schema }) { + return ( + + + + + + {schema.additionalProperties === true && ( + + )} + {schema.additionalItems === true && ( + + )} + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/plugins/config-schema/src/components/SchemaView/ObjectView.tsx b/plugins/config-schema/src/components/SchemaView/ObjectView.tsx new file mode 100644 index 0000000000..e3d0effd0d --- /dev/null +++ b/plugins/config-schema/src/components/SchemaView/ObjectView.tsx @@ -0,0 +1,92 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Box, Typography } from '@material-ui/core'; +import React from 'react'; +import { ChildView } from './ChildView'; +import { MetadataView } from './MetadataView'; +import { SchemaViewProps } from './types'; + +function isRequired(name: string, required?: boolean | string[]) { + if (required === true) { + return true; + } + if (Array.isArray(required)) { + return required.includes(name); + } + return false; +} + +export function ObjectView({ path, depth, schema }: SchemaViewProps) { + const properties = Object.entries(schema.properties ?? {}); + const patternProperties = Object.entries(schema.patternProperties ?? {}); + + return ( + <> + {depth > 0 && ( + + {schema.description && ( + + {schema.description} + + )} + + + )} + {properties.length > 0 && ( + <> + {depth > 0 && Properties} + {properties.map(([name, propSchema], index) => ( + + ))} + + )} + {patternProperties.length > 0 && ( + <> + {depth > 0 && ( + Pattern Properties + )} + {patternProperties.map(([name, propSchema], index) => ( + ` : name} + depth={depth + 1} + schema={propSchema} + lastChild={index === patternProperties.length - 1} + required={isRequired(name, schema.required)} + /> + ))} + + )} + {schema.additionalProperties && schema.additionalProperties !== true && ( + <> + Additional Properties + + + )} + + ); +} diff --git a/plugins/config-schema/src/components/SchemaView/ScalarView.tsx b/plugins/config-schema/src/components/SchemaView/ScalarView.tsx new file mode 100644 index 0000000000..1349358abd --- /dev/null +++ b/plugins/config-schema/src/components/SchemaView/ScalarView.tsx @@ -0,0 +1,33 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Box, Typography } from '@material-ui/core'; +import React from 'react'; +import { MetadataView } from './MetadataView'; +import { SchemaViewProps } from './types'; + +export function ScalarView({ schema }: SchemaViewProps) { + return ( + <> + {schema.description && ( + + {schema.description} + + )} + + + ); +} diff --git a/plugins/config-schema/src/components/SchemaView/SchemaView.tsx b/plugins/config-schema/src/components/SchemaView/SchemaView.tsx new file mode 100644 index 0000000000..bf5d7fd18c --- /dev/null +++ b/plugins/config-schema/src/components/SchemaView/SchemaView.tsx @@ -0,0 +1,62 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React from 'react'; +import { ArrayView } from './ArrayView'; +import { MatchView } from './MatchView'; +import { ObjectView } from './ObjectView'; +import { ScalarView } from './ScalarView'; +import { SchemaViewProps } from './types'; + +export function SchemaView(props: SchemaViewProps) { + const { schema } = props; + if (schema.anyOf) { + return ( + + ); + } + if (schema.oneOf) { + return ( + + ); + } + if (schema.allOf) { + return ( + + ); + } + switch (schema.type) { + case 'array': + return ; + case 'object': + case undefined: + return ; + default: + return ; + } +} diff --git a/plugins/config-schema/src/components/SchemaView/index.ts b/plugins/config-schema/src/components/SchemaView/index.ts new file mode 100644 index 0000000000..8840696be6 --- /dev/null +++ b/plugins/config-schema/src/components/SchemaView/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { SchemaView } from './SchemaView'; diff --git a/plugins/config-schema/src/components/SchemaView/types.ts b/plugins/config-schema/src/components/SchemaView/types.ts new file mode 100644 index 0000000000..94b676ec3e --- /dev/null +++ b/plugins/config-schema/src/components/SchemaView/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Schema } from 'jsonschema'; + +export interface SchemaViewProps { + path: string; + depth: number; + schema: Schema; +} diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index e34755a39e..3ad1f55451 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -13,445 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { - createContext, - ReactNode, - useContext, - useEffect, - useMemo, - useRef, -} from 'react'; -import { Schema } from 'jsonschema'; -import { - Box, - Chip, - createStyles, - Divider, - fade, - makeStyles, - Paper, - Table, - TableBody, - TableCell, - TableRow, - Typography, - withStyles, -} from '@material-ui/core'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; + +import { Box, createStyles, fade, Paper, withStyles } from '@material-ui/core'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; -import { JsonValue } from '@backstage/config'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { TreeItem, TreeItemProps, TreeView } from '@material-ui/lab'; - -class ScrollForwarder { - private readonly listeners = new Map void>(); - - setScrollListener(id: string, listener: () => void): () => void { - this.listeners.set(id, listener); - - return () => { - if (this.listeners.get(id) === listener) { - this.listeners.delete(id); - } - }; - } - - scrollTo(id: string) { - this.listeners.get(id)?.(); - } -} - -const ScrollContext = createContext(undefined); - -interface SchemaViewProps { - path: string; - depth: number; - schema: Schema; -} - -export interface MetadataViewRowProps { - label: string; - text?: string; - data?: JsonValue; -} - -export function MetadataViewRow({ label, text, data }: MetadataViewRowProps) { - if (text === undefined && data === undefined) { - return null; - } - return ( - - - - {label} - - - - - {data ? JSON.stringify(data) : text} - - - - ); -} - -export function MetadataView({ schema }: { schema: Schema }) { - return ( - - - - - - {schema.additionalProperties === true && ( - - )} - {schema.additionalItems === true && ( - - )} - - - - - - - - - - - - - - - -
-
- ); -} - -export function ScalarView({ schema }: SchemaViewProps) { - return ( - <> - {schema.description && ( - - {schema.description} - - )} - - - ); -} - -function isRequired(name: string, required?: boolean | string[]) { - if (required === true) { - return true; - } - if (Array.isArray(required)) { - return required.includes(name); - } - return false; -} - -function titleVariant(depth: number) { - if (depth <= 1) { - return 'h2'; - } else if (depth === 2) { - return 'h3'; - } else if (depth === 3) { - return 'h4'; - } else if (depth === 4) { - return 'h5'; - } - return 'h6'; -} - -export function VisibilityView({ schema }: { schema: Schema }) { - const { visibility } = schema as { visibility?: string }; - if (visibility === 'frontend') { - return ( - } - /> - ); - } else if (visibility === 'secret') { - return ( - } - /> - ); - } - return null; -} - -const useChildViewStyles = makeStyles(theme => ({ - title: { - marginBottom: 0, - }, - chip: { - marginLeft: theme.spacing(1), - marginRight: 0, - marginBottom: 0, - }, -})); - -export function ChildView({ - path, - depth, - schema, - required, - lastChild, -}: { - path: string; - depth: number; - schema?: Schema; - required?: boolean; - lastChild?: boolean; -}) { - const classes = useChildViewStyles(); - const titleRef = useRef(null); - const scroll = useContext(ScrollContext); - - useEffect(() => { - return scroll?.setScrollListener(path, () => { - titleRef.current?.scrollIntoView({ behavior: 'smooth' }); - }); - }, [scroll, path]); - - const chips = new Array(); - const chipProps = { size: 'small' as const, classes: { root: classes.chip } }; - - if (required) { - chips.push( - , - ); - } - - const visibility = (schema as { visibility?: string })?.visibility; - if (visibility === 'frontend') { - chips.push( - , - ); - } else if (visibility === 'secret') { - chips.push( - , - ); - } - - return ( - - - - - - {path} - - {chips.length > 0 && } - {chips} - - {schema && ( - - )} - - - ); -} - -export function ArrayView({ path, depth, schema }: SchemaViewProps) { - const itemDepth = depth + 1; - const itemPath = path ? `${path}[]` : '[]'; - const itemSchema = schema.items; - - return ( - <> - - {schema.description && ( - - {schema.description} - - )} - - - Items - - {schema.additionalItems && schema.additionalItems !== true && ( - <> - Additional Items - - - )} - - ); -} - -export function ObjectView({ path, depth, schema }: SchemaViewProps) { - const properties = Object.entries(schema.properties ?? {}); - const patternProperties = Object.entries(schema.patternProperties ?? {}); - - return ( - <> - {depth > 0 && ( - - {schema.description && ( - - {schema.description} - - )} - - - )} - {properties.length > 0 && ( - <> - {depth > 0 && Properties} - {properties.map(([name, propSchema], index) => ( - - ))} - - )} - {patternProperties.length > 0 && ( - <> - {depth > 0 && ( - Pattern Properties - )} - {patternProperties.map(([name, propSchema], index) => ( - ` : name} - depth={depth + 1} - schema={propSchema} - lastChild={index === patternProperties.length - 1} - required={isRequired(name, schema.required)} - /> - ))} - - )} - {schema.additionalProperties && schema.additionalProperties !== true && ( - <> - Additional Properties - - - )} - - ); -} - -export function MatchView({ - path, - depth, - schema, - label, -}: { - path: string; - depth: number; - schema: Schema[]; - label: string; -}) { - return ( - <> - {label} - {schema.map((optionSchema, index) => ( - - ))} - - ); -} - -export function SchemaView(props: SchemaViewProps) { - const { schema } = props; - if (schema.anyOf) { - return ( - - ); - } - if (schema.oneOf) { - return ( - - ); - } - if (schema.allOf) { - return ( - - ); - } - switch (schema.type) { - case 'array': - return ; - case 'object': - case undefined: - return ; - default: - return ; - } -} +import { Schema } from 'jsonschema'; +import React, { ReactNode, useMemo, useRef } from 'react'; +import { SchemaView } from '../SchemaView'; +import { ScrollTargetsProvider, useScrollTargets } from './ScrollContext'; export interface SchemaViewerProps { schema: Schema; @@ -593,7 +163,7 @@ export function createSchemaBrowserItems( } export function SchemaBrowser({ schema }: { schema: Schema }) { - const scroll = useContext(ScrollContext); + const scroll = useScrollTargets(); const expandedRef = useRef([]); const data = useMemo(() => { const expanded = new Array(); @@ -642,7 +212,7 @@ export const SchemaViewer = ({ schema }: SchemaViewerProps) => { maxHeight="100%" > - + @@ -650,7 +220,7 @@ export const SchemaViewer = ({ schema }: SchemaViewerProps) => { - + diff --git a/plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx b/plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx new file mode 100644 index 0000000000..085b237754 --- /dev/null +++ b/plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 React, { createContext, ReactNode, useContext } from 'react'; + +class ScrollTargetsForwarder { + private readonly listeners = new Map void>(); + + setScrollListener(id: string, listener: () => void): () => void { + this.listeners.set(id, listener); + + return () => { + if (this.listeners.get(id) === listener) { + this.listeners.delete(id); + } + }; + } + + scrollTo(id: string) { + this.listeners.get(id)?.(); + } +} + +const ScrollTargetsContext = createContext( + undefined, +); + +export function ScrollTargetsProvider({ children }: { children: ReactNode }) { + return ( + + ); +} + +export function useScrollTargets() { + return useContext(ScrollTargetsContext); +} From bcfef33a7f400ed91ced16a1682d4b843f7ba1c1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Apr 2021 12:40:30 +0200 Subject: [PATCH 30/58] config-schema: break out schema browser and scroll targets context Signed-off-by: Patrik Oldsberg --- .../ConfigSchemaPage/ConfigSchemaPage.tsx | 2 +- .../SchemaBrowser/SchemaBrowser.tsx | 196 ++++++++++++++++++ .../src/components/SchemaBrowser/index.ts | 17 ++ .../src/components/SchemaView/ChildView.tsx | 2 +- .../components/SchemaViewer/SchemaViewer.tsx | 183 +--------------- .../ScrollTargetsContext.tsx | 0 .../components/ScrollTargetsContext/index.ts | 20 ++ 7 files changed, 239 insertions(+), 181 deletions(-) create mode 100644 plugins/config-schema/src/components/SchemaBrowser/SchemaBrowser.tsx create mode 100644 plugins/config-schema/src/components/SchemaBrowser/index.ts rename plugins/config-schema/src/components/{SchemaViewer => ScrollTargetsContext}/ScrollTargetsContext.tsx (100%) create mode 100644 plugins/config-schema/src/components/ScrollTargetsContext/index.ts diff --git a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx index add916e997..4bc7cfd98c 100644 --- a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx +++ b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx @@ -27,7 +27,7 @@ export const ConfigSchemaPage = () => { return ( -
+
{schema ? : 'No schema available'} diff --git a/plugins/config-schema/src/components/SchemaBrowser/SchemaBrowser.tsx b/plugins/config-schema/src/components/SchemaBrowser/SchemaBrowser.tsx new file mode 100644 index 0000000000..52005802b8 --- /dev/null +++ b/plugins/config-schema/src/components/SchemaBrowser/SchemaBrowser.tsx @@ -0,0 +1,196 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { createStyles, fade, withStyles } from '@material-ui/core'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { TreeItem, TreeItemProps, TreeView } from '@material-ui/lab'; +import { Schema } from 'jsonschema'; +import React, { ReactNode, useMemo, useRef } from 'react'; +import { useScrollTargets } from '../ScrollTargetsContext'; + +const StyledTreeItem = withStyles(theme => + createStyles({ + label: { + userSelect: 'none', + }, + group: { + marginLeft: 7, + paddingLeft: theme.spacing(1), + borderLeft: `1px solid ${fade(theme.palette.text.primary, 0.15)}`, + }, + }), +)((props: TreeItemProps) => ); + +export function createSchemaBrowserItems( + expanded: string[], + schema: Schema, + path: string = '', + depth: number = 0, +): ReactNode { + let matchArr; + if (schema.anyOf) { + matchArr = schema.anyOf; + } else if (schema.oneOf) { + matchArr = schema.oneOf; + } else if (schema.allOf) { + matchArr = schema.allOf; + } + if (matchArr) { + return matchArr.map((childSchema, index) => { + const childPath = `${path}/${index + 1}`; + if (depth > 0) expanded.push(childPath); + return ( + `} + > + {createSchemaBrowserItems( + expanded, + childSchema, + childPath, + depth + 1, + )} + + ); + }); + } + + switch (schema.type) { + case 'array': { + const childPath = `${path}[]`; + if (depth > 0) expanded.push(childPath); + return ( + + {schema.items && + createSchemaBrowserItems( + expanded, + schema.items as Schema, + childPath, + depth + 1, + )} + + ); + } + case 'object': + case undefined: { + const children = []; + + if (schema.properties) { + children.push( + ...Object.entries(schema.properties).map(([name, childSchema]) => { + const childPath = path ? `${path}.${name}` : name; + if (depth > 0) expanded.push(childPath); + return ( + + {createSchemaBrowserItems( + expanded, + childSchema, + childPath, + depth + 1, + )} + + ); + }), + ); + } + + if (schema.patternProperties) { + children.push( + ...Object.entries(schema.patternProperties).map( + ([name, childSchema]) => { + const childPath = `${path}.<${name}>`; + if (depth > 0) expanded.push(childPath); + return ( + `} + > + {createSchemaBrowserItems( + expanded, + childSchema, + childPath, + depth + 1, + )} + + ); + }, + ), + ); + } + + if (schema.additionalProperties && schema.additionalProperties !== true) { + const childPath = `${path}.*`; + if (depth > 0) expanded.push(childPath); + children.push( + + {createSchemaBrowserItems( + expanded, + schema.additionalProperties, + childPath, + depth + 1, + )} + , + ); + } + + return <>{children}; + } + + default: + return null; + } +} + +export function SchemaBrowser({ schema }: { schema: Schema }) { + const scroll = useScrollTargets(); + const expandedRef = useRef([]); + const data = useMemo(() => { + const expanded = new Array(); + + const items = createSchemaBrowserItems(expanded, schema); + + return { items, expanded }; + }, [schema]); + + if (!scroll) { + throw new Error('No scroll handler available'); + } + + const handleToggle = (_event: unknown, expanded: string[]) => { + expandedRef.current = expanded; + }; + + const handleSelect = (_event: unknown, nodeId: string) => { + if (expandedRef.current.includes(nodeId)) { + scroll.scrollTo(nodeId); + } + }; + + return ( + } + defaultExpandIcon={} + onNodeToggle={handleToggle} + onNodeSelect={handleSelect} + > + {data.items} + + ); +} diff --git a/plugins/config-schema/src/components/SchemaBrowser/index.ts b/plugins/config-schema/src/components/SchemaBrowser/index.ts new file mode 100644 index 0000000000..2b3e8fe79a --- /dev/null +++ b/plugins/config-schema/src/components/SchemaBrowser/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { SchemaBrowser } from './SchemaBrowser'; diff --git a/plugins/config-schema/src/components/SchemaView/ChildView.tsx b/plugins/config-schema/src/components/SchemaView/ChildView.tsx index bc4f3bb63e..ff970d5251 100644 --- a/plugins/config-schema/src/components/SchemaView/ChildView.tsx +++ b/plugins/config-schema/src/components/SchemaView/ChildView.tsx @@ -18,7 +18,7 @@ import { JsonValue } from '@backstage/config'; import { Box, Chip, Divider, makeStyles, Typography } from '@material-ui/core'; import { Schema } from 'jsonschema'; import React, { useEffect, useRef } from 'react'; -import { useScrollTargets } from '../SchemaViewer/ScrollContext'; +import { useScrollTargets } from '../ScrollTargetsContext/ScrollTargetsContext'; import { SchemaView } from './SchemaView'; export interface MetadataViewRowProps { diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx index 3ad1f55451..3ed3cca0ee 100644 --- a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.tsx @@ -14,192 +14,17 @@ * limitations under the License. */ -import { Box, createStyles, fade, Paper, withStyles } from '@material-ui/core'; -import ChevronRightIcon from '@material-ui/icons/ChevronRight'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; -import { TreeItem, TreeItemProps, TreeView } from '@material-ui/lab'; +import { Box, Paper } from '@material-ui/core'; import { Schema } from 'jsonschema'; -import React, { ReactNode, useMemo, useRef } from 'react'; +import React from 'react'; import { SchemaView } from '../SchemaView'; -import { ScrollTargetsProvider, useScrollTargets } from './ScrollContext'; +import { SchemaBrowser } from '../SchemaBrowser'; +import { ScrollTargetsProvider } from '../ScrollTargetsContext/ScrollTargetsContext'; export interface SchemaViewerProps { schema: Schema; } -const StyledTreeItem = withStyles(theme => - createStyles({ - label: { - userSelect: 'none', - }, - group: { - marginLeft: 7, - paddingLeft: theme.spacing(1), - borderLeft: `1px solid ${fade(theme.palette.text.primary, 0.15)}`, - }, - }), -)((props: TreeItemProps) => ); - -export function createSchemaBrowserItems( - expanded: string[], - schema: Schema, - path: string = '', - depth: number = 0, -): ReactNode { - let matchArr; - if (schema.anyOf) { - matchArr = schema.anyOf; - } else if (schema.oneOf) { - matchArr = schema.oneOf; - } else if (schema.allOf) { - matchArr = schema.allOf; - } - if (matchArr) { - return matchArr.map((childSchema, index) => { - const childPath = `${path}/${index + 1}`; - if (depth > 0) expanded.push(childPath); - return ( - `} - > - {createSchemaBrowserItems( - expanded, - childSchema, - childPath, - depth + 1, - )} - - ); - }); - } - - switch (schema.type) { - case 'array': { - const childPath = `${path}[]`; - if (depth > 0) expanded.push(childPath); - return ( - - {schema.items && - createSchemaBrowserItems( - expanded, - schema.items as Schema, - childPath, - depth + 1, - )} - - ); - } - case 'object': - case undefined: { - const children = []; - - if (schema.properties) { - children.push( - ...Object.entries(schema.properties).map(([name, childSchema]) => { - const childPath = path ? `${path}.${name}` : name; - if (depth > 0) expanded.push(childPath); - return ( - - {createSchemaBrowserItems( - expanded, - childSchema, - childPath, - depth + 1, - )} - - ); - }), - ); - } - - if (schema.patternProperties) { - children.push( - ...Object.entries(schema.patternProperties).map( - ([name, childSchema]) => { - const childPath = `${path}.<${name}>`; - if (depth > 0) expanded.push(childPath); - return ( - `} - > - {createSchemaBrowserItems( - expanded, - childSchema, - childPath, - depth + 1, - )} - - ); - }, - ), - ); - } - - if (schema.additionalProperties && schema.additionalProperties !== true) { - const childPath = `${path}.*`; - if (depth > 0) expanded.push(childPath); - children.push( - - {createSchemaBrowserItems( - expanded, - schema.additionalProperties, - childPath, - depth + 1, - )} - , - ); - } - - return <>{children}; - } - - default: - return null; - } -} - -export function SchemaBrowser({ schema }: { schema: Schema }) { - const scroll = useScrollTargets(); - const expandedRef = useRef([]); - const data = useMemo(() => { - const expanded = new Array(); - - const items = createSchemaBrowserItems(expanded, schema); - - return { items, expanded }; - }, [schema]); - - if (!scroll) { - throw new Error('No scroll handler available'); - } - - const handleToggle = (_event: unknown, expanded: string[]) => { - expandedRef.current = expanded; - }; - - const handleSelect = (_event: unknown, nodeId: string) => { - if (expandedRef.current.includes(nodeId)) { - scroll.scrollTo(nodeId); - } - }; - - return ( - } - defaultExpandIcon={} - onNodeToggle={handleToggle} - onNodeSelect={handleSelect} - > - {data.items} - - ); -} - export const SchemaViewer = ({ schema }: SchemaViewerProps) => { return ( diff --git a/plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx b/plugins/config-schema/src/components/ScrollTargetsContext/ScrollTargetsContext.tsx similarity index 100% rename from plugins/config-schema/src/components/SchemaViewer/ScrollTargetsContext.tsx rename to plugins/config-schema/src/components/ScrollTargetsContext/ScrollTargetsContext.tsx diff --git a/plugins/config-schema/src/components/ScrollTargetsContext/index.ts b/plugins/config-schema/src/components/ScrollTargetsContext/index.ts new file mode 100644 index 0000000000..d2d35ec6b1 --- /dev/null +++ b/plugins/config-schema/src/components/ScrollTargetsContext/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { + ScrollTargetsProvider, + useScrollTargets, +} from './ScrollTargetsContext'; From 589427078385332f66d77605fac80ced8d0e5a78 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Apr 2021 13:47:34 +0200 Subject: [PATCH 31/58] config-schema: separate loading and missing schema Signed-off-by: Patrik Oldsberg --- .../ConfigSchemaPage/ConfigSchemaPage.tsx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx index 4bc7cfd98c..a2082ef972 100644 --- a/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx +++ b/plugins/config-schema/src/components/ConfigSchemaPage/ConfigSchemaPage.tsx @@ -14,23 +14,35 @@ * limitations under the License. */ import React, { useMemo } from 'react'; -import { Header, Page, Content, useApi } from '@backstage/core'; +import { Header, Page, Content, useApi, Progress } from '@backstage/core'; import { useObservable } from 'react-use'; import { configSchemaApiRef } from '../../api'; import { SchemaViewer } from '../SchemaViewer'; +import { Typography } from '@material-ui/core'; export const ConfigSchemaPage = () => { const configSchemaApi = useApi(configSchemaApiRef); - const schema = useObservable( + const schemaResult = useObservable( useMemo(() => configSchemaApi.schema$(), [configSchemaApi]), - )?.schema; + ); + + let content; + if (schemaResult) { + if (schemaResult.schema) { + content = ; + } else { + content = ( + No configuration schema available + ); + } + } else { + content = ; + } return (
- - {schema ? : 'No schema available'} - + {content} ); }; From c1862f42caeaa4653a40db1223b8678ff6c2bf64 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Apr 2021 13:48:20 +0200 Subject: [PATCH 32/58] config-schema: add StaticSchemaLoader Signed-off-by: Patrik Oldsberg --- plugins/config-schema/package.json | 1 + .../src/api/StaticSchemaLoader.ts | 57 +++++++++++++++++++ plugins/config-schema/src/api/index.ts | 1 + plugins/config-schema/src/index.ts | 2 + 4 files changed, 61 insertions(+) create mode 100644 plugins/config-schema/src/api/StaticSchemaLoader.ts diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 67dcfa5976..83d04e1334 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -22,6 +22,7 @@ "dependencies": { "@backstage/config": "^0.1.4", "@backstage/core": "^0.7.3", + "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", diff --git a/plugins/config-schema/src/api/StaticSchemaLoader.ts b/plugins/config-schema/src/api/StaticSchemaLoader.ts new file mode 100644 index 0000000000..ee61370468 --- /dev/null +++ b/plugins/config-schema/src/api/StaticSchemaLoader.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Observable } from '@backstage/core'; +import ObservableImpl from 'zen-observable'; +import { ResponseError } from '@backstage/errors'; +import { Schema } from 'jsonschema'; +import { ConfigSchemaApi, ConfigSchemaResult } from './types'; + +const DEFAULT_URL = 'config-schema.json'; + +/** + * A ConfigSchemaApi implementation that loads the configuration from a URL. + */ +export class StaticSchemaLoader implements ConfigSchemaApi { + private readonly url: string; + + constructor({ url = DEFAULT_URL }: { url?: string } = {}) { + this.url = url; + } + + schema$(): Observable { + return new ObservableImpl(subscriber => { + this.fetchSchema().then( + schema => subscriber.next({ schema }), + error => subscriber.error(error), + ); + }); + } + + private async fetchSchema(): Promise { + const res = await fetch(this.url); + + if (!res.ok) { + if (res.status === 404) { + return undefined; + } + + throw ResponseError.fromResponse(res); + } + + return await res.json(); + } +} diff --git a/plugins/config-schema/src/api/index.ts b/plugins/config-schema/src/api/index.ts index ed5e40c268..eb705b3fa3 100644 --- a/plugins/config-schema/src/api/index.ts +++ b/plugins/config-schema/src/api/index.ts @@ -16,3 +16,4 @@ export { configSchemaApiRef } from './types'; export type { ConfigSchemaApi } from './types'; +export { StaticSchemaLoader } from './StaticSchemaLoader'; diff --git a/plugins/config-schema/src/index.ts b/plugins/config-schema/src/index.ts index 0254d6a36c..009b093d50 100644 --- a/plugins/config-schema/src/index.ts +++ b/plugins/config-schema/src/index.ts @@ -13,4 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +export * from './api'; export { configSchemaPlugin, ConfigSchemaPage } from './plugin'; From 15c58af8ad2f930d9e34b5f2ceb4584720b694c7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Apr 2021 14:02:59 +0200 Subject: [PATCH 33/58] config-schema: update README Signed-off-by: Patrik Oldsberg --- plugins/config-schema/README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/plugins/config-schema/README.md b/plugins/config-schema/README.md index 4d5d2c588d..7367868e7f 100644 --- a/plugins/config-schema/README.md +++ b/plugins/config-schema/README.md @@ -1,13 +1,19 @@ # config-schema -Welcome to the config-schema plugin! +The `config-schema` plugin lets you browse a documentation reference of the configuration schema of a particular Backstage installation. It is intended as a tool for integrators rather than something that is useful to end users of Backstage. -_This plugin was created through the Backstage CLI_ +## Usage -## Getting started +The plugin exports a single full-page extension, the `ConfigSchemaPage`, which you add to an app like a usual top-level tool on a dedicated route. -Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/config-schema](http://localhost:3000/config-schema). +It also exports a `configSchemaApiRef` without any default implementation, meaning that an API needs to be registered in the app for the plugin to work. An implementation of the API that is provided out of the box is the `StaticSchemaLoader`, which loads the schema from a URL. It can be added to the app by adding the following to your app's `api.ts`: -You can also serve the plugin in isolation by running `yarn start` in the plugin directory. -This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. +```ts +createApiFactory(configSchemaApiRef, new StaticSchemaLoader()); +``` + +The configuration schema consumed by the `StaticSchemaLoader` can be generated using the following command: + +```shell +yarn --silent backstage-cli config:schema --format=json +``` From a10377f2451965df81cd8ab3c33c90cf06d172a6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Apr 2021 14:18:25 +0200 Subject: [PATCH 34/58] config-schema: bump @backstage package versions Signed-off-by: Patrik Oldsberg --- plugins/config-schema/package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 83d04e1334..02e3ac3514 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/config": "^0.1.4", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -34,9 +34,9 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.6.6", + "@backstage/cli": "^0.6.7", "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.9", + "@backstage/test-utils": "^0.1.10", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^12.0.7", From e23b3f8f658e85ef59f7de4a5fa8cbf980500495 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 10 Apr 2021 15:10:56 +0200 Subject: [PATCH 35/58] config-schema: added basic test + key fixes Signed-off-by: Patrik Oldsberg --- .../src/components/SchemaView/MatchView.tsx | 1 + .../src/components/SchemaView/ObjectView.tsx | 2 + .../SchemaViewer/SchemaViewer.test.tsx | 136 ++++++++++++++++++ 3 files changed, 139 insertions(+) create mode 100644 plugins/config-schema/src/components/SchemaViewer/SchemaViewer.test.tsx diff --git a/plugins/config-schema/src/components/SchemaView/MatchView.tsx b/plugins/config-schema/src/components/SchemaView/MatchView.tsx index df0dbba8ef..db6060b31a 100644 --- a/plugins/config-schema/src/components/SchemaView/MatchView.tsx +++ b/plugins/config-schema/src/components/SchemaView/MatchView.tsx @@ -34,6 +34,7 @@ export function MatchView({ {label} {schema.map((optionSchema, index) => ( 0 && Properties} {properties.map(([name, propSchema], index) => ( ( ` : name} depth={depth + 1} schema={propSchema} diff --git a/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.test.tsx b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.test.tsx new file mode 100644 index 0000000000..48dc92098a --- /dev/null +++ b/plugins/config-schema/src/components/SchemaViewer/SchemaViewer.test.tsx @@ -0,0 +1,136 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { render, screen } from '@testing-library/react'; +import { Schema } from 'jsonschema'; +import React from 'react'; +import { SchemaViewer } from './SchemaViewer'; + +describe('SchemaViewer', () => { + it('should render a simple schema', () => { + const schema = { + type: 'object', + properties: { + a: { + description: 'My A', + type: 'string', + }, + b: { + description: 'My B', + type: 'number', + }, + }, + }; + + render(); + + expect(screen.getAllByText('a').length).toBe(2); + expect(screen.getByText('My A')).toBeInTheDocument(); + + expect(screen.getAllByText('b').length).toBe(2); + expect(screen.getByText('My B')).toBeInTheDocument(); + }); + + it('should render complex schema', () => { + const schema: Schema = { + type: 'object', + properties: { + a: { + description: 'My A', + type: 'object', + patternProperties: { + 'prefix.*': { + description: 'Prefix prop', + type: 'string', + }, + }, + additionalProperties: { + description: 'Additional properties for A', + type: 'number', + minimum: 72, + maximum: 79, + }, + }, + b: { + oneOf: [ + { type: 'string', description: 'B one of 1' }, + { + type: 'array', + description: 'B one of 2', + items: { + anyOf: [ + { type: 'string', description: 'Any of B 1' }, + { type: 'number', description: 'Any of B 2' }, + { + type: 'object', + description: 'Any of B 3', + properties: { + deep: { + allOf: [ + { + type: 'number', + description: 'Impossible 1', + }, + { + type: 'boolean', + description: 'Impossible 2', + }, + ], + }, + }, + }, + ], + }, + }, + ], + }, + }, + }; + + render(); + + expect(screen.getAllByText('a').length).toBe(2); + expect(screen.getByText('My A')).toBeInTheDocument(); + expect(screen.getByText('a.')).toBeInTheDocument(); + expect(screen.getByText('Prefix prop')).toBeInTheDocument(); + + expect(screen.getByText('a.*')).toBeInTheDocument(); + expect(screen.getByText('Additional properties for A')).toBeInTheDocument(); + expect(screen.getByText('72')).toBeInTheDocument(); + expect(screen.getByText('79')).toBeInTheDocument(); + + expect(screen.getAllByText('b').length).toBe(2); + expect(screen.getByText('b/1')).toBeInTheDocument(); + expect(screen.getByText('B one of 1')).toBeInTheDocument(); + + expect(screen.getByText('b/2')).toBeInTheDocument(); + expect(screen.getByText('B one of 2')).toBeInTheDocument(); + + expect(screen.getByText('b/2[]')).toBeInTheDocument(); + expect(screen.getByText('b/2[]/1')).toBeInTheDocument(); + expect(screen.getByText('Any of B 1')).toBeInTheDocument(); + expect(screen.getByText('b/2[]/2')).toBeInTheDocument(); + expect(screen.getByText('Any of B 2')).toBeInTheDocument(); + expect(screen.getByText('b/2[]/3')).toBeInTheDocument(); + expect(screen.getByText('Any of B 3')).toBeInTheDocument(); + + expect(screen.getByText('b/2[]/3.deep')).toBeInTheDocument(); + expect(screen.getByText('b/2[]/3.deep/1')).toBeInTheDocument(); + expect(screen.getByText('Impossible 1')).toBeInTheDocument(); + expect(screen.getByText('b/2[]/3.deep/2')).toBeInTheDocument(); + expect(screen.getByText('Impossible 2')).toBeInTheDocument(); + }); +}); From 77f13cb4ca77af0d92cfe02e919c7f6084f3571d Mon Sep 17 00:00:00 2001 From: Jussi Hallila Date: Mon, 12 Apr 2021 15:19:42 +0200 Subject: [PATCH 36/58] Update roadie plugins to newer versions * Enable plugins in entity page * Change plugin imports to use new named exports Signed-off-by: Jussi Hallila --- packages/app/package.json | 8 +- .../app/src/components/catalog/EntityPage.tsx | 86 +++++++++---------- packages/app/src/plugins.ts | 8 +- yarn.lock | 36 ++++---- 4 files changed, 71 insertions(+), 67 deletions(-) diff --git a/packages/app/package.json b/packages/app/package.json index 2bac2e6e09..1fc0335aa2 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -42,10 +42,10 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.0.12", - "@roadiehq/backstage-plugin-buildkite": "^0.3.0", - "@roadiehq/backstage-plugin-github-insights": "^0.3.3", - "@roadiehq/backstage-plugin-github-pull-requests": "^0.7.9", - "@roadiehq/backstage-plugin-travis-ci": "^0.4.7", + "@roadiehq/backstage-plugin-buildkite": "^1.0.0", + "@roadiehq/backstage-plugin-github-insights": "^1.0.0", + "@roadiehq/backstage-plugin-github-pull-requests": "^1.0.0", + "@roadiehq/backstage-plugin-travis-ci": "^1.0.0", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index d8ee62a12c..d6f91957a9 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -81,27 +81,27 @@ import { EntitySentryContent } from '@backstage/plugin-sentry'; import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; import { EntityTodoContent } from '@backstage/plugin-todo'; import { Button, Grid } from '@material-ui/core'; -// import { -// EntityBuildkiteContent, -// isBuildkiteAvailable, -// } from '@roadiehq/backstage-plugin-buildkite'; -// import { -// EntityGitHubInsightsContent, -// EntityLanguagesCard, -// EntityReadMeCard, -// EntityReleasesCard, -// isGithubInsightsAvailable, -// } from '@roadiehq/backstage-plugin-github-insights'; -// import { -// EntityGithubPullRequestsContent, -// EntityGithubPullRequestsOverviewCard, -// isGithubPullRequestsAvailable, -// } from '@roadiehq/backstage-plugin-github-pull-requests'; -// import { -// EntityTravisCIContent, -// EntityTravisCIOverviewCard, -// isTravisciAvailable, -// } from '@roadiehq/backstage-plugin-travis-ci'; +import { + EntityBuildkiteContent, + isBuildkiteAvailable, +} from '@roadiehq/backstage-plugin-buildkite'; +import { + EntityGithubInsightsContent, + EntityGithubInsightsLanguagesCard, + EntityGithubInsightsReadmeCard, + EntityGithubInsightsReleasesCard, + isGithubInsightsAvailable, +} from '@roadiehq/backstage-plugin-github-insights'; +import { + EntityGithubPullRequestsContent, + EntityGithubPullRequestsOverviewCard, + isGithubPullRequestsAvailable, +} from '@roadiehq/backstage-plugin-github-pull-requests'; +import { + EntityTravisCIContent, + EntityTravisCIOverviewCard, + isTravisciAvailable, +} from '@roadiehq/backstage-plugin-travis-ci'; const EntityLayoutWrapper = (props: { children?: ReactNode }) => { const [badgesDialogOpen, setBadgesDialogOpen] = useState(false); @@ -135,9 +135,9 @@ export const cicdContent = ( - {/* + - */} + @@ -147,9 +147,9 @@ export const cicdContent = ( - {/* + - */} + @@ -182,11 +182,11 @@ const cicdCard = ( - {/* + - */} + @@ -228,17 +228,17 @@ const overviewContent = ( {cicdCard} - {/* + Boolean(isGithubInsightsAvailable(e))}> - - + + - + - */} + @@ -248,13 +248,13 @@ const overviewContent = ( - {/* + Boolean(isGithubPullRequestsAvailable(e))}> - */} + @@ -295,13 +295,13 @@ const serviceEntityPage = ( - {/* + - */} + - {/* - - */} + + + @@ -339,13 +339,13 @@ const websiteEntityPage = ( - {/* + - */} + - {/* + - */} + diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index ccc7e2a45c..e08bfd6abe 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -27,19 +27,19 @@ export { plugin as GraphiQL } from '@backstage/plugin-graphiql'; export { plugin as GithubActions } from '@backstage/plugin-github-actions'; export { plugin as Rollbar } from '@backstage/plugin-rollbar'; export { plugin as Newrelic } from '@backstage/plugin-newrelic'; -export { plugin as TravisCI } from '@roadiehq/backstage-plugin-travis-ci'; +export { travisciPlugin } from '@roadiehq/backstage-plugin-travis-ci'; export { plugin as Jenkins } from '@backstage/plugin-jenkins'; export { plugin as ApiDocs } from '@backstage/plugin-api-docs'; -export { plugin as GithubPullRequests } from '@roadiehq/backstage-plugin-github-pull-requests'; +export { githubPullRequestsPlugin } from '@roadiehq/backstage-plugin-github-pull-requests'; export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects'; export { plugin as Kubernetes } from '@backstage/plugin-kubernetes'; export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; -export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; +export { githubInsightsPlugin } from '@roadiehq/backstage-plugin-github-insights'; export { plugin as CatalogImport } from '@backstage/plugin-catalog-import'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; export { plugin as PagerDuty } from '@backstage/plugin-pagerduty'; -export { plugin as Buildkite } from '@roadiehq/backstage-plugin-buildkite'; +export { buildkitePlugin } from '@roadiehq/backstage-plugin-buildkite'; export { plugin as Search } from '@backstage/plugin-search'; export { plugin as Org } from '@backstage/plugin-org'; export { plugin as Kafka } from '@backstage/plugin-kafka'; diff --git a/yarn.lock b/yarn.lock index c3bd4a2296..c58b697ac5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4375,14 +4375,15 @@ resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.4.1.tgz#b0dedff8877b114147e298966ca3faba895a7a62" integrity sha512-pZaWL5Dw+km8S0hFLIK1lRHaeNtheMxTF2mZrWhf6HlGWCTGkQJnXta2UgshJN/nKtZPgO1L4FKz42Eun9nnhg== -"@roadiehq/backstage-plugin-buildkite@^0.3.0": - version "0.3.0" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-0.3.0.tgz#60db5e712b88bc9cf3476737a1835f4fc26ae616" - integrity sha512-YeATwsR9G93/l3zZmzDN2k0vZzo0Tw0G0pp16B6p1Tz4lBUxiQ40D0anSsVrzep4NR3rxbzqzh7OGZ8Pihfcpw== +"@roadiehq/backstage-plugin-buildkite@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-1.0.0.tgz#0c00352eb7a204ac45f55bc5dd4c17c9c1cfdf9e" + integrity sha512-R6pxMSOeTK+4OBBhSktEEEIkaVTcOWTsgnpB5FEZ748YZ3Ul4dp4H2S3bup2OnMIL79xY2oAxYt1iRmAe69Bng== dependencies: "@backstage/catalog-model" "^0.7.4" "@backstage/core" "^0.7.3" "@backstage/plugin-catalog" "^0.5.1" + "@backstage/plugin-catalog-react" "^0.1.4" "@backstage/theme" "^0.2.5" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" @@ -4396,13 +4397,14 @@ react-router-dom "6.0.0-beta.0" react-use "^15.3.6" -"@roadiehq/backstage-plugin-github-insights@^0.3.3": - version "0.3.3" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.3.3.tgz#f2b4aeff76930d4ea4f5ca80bf1ad6f407dd6b74" - integrity sha512-sRczrC4JRli8Po1FXL5M6qiCJiUUWpin+kH7XSlo5JJoK5+UdNP05l1qFkMkeCyPdkJp/iVvXArkHPyWnm361g== +"@roadiehq/backstage-plugin-github-insights@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-1.0.0.tgz#8b30bd3c4979aab8cd145c4c0b28488badd8e662" + integrity sha512-KynUXVGb+lpMoDoGrolOtsY9roFuPaMre92g1/DZBoLU2BXFpdmnhJXf1YaDHVuBIITP0AhtQfZpu8KNXm/Cmg== dependencies: "@backstage/catalog-model" "^0.7.4" "@backstage/core" "^0.7.3" + "@backstage/plugin-catalog-react" "^0.1.4" "@backstage/theme" "^0.2.5" "@date-io/core" "2.10.7" "@material-ui/core" "^4.11.0" @@ -4416,13 +4418,14 @@ react-router "^6.0.0-beta.0" react-use "^17.2.1" -"@roadiehq/backstage-plugin-github-pull-requests@^0.7.9": - version "0.7.9" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.7.9.tgz#61ea6bc6a50defe0223ef8dff124379673f54c55" - integrity sha512-qUyCShhtV4x3QaZewiw13lQBmWLTjybOTsy7x+j4iY6IXkOhol5E2uWVTWifJLxDXg76FsXKWw9eRIqBSOrTRQ== +"@roadiehq/backstage-plugin-github-pull-requests@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-1.0.0.tgz#5103d9ff65ed38a752ceb8ac1c7369fd0724388f" + integrity sha512-HAi3X0kto/xMNaMzy9f4t142MGvxwuxDfiLN+bZUsWwta4iI3CgK/9fZAd5kU9+E04EFxkRXmg72FXxG64H6Zg== dependencies: "@backstage/catalog-model" "^0.7.4" "@backstage/core" "^0.7.3" + "@backstage/plugin-catalog-react" "^0.1.4" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@octokit/rest" "^18.1.1" @@ -4436,14 +4439,15 @@ react-router "6.0.0-beta.0" react-use "^15.3.6" -"@roadiehq/backstage-plugin-travis-ci@^0.4.7": - version "0.4.7" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.4.7.tgz#ce37ff8b3124975999f0cfe01170b8b5aee55fb3" - integrity sha512-hDyOND6/gpCy0GpHLwTDXwNfVWVIBpjh5Ipac0bBa7Yyyj0MKI4UVmLu1K99fRZk1aI6n6TIyLYAJTabLmuYuQ== +"@roadiehq/backstage-plugin-travis-ci@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-1.0.0.tgz#5aa29f4ba2086b6341a903a22c843d898808f503" + integrity sha512-JjMDwbou1iY0sSoit8ttpzXEP6lhmLmxf/Pj6Am/1rcia5KLdeMUqpilkzQ33LeHiMXXxu95gp4r6aJ5vC9wGg== dependencies: "@backstage/catalog-model" "^0.7.4" "@backstage/core" "^0.7.3" "@backstage/plugin-catalog" "^0.5.1" + "@backstage/plugin-catalog-react" "^0.1.4" "@backstage/theme" "^0.2.5" "@material-ui/core" "^4.9.1" "@material-ui/icons" "^4.9.1" From ca324156b51e92da4c95dfa8b1a7a5c8fd3c028f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 12 Apr 2021 14:24:18 +0000 Subject: [PATCH 37/58] chore(deps): bump @babel/core from 7.12.9 to 7.13.15 Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.12.9 to 7.13.15. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.13.15/packages/babel-core) Signed-off-by: dependabot[bot] --- yarn.lock | 358 +++++++++++++++--------------------------------------- 1 file changed, 101 insertions(+), 257 deletions(-) diff --git a/yarn.lock b/yarn.lock index f4843d960e..7d4eda4f1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -249,7 +249,7 @@ dependencies: "@babel/highlight" "^7.0.0" -"@babel/code-frame@7.10.4", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": +"@babel/code-frame@7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== @@ -263,77 +263,45 @@ dependencies: "@babel/highlight" "^7.8.3" -"@babel/code-frame@^7.12.13": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.5.5": version "7.12.13" resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658" integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g== dependencies: "@babel/highlight" "^7.12.13" -"@babel/compat-data@^7.12.5", "@babel/compat-data@^7.12.7": +"@babel/compat-data@^7.12.7": version "7.12.7" resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz#9329b4782a7d6bbd7eef57e11addf91ee3ef1e41" integrity sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw== +"@babel/compat-data@^7.13.12": + version "7.13.15" + resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz#7e8eea42d0b64fda2b375b22d06c605222e848f4" + integrity sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA== + "@babel/core@^7.0.0", "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.4.4", "@babel/core@^7.7.5": - version "7.12.9" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" - integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== + version "7.13.15" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.13.15.tgz#a6d40917df027487b54312202a06812c4f7792d0" + integrity sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ== dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.5" - "@babel/helper-module-transforms" "^7.12.1" - "@babel/helpers" "^7.12.5" - "@babel/parser" "^7.12.7" - "@babel/template" "^7.12.7" - "@babel/traverse" "^7.12.9" - "@babel/types" "^7.12.7" + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.9" + "@babel/helper-compilation-targets" "^7.13.13" + "@babel/helper-module-transforms" "^7.13.14" + "@babel/helpers" "^7.13.10" + "@babel/parser" "^7.13.15" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.15" + "@babel/types" "^7.13.14" convert-source-map "^1.7.0" debug "^4.1.0" - gensync "^1.0.0-beta.1" + gensync "^1.0.0-beta.2" json5 "^2.1.2" - lodash "^4.17.19" - resolve "^1.3.2" - semver "^5.4.1" + semver "^6.3.0" source-map "^0.5.0" -"@babel/generator@^7.10.5": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz#1b903554bc8c583ee8d25f1e8969732e6b829a69" - integrity sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig== - dependencies: - "@babel/types" "^7.10.5" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.11.0", "@babel/generator@^7.5.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.11.0.tgz#4b90c78d8c12825024568cbe83ee6c9af193585c" - integrity sha512-fEm3Uzw7Mc9Xi//qU20cBKatTfs2aOtKqmvy/Vm7RkJEGFQ4xc9myCfbXxqK//ZS8MR/ciOHw6meGASJuKmDfQ== - dependencies: - "@babel/types" "^7.11.0" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.11.5", "@babel/generator@^7.9.6": - version "7.11.6" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" - integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA== - dependencies: - "@babel/types" "^7.11.5" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.12.5": - version "7.12.5" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.12.5.tgz#a2c50de5c8b6d708ab95be5e6053936c1884a4de" - integrity sha512-m16TQQJ8hPt7E+OS/XVQg/7U184MLXtvuGbCdA7na61vha+ImkyyNM/9DDA0unYCVZn3ZOhng+qz48/KBOT96A== - dependencies: - "@babel/types" "^7.12.5" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/generator@^7.13.0": +"@babel/generator@^7.11.5", "@babel/generator@^7.13.9", "@babel/generator@^7.5.0": version "7.13.9" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39" integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== @@ -399,15 +367,15 @@ "@babel/helper-annotate-as-pure" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-compilation-targets@^7.12.5": - version "7.12.5" - resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz#cb470c76198db6a24e9dbc8987275631e5d29831" - integrity sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw== +"@babel/helper-compilation-targets@^7.12.5", "@babel/helper-compilation-targets@^7.13.13": + version "7.13.13" + resolved "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz#2b2972a0926474853f41e4adbc69338f520600e5" + integrity sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ== dependencies: - "@babel/compat-data" "^7.12.5" - "@babel/helper-validator-option" "^7.12.1" + "@babel/compat-data" "^7.13.12" + "@babel/helper-validator-option" "^7.12.17" browserslist "^4.14.5" - semver "^5.5.0" + semver "^6.3.0" "@babel/helper-create-class-features-plugin@^7.10.4": version "7.10.5" @@ -466,7 +434,7 @@ "@babel/traverse" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helper-function-name@^7.10.4", "@babel/helper-function-name@^7.9.5": +"@babel/helper-function-name@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== @@ -519,12 +487,12 @@ dependencies: "@babel/types" "^7.12.7" -"@babel/helper-member-expression-to-functions@^7.13.0": - version "7.13.0" - resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz#6aa4bb678e0f8c22f58cdb79451d30494461b091" - integrity sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ== +"@babel/helper-member-expression-to-functions@^7.13.12": + version "7.13.12" + resolved "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72" + integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw== dependencies: - "@babel/types" "^7.13.0" + "@babel/types" "^7.13.12" "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4": version "7.10.4" @@ -540,27 +508,26 @@ dependencies: "@babel/types" "^7.12.5" -"@babel/helper-module-imports@^7.12.13": - version "7.12.13" - resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz#ec67e4404f41750463e455cc3203f6a32e93fcb0" - integrity sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g== +"@babel/helper-module-imports@^7.13.12": + version "7.13.12" + resolved "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz#c6a369a6f3621cb25da014078684da9196b61977" + integrity sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA== dependencies: - "@babel/types" "^7.12.13" + "@babel/types" "^7.13.12" -"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.13.0": - version "7.13.0" - resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.0.tgz#42eb4bd8eea68bab46751212c357bfed8b40f6f1" - integrity sha512-Ls8/VBwH577+pw7Ku1QkUWIyRRNHpYlts7+qSqBBFCW3I8QteB9DxfcZ5YJpOwH6Ihe/wn8ch7fMGOP1OhEIvw== +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.13.14": + version "7.13.14" + resolved "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz#e600652ba48ccb1641775413cb32cfa4e8b495ef" + integrity sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g== dependencies: - "@babel/helper-module-imports" "^7.12.13" - "@babel/helper-replace-supers" "^7.13.0" - "@babel/helper-simple-access" "^7.12.13" + "@babel/helper-module-imports" "^7.13.12" + "@babel/helper-replace-supers" "^7.13.12" + "@babel/helper-simple-access" "^7.13.12" "@babel/helper-split-export-declaration" "^7.12.13" "@babel/helper-validator-identifier" "^7.12.11" "@babel/template" "^7.12.13" - "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" - lodash "^4.17.19" + "@babel/traverse" "^7.13.13" + "@babel/types" "^7.13.14" "@babel/helper-optimise-call-expression@^7.10.4": version "7.10.4" @@ -617,15 +584,15 @@ "@babel/traverse" "^7.12.5" "@babel/types" "^7.12.5" -"@babel/helper-replace-supers@^7.13.0": - version "7.13.0" - resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz#6034b7b51943094cb41627848cb219cb02be1d24" - integrity sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw== +"@babel/helper-replace-supers@^7.13.12": + version "7.13.12" + resolved "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz#6442f4c1ad912502481a564a7386de0c77ff3804" + integrity sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw== dependencies: - "@babel/helper-member-expression-to-functions" "^7.13.0" + "@babel/helper-member-expression-to-functions" "^7.13.12" "@babel/helper-optimise-call-expression" "^7.12.13" "@babel/traverse" "^7.13.0" - "@babel/types" "^7.13.0" + "@babel/types" "^7.13.12" "@babel/helper-simple-access@^7.12.13": version "7.12.13" @@ -634,6 +601,13 @@ dependencies: "@babel/types" "^7.12.13" +"@babel/helper-simple-access@^7.13.12": + version "7.13.12" + resolved "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6" + integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA== + dependencies: + "@babel/types" "^7.13.12" + "@babel/helper-skip-transparent-expression-wrappers@^7.11.0": version "7.11.0" resolved "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.11.0.tgz#eec162f112c2f58d3af0af125e3bb57665146729" @@ -648,7 +622,7 @@ dependencies: "@babel/types" "^7.12.1" -"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0", "@babel/helper-split-export-declaration@^7.8.3": +"@babel/helper-split-export-declaration@^7.10.4", "@babel/helper-split-export-declaration@^7.11.0": version "7.11.0" resolved "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== @@ -677,6 +651,11 @@ resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz#175567380c3e77d60ff98a54bb015fe78f2178d9" integrity sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A== +"@babel/helper-validator-option@^7.12.17": + version "7.12.17" + resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz#d1fbf012e1a79b7eebbfdc6d270baaf8d9eb9831" + integrity sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw== + "@babel/helper-wrap-function@^7.10.4": version "7.10.4" resolved "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.10.4.tgz#8a6f701eab0ff39f765b5a1cfef409990e624b87" @@ -687,14 +666,14 @@ "@babel/traverse" "^7.10.4" "@babel/types" "^7.10.4" -"@babel/helpers@^7.12.5": - version "7.12.5" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz#1a1ba4a768d9b58310eda516c449913fe647116e" - integrity sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA== +"@babel/helpers@^7.13.10": + version "7.13.10" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8" + integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ== dependencies: - "@babel/template" "^7.10.4" - "@babel/traverse" "^7.12.5" - "@babel/types" "^7.12.5" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" "@babel/highlight@^7.0.0", "@babel/highlight@^7.10.4", "@babel/highlight@^7.8.3": version "7.10.4" @@ -714,35 +693,15 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@7.11.5", "@babel/parser@^7.11.5": +"@babel/parser@7.11.5": version "7.11.5" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== -"@babel/parser@^7.0.0", "@babel/parser@^7.11.0": - version "7.11.3" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.11.3.tgz#9e1eae46738bcd08e23e867bab43e7b95299a8f9" - integrity sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA== - -"@babel/parser@^7.1.0", "@babel/parser@^7.8.6", "@babel/parser@^7.9.6": - version "7.9.6" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz#3b1bbb30dabe600cd72db58720998376ff653bc7" - integrity sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q== - -"@babel/parser@^7.10.4", "@babel/parser@^7.10.5": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz#e7c6bf5a7deff957cec9f04b551e2762909d826b" - integrity sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ== - -"@babel/parser@^7.12.13", "@babel/parser@^7.13.0": - version "7.13.11" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz#f93ebfc99d21c1772afbbaa153f47e7ce2f50b88" - integrity sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q== - -"@babel/parser@^7.12.7": - version "7.12.7" - resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.7.tgz#fee7b39fe809d0e73e5b25eecaf5780ef3d73056" - integrity sha512-oWR02Ubp4xTLCAqPRiNIuMVgNO5Aif/xpXtabhzW2HWUD47XJsAB4Zd/Rg30+XeQA3juXigV7hlquOTmwqLiwg== +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.11.5", "@babel/parser@^7.12.13", "@babel/parser@^7.13.15": + version "7.13.15" + resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz#8e66775fb523599acb6a289e12929fa5ab0954d8" + integrity sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ== "@babel/plugin-proposal-async-generator-functions@^7.12.1": version "7.12.1" @@ -1663,16 +1622,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.10.4": - version "7.10.4" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" - integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/parser" "^7.10.4" - "@babel/types" "^7.10.4" - -"@babel/template@^7.12.13": +"@babel/template@^7.10.4", "@babel/template@^7.12.13", "@babel/template@^7.3.3": version "7.12.13" resolved "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327" integrity sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA== @@ -1681,24 +1631,6 @@ "@babel/parser" "^7.12.13" "@babel/types" "^7.12.13" -"@babel/template@^7.12.7": - version "7.12.7" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc" - integrity sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/parser" "^7.12.7" - "@babel/types" "^7.12.7" - -"@babel/template@^7.3.3": - version "7.8.6" - resolved "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" - integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/parser" "^7.8.6" - "@babel/types" "^7.8.6" - "@babel/traverse@7.11.5": version "7.11.5" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" @@ -1714,82 +1646,21 @@ globals "^11.1.0" lodash "^4.17.19" -"@babel/traverse@^7.0.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24" - integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.11.0" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/parser" "^7.11.0" - "@babel/types" "^7.11.0" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.19" - -"@babel/traverse@^7.1.0": - version "7.9.6" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz#5540d7577697bf619cc57b92aa0f1c231a94f442" - integrity sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg== - dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.6" - "@babel/helper-function-name" "^7.9.5" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.6" - "@babel/types" "^7.9.6" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.13" - -"@babel/traverse@^7.10.4": - version "7.10.5" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz#77ce464f5b258be265af618d8fddf0536f20b564" - integrity sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.10.5" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.10.4" - "@babel/parser" "^7.10.5" - "@babel/types" "^7.10.5" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.19" - -"@babel/traverse@^7.12.5", "@babel/traverse@^7.12.9": - version "7.12.9" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.9.tgz#fad26c972eabbc11350e0b695978de6cc8e8596f" - integrity sha512-iX9ajqnLdoU1s1nHt36JDI9KG4k+vmI8WgjK5d+aDTwQbL2fUnzedNedssA645Ede3PM2ma1n8Q4h2ohwXgMXw== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/generator" "^7.12.5" - "@babel/helper-function-name" "^7.10.4" - "@babel/helper-split-export-declaration" "^7.11.0" - "@babel/parser" "^7.12.7" - "@babel/types" "^7.12.7" - debug "^4.1.0" - globals "^11.1.0" - lodash "^4.17.19" - -"@babel/traverse@^7.13.0": - version "7.13.0" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc" - integrity sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ== +"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.12.5", "@babel/traverse@^7.13.0", "@babel/traverse@^7.13.13", "@babel/traverse@^7.13.15": + version "7.13.15" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz#c38bf7679334ddd4028e8e1f7b3aa5019f0dada7" + integrity sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ== dependencies: "@babel/code-frame" "^7.12.13" - "@babel/generator" "^7.13.0" + "@babel/generator" "^7.13.9" "@babel/helper-function-name" "^7.12.13" "@babel/helper-split-export-declaration" "^7.12.13" - "@babel/parser" "^7.13.0" - "@babel/types" "^7.13.0" + "@babel/parser" "^7.13.15" + "@babel/types" "^7.13.14" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.19" -"@babel/types@7.11.5", "@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.8.6", "@babel/types@^7.9.6": +"@babel/types@7.11.5": version "7.11.5" resolved "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q== @@ -1798,37 +1669,10 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@babel/types@^7.11.0": - version "7.11.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d" - integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA== - dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - lodash "^4.17.19" - to-fast-properties "^2.0.0" - -"@babel/types@^7.12.1", "@babel/types@^7.12.5", "@babel/types@^7.12.6", "@babel/types@^7.12.7": - version "7.12.7" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.12.7.tgz#6039ff1e242640a29452c9ae572162ec9a8f5d13" - integrity sha512-MNyI92qZq6jrQkXvtIiykvl4WtoRrVV9MPn+ZfsoEENjiWcBQ3ZSHrkxnJWgWtLX3XXqX5hrSQ+X69wkmesXuQ== - dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - lodash "^4.17.19" - to-fast-properties "^2.0.0" - -"@babel/types@^7.12.10": - version "7.12.10" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.12.10.tgz#7965e4a7260b26f09c56bcfcb0498af1f6d9b260" - integrity sha512-sf6wboJV5mGyip2hIpDSKsr80RszPinEFjsHTalMxZAZkoQ2/2yQzxlcFN52SJqsyPfLtPmenL4g2KB3KJXPDw== - dependencies: - "@babel/helper-validator-identifier" "^7.10.4" - lodash "^4.17.19" - to-fast-properties "^2.0.0" - -"@babel/types@^7.12.13", "@babel/types@^7.13.0": - version "7.13.0" - resolved "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80" - integrity sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA== +"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.12.1", "@babel/types@^7.12.10", "@babel/types@^7.12.13", "@babel/types@^7.12.5", "@babel/types@^7.12.6", "@babel/types@^7.12.7", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.14", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.13.14" + resolved "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz#c35a4abb15c7cd45a2746d78ab328e362cbace0d" + integrity sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ== dependencies: "@babel/helper-validator-identifier" "^7.12.11" lodash "^4.17.19" @@ -13898,10 +13742,10 @@ generic-pool@2.4.3: resolved "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.3.tgz#780c36f69dfad05a5a045dd37be7adca11a4f6ff" integrity sha1-eAw29p360FpaBF3Te+etyhGk9v8= -gensync@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" - integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== get-caller-file@^1.0.1: version "1.0.3" @@ -14502,7 +14346,7 @@ graphql-language-service-interface@2.8.2, graphql-language-service-interface@^2. graphql-language-service-utils "^2.5.1" vscode-languageserver-types "^3.15.1" -graphql-language-service-parser@^1.9.0: +graphql-language-service-parser@1.9.0, graphql-language-service-parser@^1.9.0: version "1.9.0" resolved "https://registry.npmjs.org/graphql-language-service-parser/-/graphql-language-service-parser-1.9.0.tgz#79af21294119a0a7e81b6b994a1af36833bab724" integrity sha512-B5xPZLbBmIp0kHvpY1Z35I5DtPoDK9wGxQVRDIzcBaiIvAmlTrDvjo3bu7vKREdjFbYKvWNgrEWENuprMbF17Q== @@ -17912,7 +17756,7 @@ lodash@4.17.15: resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -lodash@4.x, lodash@^4.0.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.20, lodash@~4.17.4: +lodash@4.x, lodash@^4.0.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.20, lodash@~4.17.4: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -23127,7 +22971,7 @@ resolve-url@^0.2.1: resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.3.2, resolve@^1.9.0: +resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.9.0: version "1.20.0" resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== From 18f7345a6c29dcfccef7df081b1708f2bf8f62ff Mon Sep 17 00:00:00 2001 From: David Tuite Date: Mon, 12 Apr 2021 19:11:35 +0100 Subject: [PATCH 38/58] Add borders to docs tables (#5281) --- .changeset/techdocs-dry-starfishes-carry.md | 5 +++++ plugins/techdocs/src/reader/components/Reader.tsx | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 .changeset/techdocs-dry-starfishes-carry.md diff --git a/.changeset/techdocs-dry-starfishes-carry.md b/.changeset/techdocs-dry-starfishes-carry.md new file mode 100644 index 0000000000..f5841c6994 --- /dev/null +++ b/.changeset/techdocs-dry-starfishes-carry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Add borders to TechDocs tables and increase font size. Fixes #5264 and #5276. diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 67b39d0ef7..874ca5e018 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -162,6 +162,16 @@ export const Reader = ({ entityId, onReady }: Props) => { .md-typeset { font-size: 1rem; } .md-nav { font-size: 1rem; } .md-grid { max-width: 90vw; margin: 0 } + .md-typeset table:not([class]) { + font-size: 1rem; + border: 1px solid ${theme.palette.text.primary}; + border-bottom: none; + border-collapse: collapse; + } + .md-typeset table:not([class]) td, .md-typeset table:not([class]) th { + border-bottom: 1px solid ${theme.palette.text.primary}; + } + .md-typeset table:not([class]) th { font-weight: bold; } @media screen and (max-width: 76.1875em) { .md-nav { background-color: ${theme.palette.background.default}; @@ -221,6 +231,7 @@ export const Reader = ({ entityId, onReady }: Props) => { :host { --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,'); --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); + } `, }), ]); From 12390778ee4b27672088e4e421309fdb54cdb561 Mon Sep 17 00:00:00 2001 From: Ben Lambert Date: Fri, 9 Apr 2021 14:25:03 +0200 Subject: [PATCH 39/58] Add Changeset Signed-off-by: blam --- .changeset/sour-dryers-sort.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/sour-dryers-sort.md diff --git a/.changeset/sour-dryers-sort.md b/.changeset/sour-dryers-sort.md new file mode 100644 index 0000000000..ee15655e82 --- /dev/null +++ b/.changeset/sour-dryers-sort.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-api-docs': patch +--- + +chore(deps): bump @asyncapi/react-component from 0.19.2 to 0.22.3 From 06eb4b06e835d6970711949865ad2abc745bba7f Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 13 Apr 2021 10:54:10 +0200 Subject: [PATCH 40/58] chore: add deps word to stlyes Signed-off-by: blam --- .github/styles/vocab.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index b0bcf9c9f7..8d89012020 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -112,6 +112,7 @@ css dataflow deadnaming declaratively +deps destructured dev devops From 4d1617a3e67d05325fa7f1a3168f48ae25a72bc5 Mon Sep 17 00:00:00 2001 From: Pranava Shashank Pindi Date: Fri, 9 Apr 2021 00:49:13 +0530 Subject: [PATCH 41/58] Updated documentation for sonarcloud plugin For a new react developer working with backstage, he gets blocked while installing the sonarcloud plugin in backstage app. He faces the following [issue](https://github.com/backstage/backstage/issues/5141) after following the steps mentioned in the plugin [documentation](https://github.com/backstage/backstage/tree/master/plugins/sonarqube). Hence, this commit adds `yarn install` and `yarn tsc` commands to be run. Also, it adds additional information on how to set the SONARQUBE_AUTH_HEADER environmental header. Signed-off-by: pranava29 Signed-off-by: pranava29 --- plugins/sonarqube/README.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/plugins/sonarqube/README.md b/plugins/sonarqube/README.md index 7afc9cc04a..f95a2c6151 100644 --- a/plugins/sonarqube/README.md +++ b/plugins/sonarqube/README.md @@ -71,8 +71,12 @@ proxy: allowedMethods: ['GET'] headers: Authorization: - # Content: 'Basic base64(":")' <-- note the trailing ':' - # Example: Basic bXktYXBpLWtleTo= + # Environmental variable: SONARQUBE_AUTH_HEADER + # Value: 'Basic base64(":")' + # Encode the ":" string using base64 encoder. + # Note the trailing colon (:) at the end of the token. + # Example environmental config: SONARQUBE_AUTH_HEADER=Basic bXktYXBpLWtleTo= + # Fetch the sonar-auth-token from https://sonarcloud.io/account/security/ $env: SONARQUBE_AUTH_HEADER sonarQube: @@ -81,7 +85,14 @@ sonarQube: 5. Get and provide `SONARQUBE_AUTH_HEADER` as env variable (https://sonarcloud.io/account/security or https://docs.sonarqube.org/latest/user-guide/user-token/) -6. Add the `sonarqube.org/project-key` annotation to your catalog-info.yaml file: +6. Run the following commands in the root folder of the project to install and compile the changes. + +```yaml +yarn install +yarn tsc +``` + +7. Add the `sonarqube.org/project-key` annotation to the catalog-info.yaml file of the target repo for which code quality analysis is needed. ```yaml apiVersion: backstage.io/v1alpha1 From 88d62cad736da437cb0967efd8a2ab7bd577064e Mon Sep 17 00:00:00 2001 From: Omer Farooq Date: Sun, 11 Apr 2021 15:50:30 +1200 Subject: [PATCH 42/58] feat(techradar-plugin) add markdown support in techradar entry description Signed-off-by: Omer Farooq --- .../src/components/RadarDescription/RadarDescription.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx index 20c489f630..3ac4f153be 100644 --- a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx +++ b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx @@ -19,6 +19,7 @@ import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; import { Button, DialogActions, DialogContent } from '@material-ui/core'; import LinkIcon from '@material-ui/icons/Link'; +import { MarkdownContent } from '@backstage/core'; export type Props = { open: boolean; @@ -43,7 +44,9 @@ const RadarDescription = (props: Props): JSX.Element => { {title} - {description} + + + {url && (
); From 5abace95ac82c9cc866412ea43b7326971456c2e Mon Sep 17 00:00:00 2001 From: Frank Showalter Date: Tue, 13 Apr 2021 10:45:52 -0400 Subject: [PATCH 49/58] Disable hot-reloading for e2e CI tests Signed-off-by: Frank Showalter --- packages/cli/src/lib/bundler/server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts index 5fc3a7b5de..24a24d25d2 100644 --- a/packages/cli/src/lib/bundler/server.ts +++ b/packages/cli/src/lib/bundler/server.ts @@ -43,7 +43,7 @@ export async function serveBundle(options: ServeOptions) { const compiler = webpack(config); const server = new WebpackDevServer(compiler, { - hot: true, + hot: !process.env.CI, contentBase: paths.targetPublic, contentBasePublicPath: config.output?.publicPath, publicPath: config.output?.publicPath, From 60ce64aa20cf7a8a870bbe7a922cfd28224bf9ee Mon Sep 17 00:00:00 2001 From: Frank Showalter Date: Tue, 13 Apr 2021 10:49:37 -0400 Subject: [PATCH 50/58] add changeset Signed-off-by: Frank Showalter --- .changeset/young-kids-remember.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/young-kids-remember.md diff --git a/.changeset/young-kids-remember.md b/.changeset/young-kids-remember.md new file mode 100644 index 0000000000..c07097234f --- /dev/null +++ b/.changeset/young-kids-remember.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Disable hot reloading in CI environments. From 78cad7589aea83a0be3b64fed25befbc5e1240d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Apr 2021 04:15:33 +0000 Subject: [PATCH 51/58] chore(deps): bump @rjsf/material-ui from 2.4.1 to 2.5.1 Bumps [@rjsf/material-ui](https://github.com/rjsf-team/react-jsonschema-form) from 2.4.1 to 2.5.1. - [Release notes](https://github.com/rjsf-team/react-jsonschema-form/releases) - [Commits](https://github.com/rjsf-team/react-jsonschema-form/compare/v2.4.1...v2.5.1) Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6f7e171b2c..ac4cf4c08e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4239,9 +4239,9 @@ shortid "^2.2.14" "@rjsf/material-ui@^2.4.0": - version "2.4.1" - resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.4.1.tgz#b0dedff8877b114147e298966ca3faba895a7a62" - integrity sha512-pZaWL5Dw+km8S0hFLIK1lRHaeNtheMxTF2mZrWhf6HlGWCTGkQJnXta2UgshJN/nKtZPgO1L4FKz42Eun9nnhg== + version "2.5.1" + resolved "https://registry.npmjs.org/@rjsf/material-ui/-/material-ui-2.5.1.tgz#b93ac9f1f4a909e2aae729616859c2d72557e53e" + integrity sha512-ooKxQJO12+i1xCGtknMZDxWSbVlSEgQ5U1I7lJ+uI/MvwAg3Rz9wb2cTEF3ErBczT7EEW+j1lR19rxBjFd78MA== "@roadiehq/backstage-plugin-buildkite@^1.0.0": version "1.0.0" From bb5055aeea70f4463c00392e57a3f2465541959b Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Apr 2021 09:59:00 +0200 Subject: [PATCH 52/58] Catalog-model: Add getEntitySourceLocation helper Co-authored-by: Patrik Oldsberg Signed-off-by: Johan Haals --- .changeset/metal-foxes-speak.md | 5 ++ .../src/location/helpers.test.ts | 51 ++++++++++++++++++- .../catalog-model/src/location/helpers.ts | 26 ++++++++++ packages/catalog-model/src/location/index.ts | 6 ++- 4 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 .changeset/metal-foxes-speak.md diff --git a/.changeset/metal-foxes-speak.md b/.changeset/metal-foxes-speak.md new file mode 100644 index 0000000000..45b8ae3c7c --- /dev/null +++ b/.changeset/metal-foxes-speak.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-model': patch +--- + +Add getEntitySourceLocation helper diff --git a/packages/catalog-model/src/location/helpers.test.ts b/packages/catalog-model/src/location/helpers.test.ts index 888c79b328..3b5994b956 100644 --- a/packages/catalog-model/src/location/helpers.test.ts +++ b/packages/catalog-model/src/location/helpers.test.ts @@ -14,7 +14,11 @@ * limitations under the License. */ -import { parseLocationReference, stringifyLocationReference } from './helpers'; +import { + getEntitySourceLocation, + parseLocationReference, + stringifyLocationReference, +} from './helpers'; describe('parseLocationReference', () => { it('works for the simple case', () => { @@ -68,3 +72,48 @@ describe('stringifyLocationReference', () => { ).toThrow('Unable to stringify location reference, empty target'); }); }); + +describe('getEntitySourceLocation', () => { + it('returns the source-location', () => { + expect( + getEntitySourceLocation({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + name: 'test', + namespace: 'default', + annotations: { + 'backstage.io/source-location': 'url:https://backstage.io/foo.yaml', + 'backstage.io/managed-by-location': 'url:https://spotify.com', + }, + }, + }), + ).toEqual({ target: 'https://backstage.io/foo.yaml', type: 'url' }); + }); + + it('returns the managed-by-location', () => { + expect( + getEntitySourceLocation({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + name: 'test', + namespace: 'default', + annotations: { + 'backstage.io/managed-by-location': 'url:https://spotify.com', + }, + }, + }), + ).toEqual({ target: 'https://spotify.com', type: 'url' }); + }); + + it('rejects missing location annotation', () => { + expect(() => + getEntitySourceLocation({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { name: 'test', namespace: 'default' }, + }), + ).toThrow(`Entity 'location:default/test' is missing location`); + }); +}); diff --git a/packages/catalog-model/src/location/helpers.ts b/packages/catalog-model/src/location/helpers.ts index fb1be5abad..9dce67d054 100644 --- a/packages/catalog-model/src/location/helpers.ts +++ b/packages/catalog-model/src/location/helpers.ts @@ -14,6 +14,9 @@ * limitations under the License. */ +import { Entity, stringifyEntityRef } from '../entity'; +import { LOCATION_ANNOTATION, SOURCE_LOCATION_ANNOTATION } from './annotation'; + /** * Parses a string form location reference. * @@ -80,3 +83,26 @@ export function stringifyLocationReference(ref: { return `${type}:${target}`; } + +/** + * Returns the source code location of the Entity, to the extend which one exists. + * + * If the returned location type is of type 'url', the target should be readable at least + * using the UrlReader from @backstage/backend-common. If it is not of type 'url', the caller + * needs to have explicit handling of each location type or signal that it is not supported. + */ +export function getEntitySourceLocation( + entity: Entity, +): { type: string; target: string } { + const locationRef = + entity.metadata?.annotations?.[SOURCE_LOCATION_ANNOTATION] ?? + entity.metadata?.annotations?.[LOCATION_ANNOTATION]; + + if (!locationRef) { + throw new Error( + `Entity '${stringifyEntityRef(entity)}' is missing location`, + ); + } + + return parseLocationReference(locationRef); +} diff --git a/packages/catalog-model/src/location/index.ts b/packages/catalog-model/src/location/index.ts index fddc8bde37..751172c6ba 100644 --- a/packages/catalog-model/src/location/index.ts +++ b/packages/catalog-model/src/location/index.ts @@ -19,7 +19,11 @@ export { ORIGIN_LOCATION_ANNOTATION, SOURCE_LOCATION_ANNOTATION, } from './annotation'; -export { parseLocationReference, stringifyLocationReference } from './helpers'; +export { + parseLocationReference, + stringifyLocationReference, + getEntitySourceLocation, +} from './helpers'; export type { Location, LocationSpec } from './types'; export { analyzeLocationSchema, From e44574d2147544aebf45f6d83c26d26048aba13f Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Wed, 14 Apr 2021 10:20:58 +0200 Subject: [PATCH 53/58] Update packages/catalog-model/src/location/helpers.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Johan Haals johan.haals@gmail.com Co-authored-by: Fredrik Adelöw Signed-off-by: Johan Haals --- packages/catalog-model/src/location/helpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catalog-model/src/location/helpers.ts b/packages/catalog-model/src/location/helpers.ts index 9dce67d054..5eff598c87 100644 --- a/packages/catalog-model/src/location/helpers.ts +++ b/packages/catalog-model/src/location/helpers.ts @@ -85,7 +85,7 @@ export function stringifyLocationReference(ref: { } /** - * Returns the source code location of the Entity, to the extend which one exists. + * Returns the source code location of the Entity, to the extent that one exists. * * If the returned location type is of type 'url', the target should be readable at least * using the UrlReader from @backstage/backend-common. If it is not of type 'url', the caller From f361f9e9dd1805aa27922aae835ced05d05a175b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 14 Apr 2021 10:46:47 +0200 Subject: [PATCH 54/58] docs: tweak docs for backstage.io/source-location Signed-off-by: Patrik Oldsberg --- docs/features/software-catalog/well-known-annotations.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 8ec4813799..0053c14b79 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -92,12 +92,14 @@ view and edit links need changing. # Example: metadata: annotations: - backstage.io/source-location: github:https://github.com/my-org/my-service + backstage.io/source-location: url:https://github.com/my-org/my-service/ ``` A `Location` reference that points to the source code of the entity (typically a `Component`). Useful when catalog files do not get ingested from the source code -repository itself. +repository itself. If the URL points to a folder, it is important that it is +suffixed with a `'/'` in order for relative path resolution to work +consistently. ### jenkins.io/github-folder From f4f9fd04aa5b7e9ce9105c83365f99e248651a0c Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 14 Apr 2021 11:47:01 +0200 Subject: [PATCH 55/58] bump(rjsf): bump core too Signed-off-by: blam --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ac4cf4c08e..f44faae442 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4222,9 +4222,9 @@ react-lifecycles-compat "^3.0.4" "@rjsf/core@^2.4.0": - version "2.4.0" - resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.4.0.tgz#c50bcff0d8178948ce08123177399d8816d51929" - integrity sha512-8zlydBkGldOxGXFEwNGFa1gzTxpcxaYn7ofegcu8XHJ7IKMCfpnU3ABg+H3eml1KZCX3FODmj1tHFJKuTmfynw== + version "2.5.1" + resolved "https://registry.npmjs.org/@rjsf/core/-/core-2.5.1.tgz#95a842d22bab5f83929662fcd73739108e9f5cbb" + integrity sha512-km8NYScXNONaL5BiSLS6wyDj49pOLZtn0iXg7Zxlm921uuf3o2AAX5SuZS5kB4Zj2zlrVMrXESexfX6bxdDYHw== dependencies: "@babel/runtime-corejs2" "^7.8.7" "@types/json-schema" "^7.0.4" From 7fd46f26dc264fc480ad207ec10cf00c9900807e Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 14 Apr 2021 14:03:17 +0200 Subject: [PATCH 56/58] Use `string` TypeScript type instead of `String` Signed-off-by: Oliver Sand --- .changeset/old-clouds-whisper.md | 5 +++++ .../KubernetesAuthTranslatorGenerator.ts | 2 +- .../src/service/KubernetesFanOutHandler.test.ts | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 .changeset/old-clouds-whisper.md diff --git a/.changeset/old-clouds-whisper.md b/.changeset/old-clouds-whisper.md new file mode 100644 index 0000000000..edaba13ad7 --- /dev/null +++ b/.changeset/old-clouds-whisper.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +Use `string` TypeScript type instead of `String`. diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts index 4ae2aff35a..d222bdbce1 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts @@ -21,7 +21,7 @@ import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator export class KubernetesAuthTranslatorGenerator { static getKubernetesAuthTranslatorInstance( - authProvider: String, + authProvider: string, ): KubernetesAuthTranslator { switch (authProvider) { case 'google': { diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index e76a114d54..7ec1a317d8 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -34,8 +34,8 @@ const mockFetch = (mock: jest.Mock) => { }; function generateMockResourcesAndErrors( - serviceId: String, - clusterName: String, + serviceId: string, + clusterName: string, ) { if (clusterName === 'empty-cluster') { return { From 442f34b87f96b1fefd3a57da9ceae7a36fcc4122 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 14 Apr 2021 14:06:09 +0200 Subject: [PATCH 57/58] Make sure the `CatalogClient` escapes URL parameters correctly Signed-off-by: Oliver Sand --- .changeset/tiny-games-hide.md | 5 ++++ packages/catalog-client/src/CatalogClient.ts | 24 ++++++++++++++++---- packages/catalog-client/src/types.ts | 2 +- plugins/catalog/src/CatalogClientWrapper.ts | 2 +- 4 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 .changeset/tiny-games-hide.md diff --git a/.changeset/tiny-games-hide.md b/.changeset/tiny-games-hide.md new file mode 100644 index 0000000000..2687174060 --- /dev/null +++ b/.changeset/tiny-games-hide.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-client': patch +--- + +Make sure the `CatalogClient` escapes URL parameters correctly. diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index 7426aabaac..3de25e9808 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -42,10 +42,14 @@ export class CatalogClient implements CatalogApi { } async getLocationById( - id: String, + id: string, options?: CatalogRequestOptions, ): Promise { - return await this.requestOptional('GET', `/locations/${id}`, options); + return await this.requestOptional( + 'GET', + `/locations/${encodeURIComponent(id)}`, + options, + ); } async getEntities( @@ -86,7 +90,9 @@ export class CatalogClient implements CatalogApi { const { kind, namespace = 'default', name } = compoundName; return this.requestOptional( 'GET', - `/entities/by-name/${kind}/${namespace}/${name}`, + `/entities/by-name/${encodeURIComponent(kind)}/${encodeURIComponent( + namespace, + )}/${encodeURIComponent(name)}`, options, ); } @@ -171,14 +177,22 @@ export class CatalogClient implements CatalogApi { id: string, options?: CatalogRequestOptions, ): Promise { - await this.requestIgnored('DELETE', `/locations/${id}`, options); + await this.requestIgnored( + 'DELETE', + `/locations/${encodeURIComponent(id)}`, + options, + ); } async removeEntityByUid( uid: string, options?: CatalogRequestOptions, ): Promise { - await this.requestIgnored('DELETE', `/entities/by-uid/${uid}`, options); + await this.requestIgnored( + 'DELETE', + `/entities/by-uid/${encodeURIComponent(uid)}`, + options, + ); } // diff --git a/packages/catalog-client/src/types.ts b/packages/catalog-client/src/types.ts index 04cbb2b689..0d25bf7483 100644 --- a/packages/catalog-client/src/types.ts +++ b/packages/catalog-client/src/types.ts @@ -46,7 +46,7 @@ export interface CatalogApi { // Locations getLocationById( - id: String, + id: string, options?: CatalogRequestOptions, ): Promise; getOriginLocationByEntity( diff --git a/plugins/catalog/src/CatalogClientWrapper.ts b/plugins/catalog/src/CatalogClientWrapper.ts index 32dff8a0a1..4966d9c13f 100644 --- a/plugins/catalog/src/CatalogClientWrapper.ts +++ b/plugins/catalog/src/CatalogClientWrapper.ts @@ -42,7 +42,7 @@ export class CatalogClientWrapper implements CatalogApi { } async getLocationById( - id: String, + id: string, options?: CatalogRequestOptions, ): Promise { return await this.client.getLocationById(id, { From fef852ecd3ccf2ab136342b3b38bca034c583814 Mon Sep 17 00:00:00 2001 From: Jonah Grimes Date: Wed, 14 Apr 2021 10:45:20 -0400 Subject: [PATCH 58/58] Reworked TechDocs page subtitle to reflect the company/organization name (#5317) * reworked page subtitle to reflect the company/organization name instead of 'Backstage' Signed-off-by: Jonah Grimes * ran prettier to reformat code Signed-off-by: Jonah Grimes * marked changeset as techdocs only Signed-off-by: Jonah Grimes --- .changeset/techdocs-chatty-impalas-compare.md | 6 +++++ .../src/home/components/TechDocsHome.test.tsx | 21 +++++++++++++++--- .../src/home/components/TechDocsHome.tsx | 22 +++++++++---------- 3 files changed, 34 insertions(+), 15 deletions(-) create mode 100644 .changeset/techdocs-chatty-impalas-compare.md diff --git a/.changeset/techdocs-chatty-impalas-compare.md b/.changeset/techdocs-chatty-impalas-compare.md new file mode 100644 index 0000000000..26e7fd571d --- /dev/null +++ b/.changeset/techdocs-chatty-impalas-compare.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Reworked the TechDocs plugin to support using the configured company name instead of +'Backstage' in the page title. diff --git a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsHome.test.tsx index 9783c98f27..8067767d39 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsHome.test.tsx @@ -14,7 +14,13 @@ * limitations under the License. */ -import { ApiProvider, ApiRegistry } from '@backstage/core'; +import { + ApiProvider, + ApiRegistry, + ConfigApi, + configApiRef, + ConfigReader, +} from '@backstage/core'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp } from '@backstage/test-utils'; import { screen } from '@testing-library/react'; @@ -26,7 +32,16 @@ describe('TechDocs Home', () => { getEntities: async () => ({ items: [] }), }; - const apiRegistry = ApiRegistry.with(catalogApiRef, catalogApi); + const configApi: ConfigApi = new ConfigReader({ + organization: { + name: 'My Company', + }, + }); + + const apiRegistry = ApiRegistry.with(catalogApiRef, catalogApi).with( + configApiRef, + configApi, + ); it('should render a TechDocs home page', async () => { await renderInTestApp( @@ -38,7 +53,7 @@ describe('TechDocs Home', () => { // Header expect(await screen.findByText('Documentation')).toBeInTheDocument(); expect( - await screen.findByText(/Documentation available in Backstage/i), + await screen.findByText(/Documentation available in My Company/i), ).toBeInTheDocument(); // Explore Content diff --git a/plugins/techdocs/src/home/components/TechDocsHome.tsx b/plugins/techdocs/src/home/components/TechDocsHome.tsx index 916a46ab7a..5e9e25e7a0 100644 --- a/plugins/techdocs/src/home/components/TechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsHome.tsx @@ -21,6 +21,8 @@ import { catalogApiRef, CatalogApi } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; import { CodeSnippet, + ConfigApi, + configApiRef, Content, Header, HeaderTabs, @@ -36,6 +38,7 @@ import { OwnedContent } from './OwnedContent'; export const TechDocsHome = () => { const [selectedTab, setSelectedTab] = useState(0); const catalogApi: CatalogApi = useApi(catalogApiRef); + const configApi: ConfigApi = useApi(configApiRef); const tabs = [{ label: 'Overview' }, { label: 'Owned Documents' }]; @@ -46,13 +49,14 @@ export const TechDocsHome = () => { }); }); + const generatedSubtitle = `Documentation available in ${ + configApi.getOptionalString('organization.name') ?? 'Backstage' + }`; + if (loading) { return ( -
+
@@ -63,10 +67,7 @@ export const TechDocsHome = () => { if (error) { return ( -
+
{ return ( -
+
setSelectedTab(index)}