diff --git a/.changeset/chatty-ghosts-lay.md b/.changeset/chatty-ghosts-lay.md new file mode 100644 index 0000000000..7d8ef3edbd --- /dev/null +++ b/.changeset/chatty-ghosts-lay.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-sonarqube': patch +--- + +Export isSonarQubeAvailable. diff --git a/.changeset/clever-walls-bow.md b/.changeset/clever-walls-bow.md new file mode 100644 index 0000000000..36e56f6e37 --- /dev/null +++ b/.changeset/clever-walls-bow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Add built-in publish action for creating GitHub pull requests. diff --git a/.changeset/fair-geese-yell.md b/.changeset/fair-geese-yell.md deleted file mode 100644 index 00c5b16854..0000000000 --- a/.changeset/fair-geese-yell.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/config-loader': minor ---- - -Fix bug where `$${...}` was not being escaped to `${...}` - -Add support for environment variable substitution in `$include`, `$file` and -`$env` transform values. - -- This change allows for including dynamic paths, such as environment specific - secrets by using the same environment variable substitution (`${..}`) already - supported outside of the various include transforms. -- If you are currently using the syntax `${...}` in your include transform values, - you will need to escape the substitution by using `$${...}` instead to maintain - the same behavior. diff --git a/.changeset/forty-ladybugs-move.md b/.changeset/forty-ladybugs-move.md deleted file mode 100644 index 254efea584..0000000000 --- a/.changeset/forty-ladybugs-move.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -When using OAuth2 authentication the name is now taken from the name property of the JWT instead of the email property diff --git a/.changeset/funny-donkeys-jam.md b/.changeset/funny-donkeys-jam.md deleted file mode 100644 index 0c45d4b5ff..0000000000 --- a/.changeset/funny-donkeys-jam.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-api': patch -'@backstage/core': patch ---- - -Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error. diff --git a/.changeset/little-starfishes-whisper.md b/.changeset/little-starfishes-whisper.md deleted file mode 100644 index ea00753b4f..0000000000 --- a/.changeset/little-starfishes-whisper.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Added `stringifyEntityRef`, which always creates a string representation of an entity reference. Also deprecated `serializeEntityRef`, as `stringifyEntityRef` should be used instead. diff --git a/.changeset/loud-apricots-fix.md b/.changeset/loud-apricots-fix.md new file mode 100644 index 0000000000..3763ebf0e3 --- /dev/null +++ b/.changeset/loud-apricots-fix.md @@ -0,0 +1,31 @@ +--- +'@backstage/create-app': patch +'@backstage/plugin-scaffolder': minor +--- + +Expose the catalog-import route as an external route from the scaffolder. + +This will make it possible to hide the "Register Existing Component" button +when you for example are running backstage with `catalog.readonly=true`. + +As a consequence of this change you need add a new binding to your createApp call to +keep the button visible. However, if you instead want to hide the button you can safely +ignore the following example. + +To bind the external route from the catalog-import plugin to the scaffolder template +index page, make sure you have the appropriate imports and add the following +to the createApp call: + +```typescript +import { catalogImportPlugin } from '@backstage/plugin-catalog-import'; + +const app = createApp({ + // ... + bindRoutes({ bind }) { + // ... + bind(scaffolderPlugin.externalRoutes, { + registerComponent: catalogImportPlugin.routes.importPage, + }); + }, +}); +``` diff --git a/.changeset/neat-lions-peel.md b/.changeset/neat-lions-peel.md deleted file mode 100644 index 1cd50641ec..0000000000 --- a/.changeset/neat-lions-peel.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-api': patch -'@backstage/core': patch ---- - -Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail. diff --git a/.changeset/polite-cameras-sin.md b/.changeset/polite-cameras-sin.md deleted file mode 100644 index b7989be2c6..0000000000 --- a/.changeset/polite-cameras-sin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/test-utils': patch ---- - -Remove unnecessary wrapping of elements rendered by `wrapInTestApp` and `renderInTestApp`, which was breaking mount discovery. diff --git a/.changeset/stale-geese-rule.md b/.changeset/stale-geese-rule.md deleted file mode 100644 index f22ca2983d..0000000000 --- a/.changeset/stale-geese-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Optimize data fetched for the `OwnershipCard`. diff --git a/.changeset/thirty-panthers-learn.md b/.changeset/thirty-panthers-learn.md deleted file mode 100644 index f0e2c05e81..0000000000 --- a/.changeset/thirty-panthers-learn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-github-deployments': patch ---- - -Add a button to reload the GitHub Deployments card diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index e339c256a4..b0bcf9c9f7 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -9,6 +9,7 @@ Blackbox Chai Changesets Chanwit +Cloudformation Codecov Codehilite Config diff --git a/app-config.yaml b/app-config.yaml index 465c7b0049..309fa28234 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -150,6 +150,10 @@ integrations: - host: dev.azure.com token: $env: AZURE_TOKEN +# googleGcs: +# clientEmail: 'example@example.com' +# privateKey: +# $env: GCS_PRIVATE_KEY catalog: rules: diff --git a/contrib/docs/tutorials/aws-deployment.md b/contrib/docs/tutorials/aws-deployment.md new file mode 100644 index 0000000000..5f94672b82 --- /dev/null +++ b/contrib/docs/tutorials/aws-deployment.md @@ -0,0 +1,195 @@ +# Deploying Backstage on AWS using ECR and EKS + +Backstage documentation shows how to build a [Docker +image](https://backstage.io/docs/getting-started/deployment-docker); this +tutorial shows how to deploy that Docker image to AWS using Elastic Container +Registry (ECR) and Elastic Kubernetes Service (EKS). Amazon also supports +deployments with Helm, covered in the [Helm +Kubernetes](../kubernetes/basic_kubernetes_example_with_helm) example. + +The basic workflow for this method is to build a Backstage Docker image, upload +the new version to a container registry, and update a Kubernetes deployment with +the new image. + +## Create a container registry + +To create an Elastic Container Registry on AWS, go to the [AWS ECR +console](https://console.aws.amazon.com/ecr/repositories). + +Click `Create repository` and give the repository a name, like `backstage`. + +## Add an ECR IAM user + +To push to this new ECR repository, there must be an IAM user with +`AmazonEC2ContainerRegistry` permission. + +Go to [AWS IAM console](https://console.aws.amazon.com/iam/home) and select +`Users`. + +1. Click `Add User` +2. Set the username to something like `ecr-publisher` and select `Programmatic access` only. +3. For `Permissions`, you can either create a new group or attach policies + directly to the user with `AmazonEC2ContainerRegistryFullAccess`. +4. Finish creating the user; an access key ID and secret access key will be + generated. + +## Publish a Backstage build + +Follow the [Docker +image](https://backstage.io/docs/getting-started/deployment-docker) +documentation to build a new Backstage Docker image: + +```shell +$ yarn build +$ docker image build . -f packages/backend/Dockerfile --tag backstage +``` + +This command builds a backend-only image, but you can similarly build a frontend +or combined Docker image. + +Next, configure the [AWS CLI](https://aws.amazon.com/cli/) to use the +`ecr-publisher` user you created: + +```shell +$ aws configure +AWS Access Key ID: +AWS Secret Access Key: +Default region name: +``` + +Now you can use the AWS CLI to push the built image to the ECR repository. It's +a good practice to use a specific version tag as well as `latest` when pushing; +more about [Semver tagging +here](https://medium.com/@mccode/using-semantic-versioning-for-docker-image-tags-dfde8be06699). + +Go to the [AWS ECR console](https://console.aws.amazon.com/ecr/repositories) and +click on your repository, then `View push commands`. This will show the +repository URL to push and some sample commands. + +```shell +# Copy from push command window +$ aws ecr get-login-password --region | docker login --username AWS --password-stdin +Login Succeeded + +$ docker tag backstage:latest /backstage:latest +$ docker push /backstage:latest + +$ docker tag backstage:latest /backstage:1.0.0 +$ docker push /backstage:1.0.0 +``` + +These will take a few minutes to upload the Docker images, then you can see +the image with `latest, 1.0.0` tags in the ECR repository. Now you can create a +Kubernetes deployment based on this image. + +## Deploy to an EKS cluster + +Creating an Elastic Kubernetes Service (EKS) cluster is beyond the scope of this +document, but it can be as easy as `eksctl create cluster` documented in the +[AWS EKS getting started +guide](https://docs.aws.amazon.com/eks/latest/userguide/getting-started-eksctl.html), +which uses a Cloudformation template to create the necessary resources. + +To deploy the Docker image to EKS, create a `kubernetes` folder in your +Backstage source folder and add a Kubernetes `deployment.yaml`: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backstage-backend + labels: + app: backstage-backend + namespace: default +spec: + replicas: 1 + selector: + matchLabels: + app: backstage-backend + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: backstage-backend + spec: + containers: + - image: /backstage:1.0.0 + imagePullPolicy: Always + name: backstage-backend + ports: + - containerPort: 7000 + protocol: TCP +``` + +Note the `image` key in the container spec referencing the ECR repository. + +Now create a simple `service.yaml` to map the container ports: + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: backstage-backend +spec: + selector: + app: backstage-backend + ports: + - protocol: TCP + port: 80 + targetPort: 7000 +``` + +Apply these Kubernetes definitions to the EKS cluster to complete the Backstage +deployment: + +```shell +$ kubectl apply -f deployment.yaml +$ kubectl apply -f service.yaml +``` + +Now you can see your Backstage workload running from the [EKS +console](https://console.aws.amazon.com/eks/home). + +## Further steps + +### Exposing Backstage with a load balancer + +Backstage users need to query the backend, which means we need to expose +the workload with a load balancer. Follow the [Application load balancing on +EKS](https://docs.aws.amazon.com/eks/latest/userguide/alb-ingress.html) guide to +set up a Load Balancer controller and Kubernetes ingress to your application. + +This is ultimately a `kubectl apply` with an ingress definition: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + namespace: default + name: backstage-ingress + annotations: + kubernetes.io/ingress.class: alb + alb.ingress.kubernetes.io/scheme: internet-facing + alb.ingress.kubernetes.io/target-type: ip +spec: + rules: + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: backstage-backend + port: + number: 80 +``` + +### Updating the deployment + +To update the Kubernetes deployment to a newly published version of your +Backstage Docker image, update the image tag reference in `deployment.yaml` and +then apply the changes to EKS with `kubectl apply -f deployment.yaml`. diff --git a/docs/auth/auth0/provider.md b/docs/auth/auth0/provider.md index 383cda2014..b546285c94 100644 --- a/docs/auth/auth0/provider.md +++ b/docs/auth/auth0/provider.md @@ -46,29 +46,5 @@ The Auth0 provider is a structure with three configuration keys: ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `auth0AuthApi` reference and -`SignInPage` component to `createApp` in `packages/app/src/App.tsx`: - -```diff -+ import { auth0AuthApiRef, SignInConfig, SignInPage } from '@backstage/core'; - -+ const auth0Provider: SignInConfig = { -+ id: 'auth0-auth-provider', -+ title: 'Auth0', -+ message: 'Sign in using Auth0', -+ apiRef: auth0AuthApiRef, -+}; -+ -const app = createApp({ - apis, - plugins: Object.values(plugins), -+ components: { -+ SignInPage: props => ( -+ -+ ), -+ }, - bindRoutes({ bind }) { -``` +`SignInPage` component as shown in +[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page). diff --git a/docs/auth/github/provider.md b/docs/auth/github/provider.md index 6e194f36f7..4b65c8c3c2 100644 --- a/docs/auth/github/provider.md +++ b/docs/auth/github/provider.md @@ -49,29 +49,5 @@ The GitHub provider is a structure with three configuration keys: ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `githubAuthApi` reference and -`SignInPage` component to `createApp` in `packages/app/src/App.tsx`: - -```diff -+ import { githubAuthApiRef, SignInConfig, SignInPage } from '@backstage/core'; - -+ const githubProvider: SignInConfig = { -+ id: 'github-auth-provider', -+ title: 'GitHub', -+ message: 'Sign in using GitHub', -+ apiRef: githubAuthApiRef, -+}; -+ -const app = createApp({ - apis, - plugins: Object.values(plugins), -+ components: { -+ SignInPage: props => ( -+ -+ ), -+ }, - bindRoutes({ bind }) { -``` +`SignInPage` component as shown in +[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page). diff --git a/docs/auth/gitlab/provider.md b/docs/auth/gitlab/provider.md index e9cfdfa98e..8e9c499dc4 100644 --- a/docs/auth/gitlab/provider.md +++ b/docs/auth/gitlab/provider.md @@ -48,29 +48,5 @@ The GitLab provider is a structure with three configuration keys: ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `gitlabAuthApi` reference and -`SignInPage` component to `createApp` in `packages/app/src/App.tsx`: - -```diff -+ import { gitlabAuthApiRef, SignInConfig, SignInPage } from '@backstage/core'; - -+ const gitlabProvider: SignInConfig = { -+ id: 'gitlab-auth-provider', -+ title: 'GitLab', -+ message: 'Sign in using GitLab', -+ apiRef: gitlabAuthApiRef, -+}; -+ -const app = createApp({ - apis, - plugins: Object.values(plugins), -+ components: { -+ SignInPage: props => ( -+ -+ ), -+ }, - bindRoutes({ bind }) { -``` +`SignInPage` component as shown in +[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page). diff --git a/docs/auth/google/provider.md b/docs/auth/google/provider.md index 2765b509f3..457224b2db 100644 --- a/docs/auth/google/provider.md +++ b/docs/auth/google/provider.md @@ -53,29 +53,5 @@ The Google provider is a structure with two configuration keys: ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `googleAuthApi` reference and -`SignInPage` component to `createApp` in `packages/app/src/App.tsx`: - -```diff -+ import { googleAuthApiRef, SignInConfig, SignInPage } from '@backstage/core'; - -+ const googleProvider: SignInConfig = { -+ id: 'google-auth-provider', -+ title: 'Google', -+ message: 'Sign in using Google', -+ apiRef: googleAuthApiRef, -+}; -+ -const app = createApp({ - apis, - plugins: Object.values(plugins), -+ components: { -+ SignInPage: props => ( -+ -+ ), -+ }, - bindRoutes({ bind }) { -``` +`SignInPage` component as shown in +[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page). diff --git a/docs/auth/index.md b/docs/auth/index.md index a8ddf17c6b..fc6ce33e2b 100644 --- a/docs/auth/index.md +++ b/docs/auth/index.md @@ -1,99 +1,120 @@ --- id: index -title: User Authentication and Authorization in Backstage -description: Documentation on User Authentication and Authorization in Backstage +title: Adding Authentication +description: How to add authentication to a Backstage application --- -## Summary +Authentication in Backstage identifies the user, and provides a way for plugins +to make requests on behalf of a user to third-party services. Backstage can have +zero (guest access), one, or many authentication providers. The default +`@backstage/create-app` template uses guest access for easy startup. -The purpose of the Auth APIs in Backstage is to identify the user, and to -provide a way for plugins to request access to 3rd party services on behalf of -the user (OAuth). This documentation focuses on the implementation of that -solution and how to extend it. For documentation on how to consume the Auth APIs -in a plugin, see [TODO](#TODO). +See [Using authentication and identity](using-auth.md) for tips on using +Backstage identity information in your app or plugins. -### Accessing Third Party Services +## Adding an authentication provider -The main pattern for talking to third party services in Backstage is -user-to-server requests, where short-lived OAuth Access Tokens are requested by -plugins to authenticate calls to external services. These calls can be made -either directly to the services or through a backend plugin or service. +Backstage comes with many common authentication providers in the core library: -By relying on user-to-server calls we keep the coupling between the frontend and -backend low, and provide a much lower barrier for plugins to make use of third -party services. This is in comparison to for example a session-based system, -where access tokens are stored server-side. Such a solution would require a much -deeper coupling between the auth backend plugin, its session storage, and other -backend plugins or separate services. A goal of Backstage is to make it as easy -as possible to create new plugins, and an auth solution based on user-to-server -OAuth helps in that regard. +- [Auth0](auth0/provider.md) +- [Azure](microsoft/provider.md) +- [GitHub](github/provider.md) +- [GitLab](gitlab/provider.md) +- [Google](google/provider.md) +- [Okta](okta/provider.md) +- [OneLogin](onelogin/provider.md) -The method with which frontend plugins request access to third party services is -through [Utility APIs](../api/utility-apis.md) for each service provider. For a -full list of providers, see the -[Utility API References](../reference/utility-apis/README.md). +These built-in providers handle the authentication flow for a particular service +including required scopes, callbacks, etc. These providers are each added to a +Backstage app in a similar way. -### Identity - WIP +### Adding provider configuration -> NOTE: Identity management and the `SignInPage` in Backstage is NOT a method -> for blocking access for unauthorized users, that either requires additional -> backend implementation or a separate service like Google's Identity-Aware -> Proxy. The identity system only serves to provide a personalized experience -> and access to a Backstage Identity Token, which can be passed to backend -> plugins. +Each built-in provider has a configuration block under the `auth` section of +`app-config.yaml`. For example, the GitHub provider: -Identity management is still work in progress, but there are already a couple of -pieces in place that can be used. +```yaml +auth: + environment: development + providers: + github: + development: + clientId: ${AUTH_GITHUB_CLIENT_ID} + clientSecret: ${AUTH_GITHUB_CLIENT_SECRET} +``` -#### Identity for Plugin Developers +See the documentation for a particular provider to see what configuration is +needed. -As a plugin developer, there are two main touchpoints for identities: the -`IdentityApi` exported by `@backstage/core` via the `identityApiRef`, and a not -yet existing middleware exported by `@backstage/backend-common`. +The `providers` key may have several authentication providers, if multiple +authentication methods are supported. Each provider may also have configuration +for different authentication environments (development, production, etc). This +allows a single auth backend to serve multiple environments, such as running a +local frontend against a deployed backend. The provider configuration matching +the local `auth.environment` setting will be selected. -The `IdentityApi` gives access to the signed-in user's identity in the frontend. -It provides access to the user's ID, lightweight profile information, and an ID -token used to make authenticated calls within Backstage. +### Adding the provider to the sign-in page -The middleware that will be provided by `@backstage/backend-common` allows -verification of Backstage ID tokens, and optionally loading additional -information about the user. The progress is tracked in -https://github.com/backstage/backstage/issues/1435. +After configuring an authentication provider, the `app` frontend package needs a +small update to show this provider as a login option. The `SignInPage` component +handles this, and takes either a `provider` or `providers` (array) prop of +`SignInConfig` definitions. -#### Identity for App Developers +These reference the [ApiRef](../reference/utility-apis/README.md) exported by +the provider. Again, an example using GitHub that can be adapted to any of the +built-in providers: -If you're setting up your own Backstage app, or want to add a new identity -provider, there are three touchpoints: the frontend auth APIs in -`@backstage/core-api`, the backend auth providers in `auth-backend`, and the -`SignInPage` component configured in the Backstage app via `createApp`. +```diff +# packages/app/src/App.tsx ++ import { githubAuthApiRef, SignInConfig, SignInPage } from '@backstage/core'; -The frontend APIs and backend providers are tightly coupled together for each -auth provider, and together they implement an e2e auth flow. Only some auth -providers also act as identity providers though. For example, at the moment of -writing, the Google Auth provider is able to act as a Backstage identity -provider, but the GitHub one can not. For an auth provider to also act as an -identity provider, it needs to implement the `BackstageIdentityApi` in the -frontend, and in the backend it needs to return a `BackstageIdentity` structure. ++ const githubProvider: SignInConfig = { ++ id: 'github-auth-provider', ++ title: 'GitHub', ++ message: 'Sign in using GitHub', ++ apiRef: githubAuthApiRef, ++}; ++ +const app = createApp({ + apis, + plugins: Object.values(plugins), ++ components: { ++ SignInPage: props => ( ++ ++ ), ++ }, + bindRoutes({ bind }) { +``` -It is up to each provider to implement the mapping between a provider identity -and the corresponding Backstage identity. That is currently still work in -progress, and as a stop-gap for example the Google provider returns the local -part of the user's email as the user ID. +To also allow unauthenticated guest access, use the `providers` prop for +`SignInPage`: -The final piece of the puzzle is the `SignInPage` component that can be -configured as part of the app. Without a sign-in page, Backstage will fall back -to a `guest` identity for all users, without any ID token. To enable sign-in, a -`SignInPage` needs to be configured, which in turn has to supply a user to the -app. The `@backstage/core` package provides a basic sign-in page that allows -both the user and the app developer to choose between a couple of different -sign-in methods, or to designate a single provider that may also be logged in to -automatically. +```diff +const app = createApp({ + apis, + plugins: Object.values(plugins), ++ components: { ++ SignInPage: props => ( ++ ++ ), ++ }, + bindRoutes({ bind }) { +``` -## Further Reading +## Adding a custom authentication provider -More details are provided in dedicated sections of the documentation. +There are generic authentication providers for OAuth2 and SAML. These can reduce +the amount of code needed to implement a custom authentication provider that +adheres to these standards. -- [OAuth](./oauth.md): Description of the generic OAuth flow implemented by the - [auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend). -- [Glossary](./glossary.md): Glossary of some common terms related to the auth - flows. +Backstage uses [Passport](http://www.passportjs.org/) under the hood, which has +a wide library of authentication strategies for different providers. See +[Add authentication provider](add-auth-provider.md) for details on adding a new +Passport-supported authentication method. diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md index 3aa9de0df1..b64f9b1902 100644 --- a/docs/auth/microsoft/provider.md +++ b/docs/auth/microsoft/provider.md @@ -49,29 +49,5 @@ The Microsoft provider is a structure with three configuration keys: ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `microsoftAuthApi` reference and -`SignInPage` component to `createApp` in `packages/app/src/App.tsx`: - -```diff -+ import { microsoftAuthApiRef, SignInConfig, SignInPage } from '@backstage/core'; - -+ const microsoftProvider: SignInConfig = { -+ id: 'microsoft-auth-provider', -+ title: 'Microsoft Azure', -+ message: 'Sign in using Azure', -+ apiRef: microsoftAuthApiRef, -+}; -+ -const app = createApp({ - apis, - plugins: Object.values(plugins), -+ components: { -+ SignInPage: props => ( -+ -+ ), -+ }, - bindRoutes({ bind }) { -``` +`SignInPage` component as shown in +[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page). diff --git a/docs/auth/okta/provider.md b/docs/auth/okta/provider.md index 091c5fffb6..da54a30add 100644 --- a/docs/auth/okta/provider.md +++ b/docs/auth/okta/provider.md @@ -62,29 +62,5 @@ The values referenced are found on the Application page on your Okta site. ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `oktaAuthApi` reference and -`SignInPage` component to `createApp` in `packages/app/src/App.tsx`: - -```diff -+ import { oktaAuthApiRef, SignInConfig, SignInPage } from '@backstage/core'; - -+ const oktaProvider: SignInConfig = { -+ id: 'okta-auth-provider', -+ title: 'Okta', -+ message: 'Sign in using Okta', -+ apiRef: oktaAuthApiRef, -+}; -+ -const app = createApp({ - apis, - plugins: Object.values(plugins), -+ components: { -+ SignInPage: props => ( -+ -+ ), -+ }, - bindRoutes({ bind }) { -``` +`SignInPage` component as shown in +[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page). diff --git a/docs/auth/onelogin/provider.md b/docs/auth/onelogin/provider.md new file mode 100644 index 0000000000..62c1657fea --- /dev/null +++ b/docs/auth/onelogin/provider.md @@ -0,0 +1,54 @@ +--- +id: provider +title: OneLogin Authentication Provider +sidebar_label: OneLogin +description: Adding OneLogin OIDC as an authentication provider in Backstage +--- + +The Backstage `core-api` package comes with a OneLogin authentication provider +that can authenticate users using OpenID Connect. + +## Create an Application on OneLogin + +To support OneLogin authentication, you must create an Application: + +1. From the OneLogin Admin portal, choose Applications +2. Click `Add App` and select `OpenID Connect` + - Display Name: Backstage (or your custom app name) +3. Click Save +4. Go to the Configuration tab for the Application and set: + - `Login Url`: `http://localhost:3000` + - `Redirect URIs`: `http://localhost:7000/api/auth/onelogin/handler/frame` +5. Click Save +6. Go to the SSO tab for the Application and set: + - `Token Endpoint` > `Authentication Method`: `POST` +7. Click Save + +## Configuration + +The provider configuration can then be added to your `app-config.yaml` under the +root `auth` configuration: + +```yaml +auth: + environment: development + providers: + onelogin: + development: + clientId: ${AUTH_ONELOGIN_CLIENT_ID} + clientSecret: ${AUTH_ONELOGIN_CLIENT_SECRET} + issuer: https://.onelogin.com/oidc/2 +``` + +The OneLogin provider is a structure with three configuration keys; **these are +found on the SSO tab** for the OneLogin Application: + +- `clientId`: The client ID +- `clientSecret`: The client secret +- `issuer`: The issuer URL + +## Adding the provider to the Backstage frontend + +To add the provider to the frontend, add the `oneloginAuthApi` reference and +`SignInPage` component as shown in +[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page). diff --git a/docs/auth/using-auth.md b/docs/auth/using-auth.md new file mode 100644 index 0000000000..fbc6017272 --- /dev/null +++ b/docs/auth/using-auth.md @@ -0,0 +1,96 @@ +--- +id: using-auth +title: Using authentication and identity +description: How to use authentication and identity in Backstage +--- + +The Auth APIs in Backstage identify the user, and provide a way for plugins to +request access to 3rd party services on behalf of the user (OAuth). This +documentation focuses on the implementation of that solution and how to extend +it. + +### Accessing Third Party Services + +The main pattern for talking to third party services in Backstage is +user-to-server requests, where short-lived OAuth Access Tokens are requested by +plugins to authenticate calls to external services. These calls can be made +either directly to the services or through a backend plugin or service. + +By relying on user-to-server calls we keep the coupling between the frontend and +backend low, and provide a much lower barrier for plugins to make use of third +party services. This is in comparison to for example a session-based system, +where access tokens are stored server-side. Such a solution would require a much +deeper coupling between the auth backend plugin, its session storage, and other +backend plugins or separate services. A goal of Backstage is to make it as easy +as possible to create new plugins, and an auth solution based on user-to-server +OAuth helps in that regard. + +The method with which frontend plugins request access to third party services is +through [Utility APIs](../api/utility-apis.md) for each service provider. For a +full list of providers, see the +[Utility API References](../reference/utility-apis/README.md). + +### Identity - WIP + +> NOTE: Identity management and the `SignInPage` in Backstage is NOT a method +> for blocking access for unauthorized users, that either requires additional +> backend implementation or a separate service like Google's Identity-Aware +> Proxy. The identity system only serves to provide a personalized experience +> and access to a Backstage Identity Token, which can be passed to backend +> plugins. + +Identity management is still work in progress, but there are already a couple of +pieces in place that can be used. + +#### Identity for Plugin Developers + +As a plugin developer, there are two main touchpoints for identities: the +`IdentityApi` exported by `@backstage/core` via the `identityApiRef`, and a not +yet existing middleware exported by `@backstage/backend-common`. + +The `IdentityApi` gives access to the signed-in user's identity in the frontend. +It provides access to the user's ID, lightweight profile information, and an ID +token used to make authenticated calls within Backstage. + +The middleware that will be provided by `@backstage/backend-common` allows +verification of Backstage ID tokens, and optionally loading additional +information about the user. The progress is tracked in +https://github.com/backstage/backstage/issues/1435. + +#### Identity for App Developers + +If you're setting up your own Backstage app, or want to add a new identity +provider, there are three touchpoints: the frontend auth APIs in +`@backstage/core-api`, the backend auth providers in `auth-backend`, and the +`SignInPage` component configured in the Backstage app via `createApp`. + +The frontend APIs and backend providers are tightly coupled together for each +auth provider, and together they implement an e2e auth flow. Only some auth +providers also act as identity providers though. For example, at the moment of +writing, the Google Auth provider is able to act as a Backstage identity +provider, but the GitHub one can not. For an auth provider to also act as an +identity provider, it needs to implement the `BackstageIdentityApi` in the +frontend, and in the backend it needs to return a `BackstageIdentity` structure. + +It is up to each provider to implement the mapping between a provider identity +and the corresponding Backstage identity. That is currently still work in +progress, and as a stop-gap for example the Google provider returns the local +part of the user's email as the user ID. + +The final piece of the puzzle is the `SignInPage` component that can be +configured as part of the app. Without a sign-in page, Backstage will fall back +to a `guest` identity for all users, without any ID token. To enable sign-in, a +`SignInPage` needs to be configured, which in turn has to supply a user to the +app. The `@backstage/core` package provides a basic sign-in page that allows +both the user and the app developer to choose between a couple of different +sign-in methods, or to designate a single provider that may also be logged in to +automatically. + +## Further Reading + +More details are provided in dedicated sections of the documentation. + +- [OAuth](./oauth.md): Description of the generic OAuth flow implemented by the + [auth-backend](https://github.com/backstage/backstage/tree/master/plugins/auth-backend). +- [Glossary](./glossary.md): Glossary of some common terms related to the auth + flows. diff --git a/docs/cli/commands.md b/docs/cli/commands.md index ca89a43a6a..f5e63824a7 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -190,10 +190,13 @@ output of `backstage-cli backend:bundle` into an image: FROM node:14-buster-slim WORKDIR /app -ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" -ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ +COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ +RUN tar xzf bundle.tar.gz && rm bundle.tar.gz CMD ["node", "packages/backend"] ``` diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md index 837a27e592..fefb20b095 100644 --- a/docs/getting-started/create-an-app.md +++ b/docs/getting-started/create-an-app.md @@ -106,6 +106,12 @@ The install process may also fail if no Python installation is available. Python is commonly available in most systems already, but if it isn't you can head for example [here](https://www.python.org/downloads/) to install it. +#### Could not execute command yarn install + +Install yarn on your system with `npm install --global yarn` or for more details +refer to the +[prerequisites](https://backstage.io/docs/getting-started/running-backstage-locally#prerequisites) + ## Run the app When the installation is complete you can open the app folder and start the app. diff --git a/docs/integrations/azure/locations.md b/docs/integrations/azure/locations.md index 4ce19bad4e..163674f00a 100644 --- a/docs/integrations/azure/locations.md +++ b/docs/integrations/azure/locations.md @@ -2,8 +2,8 @@ id: locations title: Azure DevOps Locations sidebar_label: Locations -description: - Integrating source code stored in Azure DevOps into the Backstage catalog +# prettier-ignore +description: Integrating source code stored in Azure DevOps into the Backstage catalog --- The Azure integration supports loading catalog entities from Azure DevOps. diff --git a/docs/integrations/bitbucket/discovery.md b/docs/integrations/bitbucket/discovery.md index b31071be7f..b24734abde 100644 --- a/docs/integrations/bitbucket/discovery.md +++ b/docs/integrations/bitbucket/discovery.md @@ -2,8 +2,8 @@ id: discovery title: Bitbucket Discovery sidebar_label: Discovery -description: - Automatically discovering catalog entities from repositories in Bitbucket +# prettier-ignore +description: Automatically discovering catalog entities from repositories in Bitbucket --- The Bitbucket integration has a special discovery processor for discovering diff --git a/docs/integrations/bitbucket/locations.md b/docs/integrations/bitbucket/locations.md index 588d8b3bcc..992a240290 100644 --- a/docs/integrations/bitbucket/locations.md +++ b/docs/integrations/bitbucket/locations.md @@ -2,8 +2,8 @@ id: locations title: Bitbucket Locations sidebar_label: Locations -description: - Integrating source code stored in Bitbucket into the Backstage catalog +# prettier-ignore +description: Integrating source code stored in Bitbucket into the Backstage catalog --- The Bitbucket integration supports loading catalog entities from bitbucket.org diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index fe52feab6e..a578d0893d 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -2,9 +2,8 @@ id: discovery title: GitHub Discovery sidebar_label: Discovery -description: - Automatically discovering catalog entities from repositories in a GitHub - organization +# prettier-ignore +description: Automatically discovering catalog entities from repositories in a GitHub organization --- The GitHub integration has a special discovery processor for discovering catalog diff --git a/docs/integrations/google-cloud-storage/locations.md b/docs/integrations/google-cloud-storage/locations.md new file mode 100644 index 0000000000..e3f6b72847 --- /dev/null +++ b/docs/integrations/google-cloud-storage/locations.md @@ -0,0 +1,57 @@ +--- +id: locations +sidebar_label: Locations +title: Google Cloud Storage Locations +# prettier-ignore +description: Setting up an integration with Google Cloud Storage +--- + +The Backstage catalog can import entities from a yaml file stored in a GCS +(Google Cloud Storage) bucket. To enable the ingestion of said entities the +`GoogleGcs` integration must be enabled first. + +## Configuration + +To configure the integration add the appropriate credentials to the Backstage +backend. There are two main ways to do this: by explicitly setting a +`clientEmail` and a `privateKey` or by letting the Google Storage SDK discover +the credentials automatically. + +### Explicit credentials + +Explicit credentials can be set in the following format: + +```yaml +integrations: + googleGcs: + clientEmail: + $env: GCS_CLIENT_EMAIL + privateKey: + $env: GCS_PRIVATE_KEY +``` + +Then make sure the environment variables `GCS_CLIENT_EMAIL` and +`GCS_PRIVATE_KEY` are set when you run Backstage. + +### Automatic discovery of Google credentials + +Since this integration uses the Google Storage SDK, you can also choose to not +provide any explicit credentials and let the SDK discover them automatically. + +One of these discovery methods is to provide an environment variable called +`GOOGLE_APPLICATION_CREDENTIALS` and set it to the file path of your JSON +service account key. + +For more details and methods to provide credentials to the Google Storage SDK +you can check [this documentation page][google gcs docs]. + +## Usage + +To use this integration to import entities from a GCS bucket go to the Google +console and browse the file you would like to import. Then copy the +`Authenticated URL` and paste it into the text box in the `register component` +form. This url should look like +`https://storage.cloud.google.com///catalog-info.yaml`. + +[google gcs docs]: + https://cloud.google.com/docs/authentication/production#auth-cloud-implicit-nodejs diff --git a/docs/integrations/index.md b/docs/integrations/index.md index 6c12aa8666..94f18daa09 100644 --- a/docs/integrations/index.md +++ b/docs/integrations/index.md @@ -2,9 +2,8 @@ id: index title: Integrations sidebar_label: Overview -description: - Configuring Backstage to read or publish data with external providers using - integrations +# prettier-ignore +description: Configuring Backstage to read or publish data with external providers using integrations --- Integrations allow Backstage to read or publish data using external providers diff --git a/docs/tutorials/switching-sqlite-postgres.md b/docs/tutorials/switching-sqlite-postgres.md index dfb82ddc51..72e5589baf 100644 --- a/docs/tutorials/switching-sqlite-postgres.md +++ b/docs/tutorials/switching-sqlite-postgres.md @@ -1,8 +1,8 @@ --- id: switching-sqlite-postgres title: Switching Backstage from SQLite to PostgreSQL -description: - How to get ready for deploying Backstage to production with PostgreSQL +# prettier-ignore +description: How to get ready for deploying Backstage to production with PostgreSQL --- The default `@backstage/create-app` database is SQLite, an in-memory database diff --git a/microsite/sidebars.json b/microsite/sidebars.json index fd21726271..64ff108a78 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -142,6 +142,11 @@ "type": "subcategory", "label": "LDAP", "ids": ["integrations/ldap/org"] + }, + { + "type": "subcategory", + "label": "Google GCS", + "ids": ["integrations/google-cloud-storage/locations"] } ], "Plugins": [ @@ -186,7 +191,6 @@ ], "Auth and identity": [ "auth/index", - "auth/add-auth-provider", { "type": "subcategory", "label": "Included providers", @@ -196,13 +200,16 @@ "auth/github/provider", "auth/gitlab/provider", "auth/google/provider", - "auth/okta/provider" + "auth/okta/provider", + "auth/onelogin/provider" ] }, + "auth/add-auth-provider", + "auth/using-auth", "auth/auth-backend", "auth/oauth", - "auth/glossary", - "auth/auth-backend-classes" + "auth/auth-backend-classes", + "auth/glossary" ], "Designing for Backstage": [ "dls/design", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 00cf81a903..5b96841e65 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,29 @@ # example-app +## 0.2.23 + +### Patch Changes + +- Updated dependencies [d0b4ebf22] +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [97b60de98] +- Updated dependencies [3f96a9d5a] +- Updated dependencies [b051e770c] +- Updated dependencies [f9c75f7a9] +- Updated dependencies [98dd5da71] +- Updated dependencies [97d53f686] +- Updated dependencies [64d2ce700] + - @backstage/plugin-badges@0.2.0 + - @backstage/core@0.7.4 + - @backstage/catalog-model@0.7.6 + - @backstage/plugin-scaffolder@0.8.2 + - @backstage/plugin-catalog-import@0.5.2 + - @backstage/plugin-catalog@0.5.3 + - @backstage/plugin-org@0.3.12 + - @backstage/plugin-github-deployments@0.1.2 + - @backstage/cli@0.6.7 + ## 0.2.21 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index e5dbbaa5ff..2bac2e6e09 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,17 +1,17 @@ { "name": "example-app", - "version": "0.2.21", + "version": "0.2.23", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.7.4", - "@backstage/cli": "^0.6.6", - "@backstage/core": "^0.7.3", + "@backstage/catalog-model": "^0.7.6", + "@backstage/cli": "^0.6.7", + "@backstage/core": "^0.7.4", "@backstage/integration-react": "^0.1.1", "@backstage/plugin-api-docs": "^0.4.9", - "@backstage/plugin-badges": "^0.1.2", - "@backstage/plugin-catalog": "^0.5.1", - "@backstage/plugin-catalog-import": "^0.5.0", + "@backstage/plugin-badges": "^0.2.0", + "@backstage/plugin-catalog": "^0.5.3", + "@backstage/plugin-catalog-import": "^0.5.2", "@backstage/plugin-catalog-react": "^0.1.3", "@backstage/plugin-circleci": "^0.2.12", "@backstage/plugin-cloudbuild": "^0.2.13", @@ -19,7 +19,7 @@ "@backstage/plugin-explore": "^0.3.2", "@backstage/plugin-gcp-projects": "^0.2.5", "@backstage/plugin-github-actions": "^0.4.2", - "@backstage/plugin-github-deployments": "^0.1.1", + "@backstage/plugin-github-deployments": "^0.1.2", "@backstage/plugin-gitops-profiles": "^0.2.6", "@backstage/plugin-graphiql": "^0.2.9", "@backstage/plugin-jenkins": "^0.4.1", @@ -27,11 +27,11 @@ "@backstage/plugin-kubernetes": "^0.4.2", "@backstage/plugin-lighthouse": "^0.2.14", "@backstage/plugin-newrelic": "^0.2.6", - "@backstage/plugin-org": "^0.3.10", + "@backstage/plugin-org": "^0.3.12", "@backstage/plugin-pagerduty": "0.3.2", "@backstage/plugin-register-component": "^0.2.12", "@backstage/plugin-rollbar": "^0.3.3", - "@backstage/plugin-scaffolder": "^0.8.0", + "@backstage/plugin-scaffolder": "^0.8.2", "@backstage/plugin-search": "^0.3.4", "@backstage/plugin-sentry": "^0.3.8", "@backstage/plugin-tech-radar": "^0.3.8", @@ -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.1.3", - "@roadiehq/backstage-plugin-github-insights": "^0.3.2", - "@roadiehq/backstage-plugin-github-pull-requests": "^0.7.6", - "@roadiehq/backstage-plugin-travis-ci": "^0.4.5", + "@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", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", @@ -57,7 +57,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/test-utils": "^0.1.9", + "@backstage/test-utils": "^0.1.10", "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index defa70dbb7..c5e5033973 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -27,7 +27,11 @@ import { CatalogIndexPage, catalogPlugin, } from '@backstage/plugin-catalog'; -import { CatalogImportPage } from '@backstage/plugin-catalog-import'; + +import { + CatalogImportPage, + catalogImportPlugin, +} from '@backstage/plugin-catalog-import'; import { CostInsightsLabelDataflowInstructionsPage, CostInsightsPage, @@ -48,8 +52,8 @@ import React from 'react'; import { hot } from 'react-hot-loader/root'; import { Navigate, Route } from 'react-router'; import { apis } from './apis'; -import { EntityPage } from './components/catalog/EntityPage'; import { Root } from './components/Root'; +import { entityPage } from './components/catalog/EntityPage'; import { providers } from './identityProviders'; import * as plugins from './plugins'; @@ -82,6 +86,9 @@ const app = createApp({ bind(explorePlugin.externalRoutes, { catalogEntity: catalogPlugin.routes.catalogEntity, }); + bind(scaffolderPlugin.externalRoutes, { + registerComponent: catalogImportPlugin.routes.importPage, + }); }, }); @@ -96,7 +103,7 @@ const routes = ( path="/catalog/:namespace/:kind/:name" element={} > - + {entityPage} } /> } /> diff --git a/packages/app/src/components/catalog/EntityPage.test.tsx b/packages/app/src/components/catalog/EntityPage.test.tsx index 1392f77a6d..9860792ea0 100644 --- a/packages/app/src/components/catalog/EntityPage.test.tsx +++ b/packages/app/src/components/catalog/EntityPage.test.tsx @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; -import { CICDSwitcher } from './EntityPage'; -import { UrlPatternDiscovery, ApiProvider, ApiRegistry } from '@backstage/core'; -import { - buildKiteApiRef, - BuildkiteApi, -} from '@roadiehq/backstage-plugin-buildkite'; -import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; +import { ApiProvider, ApiRegistry } from '@backstage/core'; +import { EntityLayout } from '@backstage/plugin-catalog'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { cicdContent } from './EntityPage'; +import { githubActionsApiRef } from '@backstage/plugin-github-actions'; describe('EntityPage Test', () => { const entity = { @@ -29,7 +29,7 @@ describe('EntityPage Test', () => { metadata: { name: 'ExampleComponent', annotations: { - 'buildkite.com/project-slug': 'exampleProject/examplePipeline', + 'github.com/project-slug': 'example/project', }, }, spec: { @@ -39,24 +39,36 @@ describe('EntityPage Test', () => { }, }; - const discoveryApi = UrlPatternDiscovery.compile('http://exampleapi.com'); + const mockedApi = { + listWorkflowRuns: jest.fn().mockResolvedValue([]), + getWorkflow: jest.fn(), + getWorkflowRun: jest.fn(), + reRunWorkflow: jest.fn(), + downloadJobLogsForWorkflowRun: jest.fn(), + } as jest.Mocked; - const apis = ApiRegistry.from([ - [buildKiteApiRef, new BuildkiteApi({ discoveryApi })], - ]); + const apis = ApiRegistry.with(githubActionsApiRef, mockedApi); - describe('CICDSwitcher Test', () => { - it('Should render Buildkite View', async () => { - const renderedComponent = await renderWithEffects( - wrapInTestApp( - - - , - ), + describe('cicdContent', () => { + it('Should render GitHub Actions View', async () => { + const rendered = await renderInTestApp( + + + + + {cicdContent} + + + + , ); - expect( - renderedComponent.getByText(/exampleProject\/examplePipeline/), - ).toBeInTheDocument(); + + expect(rendered.getByText('ExampleComponent')).toBeInTheDocument(); + + await expect( + rendered.findByText('No Workflow Data'), + ).resolves.toBeInTheDocument(); + expect(rendered.getByText('Create new Workflow')).toBeInTheDocument(); }); }); }); diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 5dc4f4c570..d8ee62a12c 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -14,174 +14,96 @@ * limitations under the License. */ -import { - ApiEntity, - DomainEntity, - Entity, - GroupEntity, - SystemEntity, - UserEntity, -} from '@backstage/catalog-model'; +import React, { ReactNode, useMemo, useState } from 'react'; +import BadgeIcon from '@material-ui/icons/CallToAction'; import { EmptyState } from '@backstage/core'; import { - ApiDefinitionCard, - ConsumedApisCard, - ConsumingComponentsCard, + EntityApiDefinitionCard, + EntityConsumingComponentsCard, EntityHasApisCard, - ProvidedApisCard, - ProvidingComponentsCard, + EntityProvidingComponentsCard, + EntityProvidedApisCard, + EntityConsumedApisCard, } from '@backstage/plugin-api-docs'; import { EntityBadgesDialog } from '@backstage/plugin-badges'; import { - AboutCard, + EntityAboutCard, EntityHasComponentsCard, EntityHasSubcomponentsCard, EntityHasSystemsCard, + EntityLayout, EntityLinksCard, - EntityPageLayout, EntitySystemDiagramCard, + EntitySwitch, + isComponentType, + isKind, } from '@backstage/plugin-catalog'; -import { EntityProvider, useEntity } from '@backstage/plugin-catalog-react'; import { - isPluginApplicableToEntity as isCircleCIAvailable, - Router as CircleCIRouter, + EntityCircleCIContent, + isCircleCIAvailable, } from '@backstage/plugin-circleci'; import { - isPluginApplicableToEntity as isCloudbuildAvailable, - Router as CloudbuildRouter, + EntityCloudbuildContent, + isCloudbuildAvailable, } from '@backstage/plugin-cloudbuild'; import { - isPluginApplicableToEntity as isGitHubActionsAvailable, - RecentWorkflowRunsCard, - Router as GitHubActionsRouter, + EntityGithubActionsContent, + EntityRecentGithubActionsRunsCard, + isGithubActionsAvailable, } from '@backstage/plugin-github-actions'; import { - isPluginApplicableToEntity as isJenkinsAvailable, - LatestRunCard as JenkinsLatestRunCard, - Router as JenkinsRouter, + EntityJenkinsContent, + EntityLatestJenkinsRunCard, + isJenkinsAvailable, } from '@backstage/plugin-jenkins'; -import { Router as KafkaRouter } from '@backstage/plugin-kafka'; -import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes'; +import { EntityKafkaContent } from '@backstage/plugin-kafka'; +import { EntityKubernetesContent } from '@backstage/plugin-kubernetes'; import { - EmbeddedRouter as LighthouseRouter, - isPluginApplicableToEntity as isLighthouseAvailable, - LastLighthouseAuditCard, + EntityLastLighthouseAuditCard, + EntityLighthouseContent, + isLighthouseAvailable, } from '@backstage/plugin-lighthouse'; import { - GroupProfileCard, - MembersListCard, - OwnershipCard, - UserProfileCard, + EntityGroupProfileCard, + EntityMembersListCard, + EntityOwnershipCard, + EntityUserProfileCard, } from '@backstage/plugin-org'; import { - isPluginApplicableToEntity as isPagerDutyAvailable, - PagerDutyCard, + EntityPagerDutyCard, + isPagerDutyAvailable, } from '@backstage/plugin-pagerduty'; import { + EntityRollbarContent, isRollbarAvailable, - Router as RollbarRouter, } from '@backstage/plugin-rollbar'; -import { Router as SentryRouter } from '@backstage/plugin-sentry'; -import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; +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 { - isPluginApplicableToEntity as isBuildkiteAvailable, - Router as BuildkiteRouter, -} from '@roadiehq/backstage-plugin-buildkite'; -import { - isPluginApplicableToEntity as isGitHubAvailable, - LanguagesCard, - ReadMeCard, - ReleasesCard, - Router as GitHubInsightsRouter, -} from '@roadiehq/backstage-plugin-github-insights'; -import { - isPluginApplicableToEntity as isPullRequestsAvailable, - PullRequestsStatsCard, - Router as PullRequestsRouter, -} from '@roadiehq/backstage-plugin-github-pull-requests'; -import { - isPluginApplicableToEntity as isTravisCIAvailable, - RecentTravisCIBuildsWidget, - Router as TravisCIRouter, -} from '@roadiehq/backstage-plugin-travis-ci'; -import React, { ReactNode, useMemo, useState } from 'react'; -import BadgeIcon from '@material-ui/icons/CallToAction'; +// 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'; -export const CICDSwitcher = ({ entity }: { entity: Entity }) => { - // This component is just an example of how you can implement your company's logic in entity page. - // You can for example enforce that all components of type 'service' should use GitHubActions - switch (true) { - case isJenkinsAvailable(entity): - return ; - case isBuildkiteAvailable(entity): - return ; - case isCircleCIAvailable(entity): - return ; - case isCloudbuildAvailable(entity): - return ; - case isTravisCIAvailable(entity): - return ; - case isGitHubActionsAvailable(entity): - return ; - default: - return ( - - Read more - - } - /> - ); - } -}; - -const RecentCICDRunsSwitcher = ({ entity }: { entity: Entity }) => { - let content: ReactNode; - switch (true) { - case isJenkinsAvailable(entity): - content = ; - break; - case isTravisCIAvailable(entity): - content = ; - break; - case isGitHubActionsAvailable(entity): - content = ( - - ); - break; - default: - content = null; - } - if (!content) { - return null; - } - return ( - - {content} - - ); -}; - -export const ErrorsSwitcher = ({ entity }: { entity: Entity }) => { - switch (true) { - case isRollbarAvailable(entity): - return ; - default: - return ; - } -}; - -const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => { +const EntityLayoutWrapper = (props: { children?: ReactNode }) => { const [badgesDialogOpen, setBadgesDialogOpen] = useState(false); const extraMenuItems = useMemo(() => { @@ -196,9 +118,9 @@ const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => { return ( <> - + {props.children} - + setBadgesDialogOpen(false)} @@ -207,350 +129,367 @@ const EntityPageLayoutWrapper = (props: { children?: React.ReactNode }) => { ); }; -const ComponentOverviewContent = ({ entity }: { entity: Entity }) => ( +export const cicdContent = ( + + + + + + {/* + + */} + + + + + + + + + + {/* + + */} + + + + + + + + Read more + + } + /> + + +); + +const cicdCard = ( + + + + + + + + {/* + + + + */} + + + + + + + +); + +const errorsContent = ( + + + + + + + + + +); + +const overviewContent = ( - - + + - {isPagerDutyAvailable(entity) && ( - - - - - - )} - - + + + + + + + + + + + - - {isGitHubAvailable(entity) && ( - <> - - - + + {cicdCard} + + {/* + Boolean(isGithubInsightsAvailable(e))}> + + + - - + + - - )} - {isLighthouseAvailable(entity) && ( - - - - )} - {isPullRequestsAvailable(entity) && ( - - - - )} - + + */} + + + + + + + + + + {/* + Boolean(isGithubPullRequestsAvailable(e))}> + + + + + */} + + ); -const ComponentApisContent = ({ entity }: { entity: Entity }) => ( - - - - - - - - -); +const serviceEntityPage = ( + + + {overviewContent} + -const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - -); + + {cicdContent} + -const WebsiteEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - } - /> - -); + + {errorsContent} + -const DefaultEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - -); - -export const ComponentEntityPage = ({ entity }: { entity: Entity }) => { - switch (entity?.spec?.type) { - case 'service': - return ; - case 'website': - return ; - default: - return ; - } -}; - -const ApiOverviewContent = ({ entity }: { entity: Entity }) => ( - - - - - - - + + + + + + + + - - + + + + + + + + + + + {/* + + */} + + {/* + + */} + + + + + + + + + +); + +const websiteEntityPage = ( + + + {overviewContent} + + + + {cicdContent} + + + + + + + + {errorsContent} + + + + + + + + + + + {/* + + */} + + {/* + + */} + + + + + +); + +const defaultEntityPage = ( + + + {overviewContent} + + + + + + + + + + +); + +const componentPage = ( + + + {serviceEntityPage} + + + + {websiteEntityPage} + + + {defaultEntityPage} + +); + +const apiPage = ( + + + + + + + + + + + + + + - - + + + + + + + + + + ); -const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => ( - - - - - +const userPage = ( + + + + + + + + + + + + ); -const ApiEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - +const groupPage = ( + + + + + + + + + + + + + + + + + + + ); -const UserOverviewContent = ({ entity }: { entity: UserEntity }) => ( - - - - - - - - +const systemPage = ( + + + + + + + + + + + + + + + ); -const UserEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - +const domainPage = ( + + + + + + + + + + + + ); -const GroupOverviewContent = ({ entity }: { entity: GroupEntity }) => ( - - - - - - - - - - - +export const entityPage = ( + + + + + + + + + {defaultEntityPage} + ); - -const GroupEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -const SystemOverviewContent = ({ entity }: { entity: SystemEntity }) => ( - - - - - - - - - - - -); - -const SystemEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - -); - -const DomainOverviewContent = ({ entity }: { entity: DomainEntity }) => ( - - - - - - - - -); - -const DomainEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -export const EntityPage = () => { - const { entity } = useEntity(); - - switch (entity?.kind?.toLocaleLowerCase('en-US')) { - case 'component': - return ; - case 'api': - return ; - case 'group': - return ; - case 'user': - return ; - case 'system': - return ; - case 'domain': - return ; - case 'location': - case 'resource': - case 'template': - default: - return ; - } -}; diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index dfe057da53..c194f7e666 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-common +## 0.6.2 + +### Patch Changes + +- b779b5fee: Add UrlReader for Google Cloud Storage +- Updated dependencies [82c66b8cd] + - @backstage/config-loader@0.6.0 + ## 0.6.1 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b860336d31..41fa657818 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.6.1", + "version": "0.6.2", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -31,9 +31,10 @@ "dependencies": { "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.4", - "@backstage/config-loader": "^0.5.1", + "@backstage/config-loader": "^0.6.0", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.1", + "@google-cloud/storage": "^5.8.0", "@octokit/rest": "^18.0.12", "@types/cors": "^2.8.6", "@types/dockerode": "^3.2.1", @@ -56,6 +57,7 @@ "minimatch": "^3.0.4", "minimist": "^1.2.5", "morgan": "^1.10.0", + "raw-body": "^2.4.1", "selfsigned": "^1.10.7", "stoppable": "^1.1.0", "tar": "^6.0.5", @@ -71,8 +73,8 @@ } }, "devDependencies": { - "@backstage/cli": "^0.6.5", - "@backstage/test-utils": "^0.1.9", + "@backstage/cli": "^0.6.7", + "@backstage/test-utils": "^0.1.10", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", "@types/concat-stream": "^1.6.0", diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts new file mode 100644 index 0000000000..e201361a91 --- /dev/null +++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright 2020 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 { ConfigReader, JsonObject } from '@backstage/config'; +import { getVoidLogger } from '../logging'; +import { ReadTreeResponseFactory } from './tree'; +import { GoogleGcsUrlReader } from './GoogleGcsUrlReader'; +import { UrlReaderPredicateTuple } from './types'; + +describe('GcsUrlReader', () => { + const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { + return GoogleGcsUrlReader.factory({ + config: new ConfigReader(config), + logger: getVoidLogger(), + treeResponseFactory: ReadTreeResponseFactory.create({ + config: new ConfigReader({}), + }), + }); + }; + + it('does not create a reader without the googleGcs field', () => { + const entries = createReader({ + integrations: {}, + }); + expect(entries).toHaveLength(0); + }); + + it('creates a reader with credentials correctly configured', () => { + const entries = createReader({ + integrations: { + googleGcs: { + privateKey: '--- BEGIN KEY ---- fakekey --- END KEY ---', + clientEmail: 'someone@example.com', + }, + }, + }); + expect(entries).toHaveLength(1); + }); + + it('creates a reader with default credentials provider', () => { + const entries = createReader({ + integrations: { + googleGcs: {}, + }, + }); + expect(entries).toHaveLength(1); + }); + + describe('predicates', () => { + const readers = createReader({ + integrations: { + googleGcs: {}, + }, + }); + const predicate = readers[0].predicate; + + it('returns true for the correct google cloud storage host', () => { + expect(predicate(new URL('https://storage.cloud.google.com'))).toBe(true); + }); + it('returns true for a url with the full path and the correct host', () => { + expect( + predicate( + new URL( + 'https://storage.cloud.google.com/team1/service1/catalog-info.yaml', + ), + ), + ).toBe(true); + }); + it('returns false for the wrong hostname under cloud.google.com', () => { + expect(predicate(new URL('https://storage2.cloud.google.com'))).toBe( + false, + ); + }); + it('returns false for a partially correct host', () => { + expect(predicate(new URL('https://cloud.google.com'))).toBe(false); + }); + it('returns false for a completely different host', () => { + expect(predicate(new URL('https://a.example.com/test'))).toBe(false); + }); + }); +}); diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts new file mode 100644 index 0000000000..4ea24fa22f --- /dev/null +++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.ts @@ -0,0 +1,105 @@ +/* + * Copyright 2020 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 { Storage } from '@google-cloud/storage'; +import { + ReaderFactory, + ReadTreeResponse, + SearchResponse, + UrlReader, +} from './types'; +import getRawBody from 'raw-body'; +import { + GoogleGcsIntegrationConfig, + readGoogleGcsIntegrationConfig, +} from '@backstage/integration'; + +const GOOGLE_GCS_HOST = 'storage.cloud.google.com'; + +const parseURL = ( + url: string, +): { host: string; bucket: string; key: string } => { + const { host, pathname } = new URL(url); + + if (host !== GOOGLE_GCS_HOST) { + throw new Error(`not a valid GCS URL: ${url}`); + } + + const [, bucket, ...key] = pathname.split('/'); + return { + host: host, + bucket, + key: key.join('/'), + }; +}; + +export class GoogleGcsUrlReader implements UrlReader { + static factory: ReaderFactory = ({ config, logger }) => { + if (!config.has('integrations.googleGcs')) { + return []; + } + const gcsConfig = readGoogleGcsIntegrationConfig( + config.getConfig('integrations.googleGcs'), + ); + let storage: Storage; + if (!gcsConfig.clientEmail || !gcsConfig.privateKey) { + logger.info( + 'googleGcs credentials not found in config. Using default credentials provider.', + ); + storage = new Storage(); + } else { + storage = new Storage({ + credentials: { + client_email: gcsConfig.clientEmail || undefined, + private_key: gcsConfig.privateKey || undefined, + }, + }); + } + const reader = new GoogleGcsUrlReader(gcsConfig, storage); + const predicate = (url: URL) => url.host === GOOGLE_GCS_HOST; + return [{ reader, predicate }]; + }; + + constructor( + private readonly integration: GoogleGcsIntegrationConfig, + private readonly storage: Storage, + ) {} + + async read(url: string): Promise { + try { + const { bucket, key } = parseURL(url); + + return await getRawBody( + this.storage.bucket(bucket).file(key).createReadStream(), + ); + } catch (error) { + throw new Error(`unable to read gcs file from ${url}, ${error}`); + } + } + + async readTree(): Promise { + throw new Error('GcsUrlReader does not implement readTree'); + } + + async search(): Promise { + throw new Error('GcsUrlReader does not implement search'); + } + + toString() { + const key = this.integration.privateKey; + return `googleGcs{host=${GOOGLE_GCS_HOST},authed=${Boolean(key)}}`; + } +} diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index 2f233463bb..06c6fdcdcf 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -24,6 +24,7 @@ import { GithubUrlReader } from './GithubUrlReader'; import { GitlabUrlReader } from './GitlabUrlReader'; import { ReadTreeResponseFactory } from './tree'; import { FetchUrlReader } from './FetchUrlReader'; +import { GoogleGcsUrlReader } from './GoogleGcsUrlReader'; type CreateOptions = { /** Root config object */ @@ -70,6 +71,7 @@ export class UrlReaders { BitbucketUrlReader.factory, GithubUrlReader.factory, GitlabUrlReader.factory, + GoogleGcsUrlReader.factory, FetchUrlReader.factory, ]), }); diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index acef405c8a..02aab45609 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -16,11 +16,13 @@ WORKDIR /app # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. # The skeleton contains the package.json of each package in the monorepo, # and along with yarn.lock and the root package.json, that's enough to run yarn install. -ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" # Then copy the rest of the backend bundle, along with any other files we might want. ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ +RUN tar xzf bundle.tar.gz && rm bundle.tar.gz CMD ["node", "packages/backend", "--config", "app-config.yaml"] diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 0ad5d6993f..71a0ba6676 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/catalog-model +## 0.7.6 + +### Patch Changes + +- 97b60de98: Added `stringifyEntityRef`, which always creates a string representation of an entity reference. Also deprecated `serializeEntityRef`, as `stringifyEntityRef` should be used instead. +- 98dd5da71: Add support for multiple links to post-scaffold task summary page + ## 0.7.5 ### Patch Changes diff --git a/packages/catalog-model/examples/apis/spotify-api.yaml b/packages/catalog-model/examples/apis/spotify-api.yaml index 0b8e80b86e..1110627473 100644 --- a/packages/catalog-model/examples/apis/spotify-api.yaml +++ b/packages/catalog-model/examples/apis/spotify-api.yaml @@ -14,4 +14,4 @@ spec: lifecycle: production owner: team-a definition: - $text: https://github.com/APIs-guru/openapi-directory/blob/master/APIs/spotify.com/v1/swagger.yaml + $text: https://github.com/APIs-guru/openapi-directory/blob/dab6854d4d599aafb0eb36e6c7ae1fe0c37509b7/APIs/spotify.com/2021.4.2/openapi.yaml diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 1e5239cc23..680c73e905 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.7.5", + "version": "0.7.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,7 +39,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.4", + "@backstage/cli": "^0.6.7", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json b/packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json index bd9999f8bd..ba3ccb9d1d 100644 --- a/packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json +++ b/packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json @@ -135,6 +135,36 @@ "output": { "type": "object", "description": "A templated object describing the outputs of the scaffolding task.", + "properties": { + "links": { + "type": "array", + "description": "A list of external hyperlinks, typically pointing to resources created or updated by the template", + "items": { + "type": "object", + "required": ["url"], + "properties": { + "url": { + "type": "string", + "description": "A url in a standard uri format.", + "examples": ["https://github.com/my-org/my-new-repo"], + "minLength": 1 + }, + "title": { + "type": "string", + "description": "A user friendly display name for the link.", + "examples": ["View new repo"], + "minLength": 1 + }, + "icon": { + "type": "string", + "description": "A key representing a visual icon to be displayed in the UI.", + "examples": ["dashboard"], + "minLength": 1 + } + } + } + } + }, "additionalProperties": { "type": "string" } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index c29669f31c..f9dc749009 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/cli +## 0.6.7 + +### Patch Changes + +- Updated dependencies [82c66b8cd] + - @backstage/config-loader@0.6.0 + ## 0.6.6 ### Patch Changes diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json index a55b215f74..f6b4f65edd 100644 --- a/packages/cli/config/tsconfig.json +++ b/packages/cli/config/tsconfig.json @@ -11,7 +11,7 @@ "incremental": true, "isolatedModules": true, "jsx": "react", - "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020", "ESNext.Promise"], + "lib": ["DOM", "DOM.Iterable", "ScriptHost", "ES2020"], "module": "ESNext", "moduleResolution": "node", "noEmit": false, diff --git a/packages/cli/package.json b/packages/cli/package.json index 01306eba2a..22b116e87c 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.6.6", + "version": "0.6.7", "private": false, "publishConfig": { "access": "public" @@ -32,7 +32,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.4.4", "@backstage/cli-common": "^0.1.1", "@backstage/config": "^0.1.4", - "@backstage/config-loader": "^0.5.1", + "@backstage/config-loader": "^0.6.0", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^4.0.0", "@lerna/project": "^4.0.0", @@ -116,11 +116,11 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.6.0", + "@backstage/backend-common": "^0.6.2", "@backstage/config": "^0.1.4", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.9", + "@backstage/test-utils": "^0.1.10", "@backstage/theme": "^0.2.5", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index cbc6473286..cb8ef123ce 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/config-loader +## 0.6.0 + +### Minor Changes + +- 82c66b8cd: Fix bug where `${...}` was not being escaped to `${...}` + + Add support for environment variable substitution in `$include`, `$file` and + `$env` transform values. + + - This change allows for including dynamic paths, such as environment specific + secrets by using the same environment variable substitution (`${..}`) already + supported outside of the various include transforms. + - If you are currently using the syntax `${...}` in your include transform values, + you will need to escape the substitution by using `${...}` instead to maintain + the same behavior. + ## 0.5.1 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index a54975c6ff..0a0a76c2d6 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.5.1", + "version": "0.6.0", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index f8f38cbf59..609940b8db 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/core-api +## 0.2.16 + +### Patch Changes + +- 1279a3325: Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading + errors visible to the user. +- 4a4681b1b: Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error. +- b051e770c: Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail. + ## 0.2.15 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 411fa44c0d..33e5ac146d 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.15", + "version": "0.2.16", "private": false, "publishConfig": { "access": "public", @@ -42,8 +42,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.6", - "@backstage/test-utils": "^0.1.9", + "@backstage/cli": "^0.6.7", + "@backstage/test-utils": "^0.1.10", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 5bdf51837d..cf3aaac931 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -24,7 +24,7 @@ import { AppConfig } from '@backstage/config'; import { SubRouteRef } from '../routing/types'; export type BootErrorPageProps = { - step: 'load-config'; + step: 'load-config' | 'load-chunk'; error: Error; }; diff --git a/packages/core-api/src/extensions/extensions.tsx b/packages/core-api/src/extensions/extensions.tsx index 9463e437e3..8ff4055e61 100644 --- a/packages/core-api/src/extensions/extensions.tsx +++ b/packages/core-api/src/extensions/extensions.tsx @@ -15,9 +15,10 @@ */ import React, { lazy, Suspense } from 'react'; +import { useApp } from '../app'; +import { BackstagePlugin, Extension } from '../plugin/types'; import { RouteRef, useRouteRef } from '../routing'; import { attachComponentData } from './componentData'; -import { Extension, BackstagePlugin } from '../plugin/types'; type ComponentLoader = | { @@ -40,30 +41,41 @@ export function createRoutableExtension< return createReactExtension({ component: { lazy: () => - component().then(InnerComponent => { - const RoutableExtensionWrapper: any = (props: any) => { - // Validate that the routing is wired up correctly in the App.tsx - try { - useRouteRef(mountPoint); - } catch { - throw new Error( - `Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` + - 'Routable extension components may not be rendered by other components and must be ' + - 'directly available as an element within the App provider component.', - ); - } - return ; - }; + component().then( + InnerComponent => { + const RoutableExtensionWrapper: any = (props: any) => { + // Validate that the routing is wired up correctly in the App.tsx + try { + useRouteRef(mountPoint); + } catch { + throw new Error( + `Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` + + 'Routable extension components may not be rendered by other components and must be ' + + 'directly available as an element within the App provider component.', + ); + } + return ; + }; - const componentName = - (InnerComponent as { displayName?: string }).displayName || - InnerComponent.name || - 'LazyComponent'; + const componentName = + (InnerComponent as { displayName?: string }).displayName || + InnerComponent.name || + 'LazyComponent'; - RoutableExtensionWrapper.displayName = `RoutableExtension(${componentName})`; + RoutableExtensionWrapper.displayName = `RoutableExtension(${componentName})`; - return RoutableExtensionWrapper as T; - }), + return RoutableExtensionWrapper as T; + }, + error => { + const RoutableExtensionWrapper: any = (_: any) => { + const app = useApp(); + const { BootErrorPage } = app.getComponents(); + + return ; + }; + return RoutableExtensionWrapper; + }, + ), }, data: { 'core.mountPoint': mountPoint, diff --git a/packages/core-api/src/icons/icons.tsx b/packages/core-api/src/icons/icons.tsx index e638131a28..fd3d0dcdab 100644 --- a/packages/core-api/src/icons/icons.tsx +++ b/packages/core-api/src/icons/icons.tsx @@ -15,6 +15,7 @@ */ import { SvgIconProps } from '@material-ui/core'; +import MuiMenuBookIcon from '@material-ui/icons/MenuBook'; import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage'; import MuiChatIcon from '@material-ui/icons/Chat'; import MuiDashboardIcon from '@material-ui/icons/Dashboard'; @@ -32,6 +33,8 @@ import { IconComponent, IconComponentMap, SystemIconKey } from './types'; export const defaultSystemIcons: IconComponentMap = { brokenImage: MuiBrokenImageIcon, + // To be confirmed: see https://github.com/backstage/backstage/issues/4970 + catalog: MuiMenuBookIcon, chat: MuiChatIcon, dashboard: MuiDashboardIcon, email: MuiEmailIcon, diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 45f26804a5..519a43788d 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/core +## 0.7.4 + +### Patch Changes + +- 1279a3325: Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading + errors visible to the user. +- 4a4681b1b: Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error. +- b051e770c: Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail. +- 98dd5da71: Add support for multiple links to post-scaffold task summary page +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [b051e770c] + - @backstage/core-api@0.2.16 + ## 0.7.3 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 051bacc4c2..d5f9042d0e 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.7.3", + "version": "0.7.4", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.4", - "@backstage/core-api": "^0.2.15", + "@backstage/core-api": "^0.2.16", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -69,8 +69,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.6", - "@backstage/test-utils": "^0.1.9", + "@backstage/cli": "^0.6.7", + "@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", diff --git a/packages/core/src/api-wrappers/createApp.test.tsx b/packages/core/src/api-wrappers/createApp.test.tsx index 31f584b68c..7eaec32b86 100644 --- a/packages/core/src/api-wrappers/createApp.test.tsx +++ b/packages/core/src/api-wrappers/createApp.test.tsx @@ -14,7 +14,10 @@ * limitations under the License. */ -import { defaultConfigLoader } from './createApp'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router'; +import { defaultConfigLoader, OptionallyWrapInRouter } from './createApp'; (process as any).env = { NODE_ENV: 'test' }; const anyEnv = process.env as any; @@ -92,3 +95,23 @@ describe('defaultConfigLoader', () => { ]); }); }); + +describe('OptionallyWrapInRouter', () => { + it('should wrap with router if not yet inside a router', async () => { + const { getByText } = render( + Test, + ); + + expect(getByText('Test')).toBeInTheDocument(); + }); + + it('should not wrap with router if already inside a router', async () => { + const { getByText } = render( + + Test + , + ); + + expect(getByText('Test')).toBeInTheDocument(); + }); +}); diff --git a/packages/core/src/api-wrappers/createApp.tsx b/packages/core/src/api-wrappers/createApp.tsx index db8d0d0636..e79cdf509f 100644 --- a/packages/core/src/api-wrappers/createApp.tsx +++ b/packages/core/src/api-wrappers/createApp.tsx @@ -14,14 +14,18 @@ * limitations under the License. */ -import React from 'react'; +import React, { PropsWithChildren } from 'react'; import privateExports, { AppOptions, defaultSystemIcons, BootErrorPageProps, AppConfigLoader, } from '@backstage/core-api'; -import { BrowserRouter, MemoryRouter } from 'react-router-dom'; +import { + BrowserRouter, + MemoryRouter, + useInRouterContext, +} from 'react-router-dom'; import LightIcon from '@material-ui/icons/WbSunny'; import DarkIcon from '@material-ui/icons/Brightness2'; import { ErrorPage } from '../layout/ErrorPage'; @@ -89,6 +93,13 @@ export const defaultConfigLoader: AppConfigLoader = async ( // The actual implementation of the app class still lives in core-api, // as it needs to be used by dev- and test-utils. +export function OptionallyWrapInRouter({ children }: PropsWithChildren<{}>) { + if (useInRouterContext()) { + return <>{children}; + } + return {children}; +} + /** * Creates a new Backstage App. */ @@ -100,12 +111,14 @@ export function createApp(options?: AppOptions) { let message = ''; if (step === 'load-config') { message = `The configuration failed to load, someone should have a look at this error: ${error.message}`; + } else if (step === 'load-chunk') { + message = `Lazy loaded chunk failed to load, try to reload the page: ${error.message}`; } // TODO: figure out a nicer way to handle routing on the error page, when it can be done. return ( - + - + ); }; diff --git a/packages/core/src/components/Dialog/Dialog.stories.tsx b/packages/core/src/components/Dialog/Dialog.stories.tsx new file mode 100644 index 0000000000..c9388dcbec --- /dev/null +++ b/packages/core/src/components/Dialog/Dialog.stories.tsx @@ -0,0 +1,127 @@ +/* + * 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 { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + Typography, +} from '@material-ui/core'; +import { makeStyles, createStyles, Theme } from '@material-ui/core/styles'; +import CloseIcon from '@material-ui/icons/Close'; +import React, { useState } from 'react'; + +const useStyles = makeStyles((theme: Theme) => + createStyles({ + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, + }), +); + +export default { + title: 'Layout/Dialog', + component: Dialog, +}; + +export const Default = () => { + const [open, setOpen] = useState(false); + const classes = useStyles(); + + const openDialog = () => { + setOpen(true); + }; + + const closeDialog = () => { + setOpen(false); + }; + + const dialogContent = () => { + return ( + <> + + This is an example of how to use the Dialog component. + + + This component is used whenever confirmation of some sort is needed, + such as: + +
    +
  • + + Consent to sensitive matters like GDPR, access, etc; + +
  • +
  • + + Save, submit, cancel after a form is completed; + +
  • +
  • + Alert message; +
  • +
  • + Buttons are optional. +
  • +
+ + The color for the secondary button is the same as the primary. + +
color="primary"
+ + ); + }; + + return ( + <> + + + + Dialog Box Title + + + + + {dialogContent()} + + + + + + + ); +}; diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 762cfbc0d5..9855790e00 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,216 @@ # @backstage/create-app +## 0.3.17 + +### Patch Changes + +- 3e7de08af: **Fully migrated the template to the new composability API** + + The `create-app` template is now fully migrated to the new composability API, see [Composability System Migration Documentation](https://backstage.io/docs/plugins/composability) for explanations and more details. The final change which is now done was to migrate the `EntityPage` from being a component built on top of the `EntityPageLayout` and several more custom components, to an element tree built with `EntitySwitch` and `EntityLayout`. + + To apply this change to an existing plugin, it is important that all plugins that you are using have already been migrated. In this case the most crucial piece is that no entity page cards of contents may require the `entity` prop, and they must instead consume the entity from context using `useEntity`. + + Since this change is large with a lot of repeated changes, we'll describe a couple of common cases rather than the entire change. If your entity pages are unchanged from the `create-app` template, you can also just bring in the latest version directly from the [template itself](https://github.com/backstage/backstage/blob/master/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx). + + The first step of the change is to change the `packages/app/src/components/catalog/EntityPage.tsx` export to `entityPage` rather than `EntityPage`. This will require an update to `App.tsx`, which is the only change we need to do outside of `EntityPage.tsx`: + + ```diff + -import { EntityPage } from './components/catalog/EntityPage'; + +import { entityPage } from './components/catalog/EntityPage'; + + } + > + - + + {entityPage} + + ``` + + The rest of the changes happen within `EntityPage.tsx`, and can be split into two broad categories, updating page components, and updating switch components. + + #### Migrating Page Components + + Let's start with an example of migrating a user page component. The following is the old code in the template: + + ```tsx + const UserOverviewContent = ({ entity }: { entity: UserEntity }) => ( + + + + + + + + + ); + + const UserEntityPage = ({ entity }: { entity: Entity }) => ( + + } + /> + + ); + ``` + + There's the main `UserEntityPage` component, and the `UserOverviewContent` component. Let's start with migrating the page contents, which we do by rendering an element rather than creating a component, as well as replace the cards with their new composability compatible variants. The new cards and content components can be identified by the `Entity` prefix. + + ```tsx + const userOverviewContent = ( + + + + + + + + + ); + ``` + + Now let's migrate the page component, again by converting it into a rendered element instead of a component, as well as replacing the use of `EntityPageLayout` with `EntityLayout`. + + ```tsx + const userPage = ( + + + {userOverviewContent} + + + ); + ``` + + At this point the `userPage` is quite small, so throughout this migration we have inlined the page contents for all pages. This is an optional step, but may help reduce noise. The final page now looks like this: + + ```tsx + const userPage = ( + + + + + + + + + + + + + ); + ``` + + #### Migrating Switch Components + + Switch components were used to select what entity page components or cards to render, based on for example the kind of entity. For this example we'll focus on the root `EntityPage` switch component, but the process is the same for example for the CI/CD switcher. + + The old `EntityPage` looked like this: + + ```tsx + export const EntityPage = () => { + const { entity } = useEntity(); + + switch (entity?.kind?.toLocaleLowerCase('en-US')) { + case 'component': + return ; + case 'api': + return ; + case 'group': + return ; + case 'user': + return ; + case 'system': + return ; + case 'domain': + return ; + case 'location': + case 'resource': + case 'template': + default: + return ; + } + }; + ``` + + In order to migrate to the composability API, we need to make this an element instead of a component, which means we're unable to keep the switch statement as is. To help with this, the catalog plugin provides an `EntitySwitch` component, which functions similar to a regular `switch` statement, which the first match being the one that is rendered. The catalog plugin also provides a number of built-in filter functions to use, such as `isKind` and `isComponentType`. + + To migrate the `EntityPage`, we convert the `switch` statement into an `EntitySwitch` element, and each `case` statement into an `EntitySwitch.Case` element. We also move over to use our new element version of the page components, with the result looking like this: + + ```tsx + export const entityPage = ( + + + + + + + + + {defaultEntityPage} + + ); + ``` + + Another example is the `ComponentEntityPage`, which is migrated from this: + + ```tsx + export const ComponentEntityPage = ({ entity }: { entity: Entity }) => { + switch (entity?.spec?.type) { + case 'service': + return ; + case 'website': + return ; + default: + return ; + } + }; + ``` + + To this: + + ```tsx + const componentPage = ( + + + {serviceEntityPage} + + + + {websiteEntityPage} + + + {defaultEntityPage} + + ); + ``` + + Note that if you want to conditionally render some piece of content, you can omit the default `EntitySwitch.Case`. If no case is matched in an `EntitySwitch`, nothing will be rendered. + +- Updated dependencies [802b41b65] +- Updated dependencies [2b2b31186] +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [97b60de98] +- Updated dependencies [3f96a9d5a] +- Updated dependencies [b051e770c] +- Updated dependencies [f9c75f7a9] +- Updated dependencies [ae6250ce3] +- Updated dependencies [98dd5da71] +- Updated dependencies [b779b5fee] + - @backstage/plugin-scaffolder-backend@0.9.5 + - @backstage/plugin-auth-backend@0.3.8 + - @backstage/core@0.7.4 + - @backstage/catalog-model@0.7.6 + - @backstage/plugin-scaffolder@0.8.2 + - @backstage/plugin-catalog-import@0.5.2 + - @backstage/test-utils@0.1.10 + - @backstage/plugin-catalog@0.5.3 + - @backstage/backend-common@0.6.2 + - @backstage/cli@0.6.7 + - @backstage/plugin-app-backend@0.3.11 + ## 0.3.16 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index e62a544ba1..35ce1b5180 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.3.16", + "version": "0.3.17", "private": false, "publishConfig": { "access": "public" diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 1cd7b77965..1ed7aa030a 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -12,14 +12,14 @@ import { CatalogIndexPage, catalogPlugin, } from '@backstage/plugin-catalog'; -import { CatalogImportPage } from '@backstage/plugin-catalog-import'; +import {CatalogImportPage, catalogImportPlugin} from '@backstage/plugin-catalog-import'; import { ScaffolderPage, scaffolderPlugin } from '@backstage/plugin-scaffolder'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; import { TechdocsPage } from '@backstage/plugin-techdocs'; import { UserSettingsPage } from '@backstage/plugin-user-settings'; import { apis } from './apis'; -import { EntityPage } from './components/catalog/EntityPage'; +import { entityPage } from './components/catalog/EntityPage'; import { Root } from './components/Root'; import * as plugins from './plugins'; @@ -33,6 +33,9 @@ const app = createApp({ bind(apiDocsPlugin.externalRoutes, { createComponent: scaffolderPlugin.routes.root, }); + bind(scaffolderPlugin.externalRoutes, { + registerComponent: catalogImportPlugin.routes.importPage, + }); }, }); @@ -47,7 +50,7 @@ const routes = ( path="/catalog/:namespace/:kind/:name" element={} > - + {entityPage}
} /> } /> diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index d05f6bfa8d..f1aba46c52 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -15,308 +15,248 @@ */ import React from 'react'; import { Button, Grid } from '@material-ui/core'; -import { - ApiEntity, - DomainEntity, - Entity, - GroupEntity, - SystemEntity, - UserEntity, -} from '@backstage/catalog-model'; import { EmptyState } from '@backstage/core'; import { - ApiDefinitionCard, - ConsumedApisCard, - ConsumingComponentsCard, + EntityApiDefinitionCard, + EntityConsumedApisCard, + EntityConsumingComponentsCard, EntityHasApisCard, - ProvidedApisCard, - ProvidingComponentsCard, + EntityProvidedApisCard, + EntityProvidingComponentsCard, } from '@backstage/plugin-api-docs'; import { - AboutCard, + EntityAboutCard, + EntitySystemDiagramCard, EntityHasComponentsCard, EntityHasSystemsCard, - EntityPageLayout, + EntityLayout, + EntitySwitch, + isComponentType, + isKind, } from '@backstage/plugin-catalog'; -import { useEntity } from '@backstage/plugin-catalog-react'; import { - isPluginApplicableToEntity as isGitHubActionsAvailable, - Router as GitHubActionsRouter, + isGithubActionsAvailable, + EntityGithubActionsContent, } from '@backstage/plugin-github-actions'; import { - GroupProfileCard, - MembersListCard, - OwnershipCard, - UserProfileCard, + EntityUserProfileCard, + EntityGroupProfileCard, + EntityMembersListCard, + EntityOwnershipCard, } from '@backstage/plugin-org'; -import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; +import { EntityTechdocsContent } from '@backstage/plugin-techdocs'; - -const CICDSwitcher = ({ entity }: { entity: Entity }) => { - // This component is just an example of how you can implement your company's logic in entity page. +const cicdContent = ( + // This is an example of how you can implement your company's logic in entity page. // You can for example enforce that all components of type 'service' should use GitHubActions - switch (true) { - case isGitHubActionsAvailable(entity): - return ; - default: - return ( - - Read more - - } - /> - ); - } -}; + + + + -const OverviewContent = ({ entity }: { entity: Entity }) => ( + + + Read more + + } + /> + + +); + +const overviewContent = ( - + ); -const ComponentApisContent = ({ entity }: { entity: Entity }) => ( - - - - - - - - -); +const serviceEntityPage = ( + + + {overviewContent} + -const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - } - /> - -); + + {cicdContent} + -const WebsiteEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - } - /> - -); - -const DefaultEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - -); - -export const ComponentEntityPage = ({ entity }: { entity: Entity }) => { - switch (entity?.spec?.type) { - case 'service': - return ; - case 'website': - return ; - default: - return ; - } -}; - -const ApiOverviewContent = ({ entity }: { entity: Entity }) => ( - - - - - - - + + + + + + + + - - + + + + + + +); + +const websiteEntityPage = ( + + + {overviewContent} + + + + {cicdContent} + + + + + + +); + +const defaultEntityPage = ( + + + {overviewContent} + + + + + + +); + +const componentPage = ( + + + {serviceEntityPage} + + + + {websiteEntityPage} + + + {defaultEntityPage} + +); + +const apiPage = ( + + + + + + + + + + + + + + - - + + + + + + + + + + ); -const ApiDefinitionContent = ({ entity }: { entity: ApiEntity }) => ( - - - - - +const userPage = ( + + + + + + + + + + + + ); -const ApiEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - +const groupPage = ( + + + + + + + + + + + + + + + + + + + ); -const UserOverviewContent = ({ entity }: { entity: UserEntity }) => ( - - - - - - - - +const systemPage = ( + + + + + + + + + + + + + + + ); -const UserEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - +const domainPage = ( + + + + + + + + + + + + ); -const GroupOverviewContent = ({ entity }: { entity: GroupEntity }) => ( - - - - - - - - - - - +export const entityPage = ( + + + + + + + + + {defaultEntityPage} + ); - -const GroupEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -const SystemOverviewContent = ({ entity }: { entity: SystemEntity }) => ( - - - - - - - - - - - -); - -const SystemEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -const DomainOverviewContent = ({ entity }: { entity: DomainEntity }) => ( - - - - - - - - -); - -const DomainEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - -); - -export const EntityPage = () => { - const { entity } = useEntity(); - - switch (entity?.kind?.toLocaleLowerCase('en-US')) { - case 'component': - return ; - case 'api': - return ; - case 'group': - return ; - case 'user': - return ; - case 'system': - return ; - case 'domain': - return ; - case 'location': - case 'resource': - case 'template': - default: - return ; - } -}; diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index acef405c8a..31231a3a4a 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -16,11 +16,13 @@ WORKDIR /app # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. # The skeleton contains the package.json of each package in the monorepo, # and along with yarn.lock and the root package.json, that's enough to run yarn install. -ADD yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ +RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" # Then copy the rest of the backend bundle, along with any other files we might want. -ADD packages/backend/dist/bundle.tar.gz app-config.yaml ./ +COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ +RUN tar xzf bundle.tar.gz && rm bundle.tar.gz CMD ["node", "packages/backend", "--config", "app-config.yaml"] diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index f0d72b7ceb..ddc84ae011 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -149,5 +149,19 @@ export interface Config { */ baseUrl?: string; }>; + + /** Integration configuration for Google Cloud Storage */ + googleGcs?: { + /** + * Service account email used to authenticate requests. + * @visibility backend + */ + clientEmail?: string; + /** + * Service account private key used to authenticate requests. + * @visibility secret + */ + privateKey?: string; + }; }; } diff --git a/packages/integration/package.json b/packages/integration/package.json index c57c0e78cd..dcfae4ea05 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -37,9 +37,9 @@ "luxon": "^1.25.0" }, "devDependencies": { - "@backstage/cli": "^0.6.4", - "@backstage/config-loader": "^0.5.1", - "@backstage/test-utils": "^0.1.7", + "@backstage/cli": "^0.6.7", + "@backstage/config-loader": "^0.6.0", + "@backstage/test-utils": "^0.1.10", "@types/jest": "^26.0.7", "@types/luxon": "^1.25.0", "msw": "^0.21.2" diff --git a/packages/integration/src/googleGcs/config.test.ts b/packages/integration/src/googleGcs/config.test.ts new file mode 100644 index 0000000000..a924e953b9 --- /dev/null +++ b/packages/integration/src/googleGcs/config.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright 2020 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 { Config, ConfigReader } from '@backstage/config'; +import { + GoogleGcsIntegrationConfig, + readGoogleGcsIntegrationConfig, +} from './config'; + +describe('readGoogleGcsIntegrationConfig', () => { + function buildConfig(data: Partial): Config { + return new ConfigReader(data); + } + + it('reads all values', () => { + const output = readGoogleGcsIntegrationConfig( + buildConfig({ + privateKey: 'fake-key', + clientEmail: 'someone@example.com', + }), + ); + expect(output).toEqual({ + privateKey: 'fake-key', + clientEmail: 'someone@example.com', + }); + }); + + it('does not fail when config is not set', () => { + const output = readGoogleGcsIntegrationConfig(buildConfig({})); + expect(output).toEqual({}); + }); +}); diff --git a/packages/integration/src/googleGcs/config.ts b/packages/integration/src/googleGcs/config.ts new file mode 100644 index 0000000000..7924138b5a --- /dev/null +++ b/packages/integration/src/googleGcs/config.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2020 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 { Config } from '@backstage/config'; + +/** + * The configuration parameters for a single Google Cloud Storage provider. + */ +export type GoogleGcsIntegrationConfig = { + /** + * Service account email used to authenticate requests. + */ + clientEmail?: string; + /** + * Service account private key used to authenticate requests. + */ + privateKey?: string; +}; + +/** + * Reads a single Google GCS integration config. + * + * @param config The config object of a single integration + */ +export function readGoogleGcsIntegrationConfig( + config: Config, +): GoogleGcsIntegrationConfig { + if (!config) { + return {}; + } + + if (!config.has('clientEmail') && !config.has('privateKey')) { + return {}; + } + + const privateKey = config.getString('privateKey').split('\\n').join('\n'); + + const clientEmail = config.getString('clientEmail'); + return { clientEmail: clientEmail, privateKey: privateKey }; +} diff --git a/packages/integration/src/googleGcs/index.ts b/packages/integration/src/googleGcs/index.ts new file mode 100644 index 0000000000..d9355fd299 --- /dev/null +++ b/packages/integration/src/googleGcs/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 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 { readGoogleGcsIntegrationConfig } from './config'; +export type { GoogleGcsIntegrationConfig } from './config'; diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index e89cf6ef92..d15eea0832 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -18,6 +18,7 @@ export * from './azure'; export * from './bitbucket'; export * from './github'; export * from './gitlab'; +export * from './googleGcs'; export { defaultScmResolveUrl } from './helpers'; export { ScmIntegrations } from './ScmIntegrations'; export type { diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index bcdc94abbb..d2c2748449 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/test-utils +## 0.1.10 + +### Patch Changes + +- ae6250ce3: Remove unnecessary wrapping of elements rendered by `wrapInTestApp` and `renderInTestApp`, which was breaking mount discovery. +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [b051e770c] + - @backstage/core-api@0.2.16 + ## 0.1.9 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index f3a979fc72..a81455df01 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.9", + "version": "0.1.10", "private": false, "publishConfig": { "access": "public", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-api": "^0.2.14", + "@backstage/core-api": "^0.2.16", "@backstage/test-utils-core": "^0.1.1", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -45,7 +45,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.5", + "@backstage/cli": "^0.6.7", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 7f34627f6b..d7f5f3754b 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -31,7 +31,7 @@ "dependencies": { "@asyncapi/react-component": "^0.19.2", "@backstage/catalog-model": "^0.7.5", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.4", "@backstage/theme": "^0.2.5", "@material-icons/font": "^1.0.2", @@ -49,9 +49,9 @@ "swagger-ui-react": "^3.37.2" }, "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", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index e8c4510d54..85fab4455d 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-app-backend +## 0.3.11 + +### Patch Changes + +- Updated dependencies [82c66b8cd] +- Updated dependencies [b779b5fee] + - @backstage/config-loader@0.6.0 + - @backstage/backend-common@0.6.2 + ## 0.3.10 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 9346c6366d..c6a1dd5a6b 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.3.10", + "version": "0.3.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.6.0", - "@backstage/config-loader": "^0.5.1", + "@backstage/backend-common": "^0.6.2", + "@backstage/config-loader": "^0.6.0", "@backstage/config": "^0.1.4", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -40,7 +40,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.5", + "@backstage/cli": "^0.6.7", "@types/supertest": "^2.0.8", "msw": "^0.20.5", "supertest": "^6.1.3" diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index bb2c44f7ff..47616b68c7 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-auth-backend +## 0.3.8 + +### Patch Changes + +- 2b2b31186: When using OAuth2 authentication the name is now taken from the name property of the JWT instead of the email property +- Updated dependencies [97b60de98] +- Updated dependencies [ae6250ce3] +- Updated dependencies [98dd5da71] +- Updated dependencies [b779b5fee] + - @backstage/catalog-model@0.7.6 + - @backstage/test-utils@0.1.10 + - @backstage/backend-common@0.6.2 + ## 0.3.7 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 183798c9e1..4af8a1736d 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.3.7", + "version": "0.3.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,12 +29,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.6.1", + "@backstage/backend-common": "^0.6.2", "@backstage/catalog-client": "^0.3.9", - "@backstage/catalog-model": "^0.7.5", + "@backstage/catalog-model": "^0.7.6", "@backstage/config": "^0.1.4", "@backstage/errors": "^0.1.1", - "@backstage/test-utils": "^0.1.9", + "@backstage/test-utils": "^0.1.10", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", "compression": "^1.7.4", @@ -68,7 +68,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.6", + "@backstage/cli": "^0.6.7", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md new file mode 100644 index 0000000000..69785a645f --- /dev/null +++ b/plugins/badges-backend/CHANGELOG.md @@ -0,0 +1,12 @@ +# @backstage/plugin-badges-backend + +## 0.1.2 + +### Patch Changes + +- d0b4ebf22: Support auth in badge plugin +- Updated dependencies [97b60de98] +- Updated dependencies [98dd5da71] +- Updated dependencies [b779b5fee] + - @backstage/catalog-model@0.7.6 + - @backstage/backend-common@0.6.2 diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 6ff83ecac9..eb2a93fe67 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-badges-backend", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.6.0", + "@backstage/backend-common": "^0.6.2", "@backstage/catalog-client": "^0.3.6", - "@backstage/catalog-model": "^0.7.3", + "@backstage/catalog-model": "^0.7.6", "@backstage/config": "^0.1.3", "@backstage/errors": "^0.1.1", "@types/express": "^4.17.6", @@ -45,7 +45,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.3", + "@backstage/cli": "^0.6.7", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/badges-backend/src/service/router.test.ts b/plugins/badges-backend/src/service/router.test.ts index 2d6075959d..1a178bfef2 100644 --- a/plugins/badges-backend/src/service/router.test.ts +++ b/plugins/badges-backend/src/service/router.test.ts @@ -114,11 +114,14 @@ describe('createRouter', () => { expect(response.text).toEqual(JSON.stringify([badge], null, 2)); expect(catalog.getEntityByName).toHaveBeenCalledTimes(1); - expect(catalog.getEntityByName).toHaveBeenCalledWith({ - namespace: 'default', - kind: 'service', - name: 'test', - }); + expect(catalog.getEntityByName).toHaveBeenCalledWith( + { + namespace: 'default', + kind: 'service', + name: 'test', + }, + { token: undefined }, + ); expect(badgeBuilder.getBadges).toHaveBeenCalledTimes(1); expect(badgeBuilder.createBadgeJson).toHaveBeenCalledTimes(1); @@ -150,11 +153,14 @@ describe('createRouter', () => { expect(response.body).toEqual(Buffer.from(image)); expect(catalog.getEntityByName).toHaveBeenCalledTimes(1); - expect(catalog.getEntityByName).toHaveBeenCalledWith({ - namespace: 'default', - kind: 'service', - name: 'test', - }); + expect(catalog.getEntityByName).toHaveBeenCalledWith( + { + namespace: 'default', + kind: 'service', + name: 'test', + }, + { token: undefined }, + ); expect(badgeBuilder.getBadges).toHaveBeenCalledTimes(0); expect(badgeBuilder.createBadgeSvg).toHaveBeenCalledTimes(1); diff --git a/plugins/badges-backend/src/service/router.ts b/plugins/badges-backend/src/service/router.ts index 49b0e36944..9be8cddb5d 100644 --- a/plugins/badges-backend/src/service/router.ts +++ b/plugins/badges-backend/src/service/router.ts @@ -46,7 +46,12 @@ export async function createRouter( router.get('/entity/:namespace/:kind/:name/badge-specs', async (req, res) => { const { namespace, kind, name } = req.params; - const entity = await catalog.getEntityByName({ namespace, kind, name }); + const entity = await catalog.getEntityByName( + { namespace, kind, name }, + { + token: getBearerToken(req.headers.authorization), + }, + ); if (!entity) { throw new NotFoundError( `No ${kind} entity in ${namespace} named "${name}"`, @@ -79,7 +84,12 @@ export async function createRouter( '/entity/:namespace/:kind/:name/badge/:badgeId', async (req, res) => { const { namespace, kind, name, badgeId } = req.params; - const entity = await catalog.getEntityByName({ namespace, kind, name }); + const entity = await catalog.getEntityByName( + { namespace, kind, name }, + { + token: getBearerToken(req.headers.authorization), + }, + ); if (!entity) { throw new NotFoundError( `No ${kind} entity in ${namespace} named "${name}"`, @@ -132,3 +142,7 @@ async function getBadgeUrl( const baseUrl = await options.discovery.getExternalBaseUrl('badges'); return `${baseUrl}/entity/${namespace}/${kind}/${name}/badge/${badgeId}`; } + +function getBearerToken(header?: string): string | undefined { + return header?.match(/Bearer\s+(\S+)/i)?.[1]; +} diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index bc3e64113a..a0efd43ccc 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-badges +## 0.2.0 + +### Minor Changes + +- d0b4ebf22: Support auth in badge plugin + +### Patch Changes + +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [97b60de98] +- Updated dependencies [b051e770c] +- Updated dependencies [98dd5da71] + - @backstage/core@0.7.4 + - @backstage/catalog-model@0.7.6 + ## 0.1.2 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index e438a6807b..1ab7315278 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-badges", - "version": "0.1.2", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.3", + "@backstage/catalog-model": "^0.7.6", + "@backstage/core": "^0.7.4", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.1.3", "@backstage/theme": "^0.2.5", @@ -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", diff --git a/plugins/badges/src/api/BadgesClient.ts b/plugins/badges/src/api/BadgesClient.ts index 927a16a7eb..c015a59703 100644 --- a/plugins/badges/src/api/BadgesClient.ts +++ b/plugins/badges/src/api/BadgesClient.ts @@ -15,7 +15,7 @@ */ import { generatePath } from 'react-router'; -import { DiscoveryApi } from '@backstage/core'; +import { DiscoveryApi, IdentityApi } from '@backstage/core'; import { ResponseError } from '@backstage/errors'; import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { entityRoute } from '@backstage/plugin-catalog-react'; @@ -23,14 +23,26 @@ import { BadgesApi, BadgeSpec } from './types'; export class BadgesClient implements BadgesApi { private readonly discoveryApi: DiscoveryApi; + private readonly identityApi: IdentityApi; - constructor(options: { discoveryApi: DiscoveryApi }) { + constructor(options: { + discoveryApi: DiscoveryApi; + identityApi: IdentityApi; + }) { this.discoveryApi = options.discoveryApi; + this.identityApi = options.identityApi; } public async getEntityBadgeSpecs(entity: Entity): Promise { const entityBadgeSpecsUrl = await this.getEntityBadgeSpecsUrl(entity); - const response = await fetch(entityBadgeSpecsUrl); + const token = await this.identityApi.getIdToken(); + const response = await fetch(entityBadgeSpecsUrl, { + headers: token + ? { + Authorization: `Bearer ${token}`, + } + : undefined, + }); if (!response.ok) { throw await ResponseError.fromResponse(response); diff --git a/plugins/badges/src/plugin.ts b/plugins/badges/src/plugin.ts index 92932aa356..a247c93a4b 100644 --- a/plugins/badges/src/plugin.ts +++ b/plugins/badges/src/plugin.ts @@ -18,6 +18,7 @@ import { createComponentExtension, createPlugin, discoveryApiRef, + identityApiRef, } from '@backstage/core'; import { badgesApiRef, BadgesClient } from './api'; @@ -26,8 +27,9 @@ export const badgesPlugin = createPlugin({ apis: [ createApiFactory({ api: badgesApiRef, - deps: { discoveryApi: discoveryApiRef }, - factory: ({ discoveryApi }) => new BadgesClient({ discoveryApi }), + deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef }, + factory: ({ discoveryApi, identityApi }) => + new BadgesClient({ discoveryApi, identityApi }), }), ], }); diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index f7f58d99cf..8a3b519fcb 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.2", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.2", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -37,9 +37,9 @@ "recharts": "^1.8.5" }, "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", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 7b5cee247f..5fd93562d6 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-import +## 0.5.2 + +### Patch Changes + +- f9c75f7a9: When importing components you will now have the ability to use non Unicode characters in the entity owner field +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [97b60de98] +- Updated dependencies [b051e770c] +- Updated dependencies [98dd5da71] + - @backstage/core@0.7.4 + - @backstage/catalog-model@0.7.6 + ## 0.5.1 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index e98bdfdb97..a168f089c8 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.5.1", + "version": "0.5.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.5", + "@backstage/catalog-model": "^0.7.6", "@backstage/catalog-client": "^0.3.9", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/integration": "^0.5.0", "@backstage/integration-react": "^0.1.1", "@backstage/plugin-catalog-react": "^0.1.4", @@ -43,6 +43,7 @@ "@octokit/rest": "^18.0.12", "@types/react": "^16.9", "git-url-parse": "^11.4.4", + "js-base64": "^3.6.0", "react": "^16.13.1", "react-dom": "^16.13.1", "react-hook-form": "^6.15.4", @@ -52,9 +53,9 @@ "yaml": "^1.10.0" }, "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/react-hooks": "^3.3.0", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index 687bea39e3..de7055cf25 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -309,7 +309,7 @@ describe('CatalogImportClient', () => { await expect( catalogImportClient.submitPullRequest({ repositoryUrl: 'https://github.com/backstage/backstage', - fileContent: 'some content', + fileContent: 'some content 🤖', title: 'A title/message', body: 'A body', }), @@ -335,7 +335,7 @@ describe('CatalogImportClient', () => { repo: 'backstage', path: 'catalog-info.yaml', message: 'A title/message', - content: 'c29tZSBjb250ZW50', + content: 'c29tZSBjb250ZW50IPCfpJY=', branch: 'backstage-integration', }); expect( diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index f6e8cb69d4..6dd48d2aa7 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -21,6 +21,7 @@ import { GitHubIntegrationConfig, ScmIntegrationRegistry, } from '@backstage/integration'; +import { Base64 } from 'js-base64'; import { Octokit } from '@octokit/rest'; import { PartialEntity } from '../types'; import { AnalyzeResult, CatalogImportApi } from './CatalogImportApi'; @@ -300,7 +301,7 @@ export class CatalogImportClient implements CatalogImportApi { repo, path: fileName, message: title, - content: btoa(fileContent), + content: Base64.encode(fileContent), branch: branchName, }) .catch(e => { diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 31f511fdbf..7a79c39af1 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog +## 0.5.3 + +### Patch Changes + +- 98dd5da71: Add support for multiple links to post-scaffold task summary page +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [97b60de98] +- Updated dependencies [b051e770c] +- Updated dependencies [98dd5da71] + - @backstage/core@0.7.4 + - @backstage/catalog-model@0.7.6 + ## 0.5.2 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index f86c43430a..4e3d364b78 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.5.2", + "version": "0.5.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.9", - "@backstage/catalog-model": "^0.7.5", - "@backstage/core": "^0.7.3", + "@backstage/catalog-model": "^0.7.6", + "@backstage/core": "^0.7.4", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.1", "@backstage/integration-react": "^0.1.1", @@ -53,9 +53,9 @@ "swr": "^0.3.0" }, "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", "@microsoft/microsoft-graph-types": "^1.25.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 3a3b847e97..1d9fa11b3c 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.2", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -50,9 +50,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", diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 8718a50a5f..a713d57883 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -32,7 +32,7 @@ "dependencies": { "@backstage/catalog-model": "^0.7.3", "@backstage/plugin-catalog-react": "^0.1.2", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,9 +47,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", diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 7aece742fc..a983e4ed3e 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/config": "^0.1.3", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -55,9 +55,9 @@ "yup": "^0.29.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", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index c640be6f23..ea6c41fcd4 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.5", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.4", "@backstage/plugin-explore-react": "^0.0.4", "@backstage/theme": "^0.2.5", @@ -45,9 +45,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", diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index d503f76928..209f7512aa 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -44,9 +44,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", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 0f05e9d05b..a381d785a0 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,9 +41,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", diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 8cc9471613..a744fcdc6e 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -34,7 +34,7 @@ "dependencies": { "@backstage/catalog-model": "^0.7.5", "@backstage/plugin-catalog-react": "^0.1.4", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/integration": "^0.5.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -50,9 +50,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", diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md new file mode 100644 index 0000000000..9369ef87f9 --- /dev/null +++ b/plugins/github-deployments/CHANGELOG.md @@ -0,0 +1,14 @@ +# @backstage/plugin-github-deployments + +## 0.1.2 + +### Patch Changes + +- 64d2ce700: Add a button to reload the GitHub Deployments card +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [97b60de98] +- Updated dependencies [b051e770c] +- Updated dependencies [98dd5da71] + - @backstage/core@0.7.4 + - @backstage/catalog-model@0.7.6 diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 39c7577bb5..21c4fc272a 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-deployments", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index a82a2c26ed..2d12272e22 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,9 +42,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", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index b9e61b02da..e9d1353112 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,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", diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index f580dc2d15..3e00ae9373 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.3", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -47,9 +47,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", diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index fb4ec0db41..52cd84b790 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.4", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -33,9 +33,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/react-hooks": "^3.4.2", diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 71f11e7a68..9d01997c13 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -33,7 +33,7 @@ "dependencies": { "@backstage/catalog-model": "^0.7.4", "@backstage/config": "^0.1.4", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.3", "@backstage/plugin-kubernetes-backend": "^0.3.2", "@backstage/theme": "^0.2.5", @@ -50,9 +50,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/react-hooks": "^3.4.2", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 7f5b678afd..2fd25a9add 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -33,7 +33,7 @@ "dependencies": { "@backstage/catalog-model": "^0.7.3", "@backstage/config": "^0.1.4", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.2", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -46,9 +46,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", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index b62fd82d20..83a46bb8b1 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,9 +41,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", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 9239eabd9b..c62c618ff4 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-org +## 0.3.12 + +### Patch Changes + +- 97d53f686: Optimize data fetched for the `OwnershipCard`. +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [97b60de98] +- Updated dependencies [b051e770c] +- Updated dependencies [98dd5da71] + - @backstage/core@0.7.4 + - @backstage/core-api@0.2.16 + - @backstage/catalog-model@0.7.6 + ## 0.3.11 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index d8de1469ee..9fa79e27c6 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.3.11", + "version": "0.3.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.5", - "@backstage/core": "^0.7.3", - "@backstage/core-api": "^0.2.14", + "@backstage/catalog-model": "^0.7.6", + "@backstage/core": "^0.7.4", + "@backstage/core-api": "^0.2.16", "@backstage/plugin-catalog-react": "^0.1.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -35,9 +35,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", diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index df1078eedd..342d375863 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -46,9 +46,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", diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 73d343a205..d63800db5c 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.5", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -45,9 +45,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", diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 1421ee5051..084dcbad32 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -47,9 +47,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/react-hooks": "^3.3.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index a54ec4430d..e2c1717a71 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend +## 0.9.5 + +### Patch Changes + +- 802b41b65: Allow custom directory to be specified for GitHub publish action +- Updated dependencies [97b60de98] +- Updated dependencies [98dd5da71] +- Updated dependencies [b779b5fee] + - @backstage/catalog-model@0.7.6 + - @backstage/backend-common@0.6.2 + ## 0.9.4 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 69aced77b5..a3a61975f1 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.9.4", + "version": "0.9.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.6.1", + "@backstage/backend-common": "^0.6.2", "@backstage/catalog-client": "^0.3.9", - "@backstage/catalog-model": "^0.7.5", + "@backstage/catalog-model": "^0.7.6", "@backstage/config": "^0.1.4", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.1", @@ -57,15 +57,17 @@ "isomorphic-git": "^1.8.0", "jsonschema": "^1.2.6", "knex": "^0.95.1", + "lodash": "^4.17.21", "luxon": "^1.26.0", "morgan": "^1.10.0", + "octokit-plugin-create-pull-request": "^3.9.3", "uuid": "^8.2.0", "winston": "^3.2.1", "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.6.6", - "@backstage/test-utils": "^0.1.9", + "@backstage/cli": "^0.6.7", + "@backstage/test-utils": "^0.1.10", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/supertest": "^2.0.8", diff --git a/plugins/scaffolder-backend/sample-templates/local-templates.yaml b/plugins/scaffolder-backend/sample-templates/local-templates.yaml index b0e16f9dea..76c76bc97a 100644 --- a/plugins/scaffolder-backend/sample-templates/local-templates.yaml +++ b/plugins/scaffolder-backend/sample-templates/local-templates.yaml @@ -10,3 +10,4 @@ spec: - ./react-ssr-template/template.yaml - ./springboot-grpc-template/template.yaml - ./v1beta2-demo/template.yaml + - ./pull-request/template.yaml diff --git a/plugins/scaffolder-backend/sample-templates/pull-request/template.yaml b/plugins/scaffolder-backend/sample-templates/pull-request/template.yaml new file mode 100644 index 0000000000..cad2a58d1f --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/pull-request/template.yaml @@ -0,0 +1,76 @@ +apiVersion: backstage.io/v1beta2 +kind: Template +metadata: + name: pull-request + title: Pull Request Action template + description: scaffolder v1beta2 template demo publishing to PR on existing git repository +spec: + owner: backstage/techdocs-core + type: service + + parameters: + - title: Fill in some steps + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + ui:options: + rows: 5 + description: + title: Description + type: string + description: Description of the component + targetPath: + title: Target Path in repo + type: string + description: Name of the directory to create in the repository + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com + + steps: + - id: fetch-base + name: Fetch Base + action: fetch:cookiecutter + input: + url: ./template + values: + name: '{{parameters.name}}' + + - id: fetch-docs + name: Fetch Docs + action: fetch:plain + input: + targetPath: ./community + url: https://github.com/backstage/community/tree/main/backstage-community-sessions + + - id: publish + name: Publish + action: publish:github:pull-request + input: + repoUrl: '{{ parameters.repoUrl }}' + title: 'Create new project: {{parameters.name}}' + branchName: 'create-{{parameters.name}}' + description: | + # New project: {{parameters.name}} + + {{#if parameters.description}} + {{parameters.description}} + {{/if}} + host: '{{parameters.host}}' + targetPath: '{{#if parameters.targetPath}}{{parameters.targetPath}}{{else}}{{parameters.name}}{{/if}}' + + output: + remoteUrl: '{{steps.publish.output.remoteUrl}}' diff --git a/plugins/scaffolder-backend/sample-templates/pull-request/template/catalog-info.yaml b/plugins/scaffolder-backend/sample-templates/pull-request/template/catalog-info.yaml new file mode 100644 index 0000000000..dd1e0ebd09 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/pull-request/template/catalog-info.yaml @@ -0,0 +1,8 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: {{cookiecutter.name | jsonify}} +spec: + type: website + lifecycle: experimental + owner: guest diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index 0829a101f2..2d4b8fb4c5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -23,6 +23,7 @@ import { createPublishAzureAction, createPublishBitbucketAction, createPublishGithubAction, + createPublishGithubPullRequestAction, createPublishGitlabAction, } from './publish'; import Docker from 'dockerode'; @@ -57,6 +58,9 @@ export const createBuiltinActions = (options: { createPublishGithubAction({ integrations, }), + createPublishGithubPullRequestAction({ + integrations, + }), createPublishGitlabAction({ integrations, }), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts index 0141de3499..e1a064b3e6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts @@ -19,7 +19,7 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { initRepoAndPush } from '../../../stages/publish/helpers'; import { GitRepositoryCreateOptions } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { getPersonalAccessTokenHandler, WebApi } from 'azure-devops-node-api'; -import { parseRepoUrl } from './util'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { createTemplateAction } from '../../createTemplateAction'; export function createPublishAzureAction(options: { @@ -30,6 +30,7 @@ export function createPublishAzureAction(options: { return createTemplateAction<{ repoUrl: string; description?: string; + sourcePath?: string; }>({ id: 'publish:azure', description: @@ -47,6 +48,11 @@ export function createPublishAzureAction(options: { title: 'Repository Description', type: 'string', }, + sourcePath: { + title: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', + type: 'string', + }, }, }, output: { @@ -112,7 +118,7 @@ export function createPublishAzureAction(options: { const repoContentsUrl = remoteUrl; await initRepoAndPush({ - dir: ctx.workspacePath, + dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl, auth: { username: 'notempty', diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts index a7370356cc..13f639d745 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts @@ -20,7 +20,7 @@ import { ScmIntegrationRegistry, } from '@backstage/integration'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -import { parseRepoUrl } from './util'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; import fetch from 'cross-fetch'; import { createTemplateAction } from '../../createTemplateAction'; @@ -165,6 +165,7 @@ export function createPublishBitbucketAction(options: { repoUrl: string; description: string; repoVisibility: 'private' | 'public'; + sourcePath?: string; }>({ id: 'publish:bitbucket', description: @@ -187,6 +188,11 @@ export function createPublishBitbucketAction(options: { type: 'string', enum: ['private', 'public'], }, + sourcePath: { + title: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', + type: 'string', + }, }, }, output: { @@ -233,7 +239,7 @@ export function createPublishBitbucketAction(options: { }); await initRepoAndPush({ - dir: ctx.workspacePath, + dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl, auth: { username: integrationConfig.config.username diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index 7ade6ffd19..70394b0039 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { InputError } from '@backstage/errors'; import { GithubCredentialsProvider, @@ -21,7 +20,7 @@ import { } from '@backstage/integration'; import { Octokit } from '@octokit/rest'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -import { parseRepoUrl } from './util'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { createTemplateAction } from '../../createTemplateAction'; export function createPublishGithubAction(options: { @@ -40,6 +39,7 @@ export function createPublishGithubAction(options: { repoUrl: string; description?: string; access?: string; + sourcePath?: string; repoVisibility: 'private' | 'internal' | 'public'; }>({ id: 'publish:github', @@ -67,6 +67,11 @@ export function createPublishGithubAction(options: { type: 'string', enum: ['private', 'public', 'internal'], }, + sourcePath: { + title: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', + type: 'string', + }, }, }, output: { @@ -164,7 +169,7 @@ export function createPublishGithubAction(options: { const repoContentsUrl = `${data.html_url}/blob/master`; await initRepoAndPush({ - dir: ctx.workspacePath, + dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl, auth: { username: 'x-access-token', diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts new file mode 100644 index 0000000000..fb9d3aeaa6 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts @@ -0,0 +1,236 @@ +/* + * 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 mockFs from 'mock-fs'; +import { Writable } from 'stream'; +import { + PullRequestCreator, + GithubPullRequestActionInput, + createPublishGithubPullRequestAction, + ClientFactoryInput, +} from './githubPullRequest'; +import { ActionContext, TemplateAction } from '../../types'; +import { getRootLogger } from '@backstage/backend-common'; + +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; + +const id = 'createPublishGithubPullRequestAction'; + +describe('createPublishGithubPullRequestAction', () => { + let instance: TemplateAction; + let fakeClient: PullRequestCreator; + + let clientFactory: (input: ClientFactoryInput) => Promise; + + beforeEach(() => { + const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); + fakeClient = { + createPullRequest: jest.fn(async (_: any) => { + return { + url: 'https://api.github.com/myorg/myrepo/pull/123', + headers: {}, + status: 201, + data: { + html_url: 'https://github.com/myorg/myrepo/pull/123', + }, + }; + }), + }; + clientFactory = jest.fn(async () => fakeClient); + + instance = createPublishGithubPullRequestAction({ + integrations, + clientFactory, + }); + }); + + describe('with no sourcePath', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + }; + + mockFs({ + [id]: { 'file.txt': 'Hello there!' }, + }); + + ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath: id, + }; + }); + it('creates a pull request', async () => { + await instance.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': 'Hello there!', + }, + }, + ], + }); + }); + + it('creates outputs for the url', async () => { + await instance.handler(ctx); + + expect(ctx.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://github.com/myorg/myrepo/pull/123', + ); + }); + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + }); + + describe('with sourcePath', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + sourcePath: 'source', + }; + + mockFs({ + [id]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath: id, + }; + }); + + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + + it('creates a pull request with only relevant files', async () => { + await instance.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'foo.txt': 'Hello there!', + }, + }, + ], + }); + }); + }); + + describe('with repoUrl', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + beforeEach(() => { + input = { + repoUrl: 'github.com?owner=myorg&repo=myrepo', + title: 'Create my new app', + branchName: 'new-app', + description: 'This PR is really good', + }; + + mockFs({ + [id]: { 'file.txt': 'Hello there!' }, + }); + + ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath: id, + }; + }); + it('creates a pull request', async () => { + await instance.handler(ctx); + + expect(fakeClient.createPullRequest).toHaveBeenCalledWith({ + owner: 'myorg', + repo: 'myrepo', + title: 'Create my new app', + head: 'new-app', + body: 'This PR is really good', + changes: [ + { + commit: 'Create my new app', + files: { + 'file.txt': 'Hello there!', + }, + }, + ], + }); + }); + + it('creates outputs for the url', async () => { + await instance.handler(ctx); + + expect(ctx.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://github.com/myorg/myrepo/pull/123', + ); + }); + afterEach(() => { + mockFs.restore(); + jest.resetAllMocks(); + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts new file mode 100644 index 0000000000..3a00610a92 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -0,0 +1,243 @@ +/* + * 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 { readFile } from 'fs-extra'; +import path from 'path'; +import { parseRepoUrl } from './util'; + +import { + GithubCredentialsProvider, + ScmIntegrationRegistry, +} from '@backstage/integration'; +import { zipObject } from 'lodash'; +import { createTemplateAction } from '../../createTemplateAction'; +import { Octokit } from '@octokit/rest'; +import { InputError, CustomErrorBase } from '@backstage/errors'; +import { createPullRequest } from 'octokit-plugin-create-pull-request'; +import globby from 'globby'; + +class GithubResponseError extends CustomErrorBase {} + +type CreatePullRequestResponse = { + data: { html_url: string }; +}; + +export interface PullRequestCreator { + createPullRequest( + options: createPullRequest.Options, + ): Promise; +} + +export type PullRequestCreatorConstructor = ( + octokit: Octokit, +) => PullRequestCreator; + +export type GithubPullRequestActionInput = { + title: string; + branchName: string; + description: string; + owner?: string; + repo?: string; + repoUrl?: string; + host?: string; + targetPath?: string; + sourcePath?: string; +}; + +export type ClientFactoryInput = { + integrations: ScmIntegrationRegistry; + host: string; + owner: string; + repo: string; +}; + +export const defaultClientFactory = async ({ + integrations, + owner, + repo, + host = 'github.com', +}: ClientFactoryInput): Promise => { + const integrationConfig = integrations.github.byHost(host)?.config; + + if (!integrationConfig) { + throw new InputError(`No integration for host ${host}`); + } + + const credentialsProvider = GithubCredentialsProvider.create( + integrationConfig, + ); + + if (!credentialsProvider) { + throw new InputError( + `No matching credentials for host ${host}, please check your integrations config`, + ); + } + + const { token } = await credentialsProvider.getCredentials({ + url: `${host}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, + }); + + if (!token) { + throw new InputError( + `No token available for host: ${host}, with owner ${owner}, and repo ${repo}`, + ); + } + + const OctokitPR = Octokit.plugin(createPullRequest); + + return new OctokitPR({ + auth: token, + baseUrl: integrationConfig.apiBaseUrl, + }); +}; + +interface CreateGithubPullRequestActionOptions { + integrations: ScmIntegrationRegistry; + clientFactory?: (input: ClientFactoryInput) => Promise; +} + +export const createPublishGithubPullRequestAction = ({ + integrations, + clientFactory = defaultClientFactory, +}: CreateGithubPullRequestActionOptions) => { + return createTemplateAction({ + id: 'publish:github:pull-request', + schema: { + input: { + required: ['owner', 'repo', 'title', 'description', 'branchName'], + type: 'object', + properties: { + owner: { + type: 'string', + title: 'Repository owner', + description: 'The owner of the target repository', + }, + repo: { + type: 'string', + title: 'Repository', + description: 'The github repository to create the file in', + }, + branchName: { + type: 'string', + title: 'Branch Name', + description: 'The name for the branch', + }, + title: { + type: 'string', + title: 'Pull Request Name', + description: 'The name for the pull request', + }, + description: { + type: 'string', + title: 'Pull Request Description', + description: 'The description of the pull request', + }, + sourcePath: { + type: 'string', + title: 'Working Subdirectory', + description: + 'Subdirectory of working directory to copy changes from', + }, + targetPath: { + type: 'string', + title: 'Repository Subdirectory', + description: 'Subdirectory of repository to apply changes to', + }, + }, + }, + output: { + required: ['remoteUrl'], + type: 'object', + properties: { + remoteUrl: { + type: 'string', + title: 'Pull Request URL', + description: 'Link to the pull request in Github', + }, + }, + }, + }, + async handler(ctx) { + let { owner, repo } = ctx.input; + let host = 'github.com'; + const { + repoUrl, + branchName, + title, + description, + targetPath, + sourcePath, + } = ctx.input; + + if (repoUrl) { + const parsed = parseRepoUrl(repoUrl); + host = parsed.host; + owner = parsed.owner; + repo = parsed.repo; + } + + if (!host || !owner || !repo) { + throw new InputError( + 'must provide either valid repo URL or owner and repo as parameters', + ); + } + + const client = await clientFactory({ integrations, host, owner, repo }); + const fileRoot = sourcePath + ? path.join(ctx.workspacePath, sourcePath) + : ctx.workspacePath; + const localFilePaths = await globby(`${fileRoot}/**/*.*`); + + const fileContents = await Promise.all( + localFilePaths.map(p => readFile(p)), + ); + + const repoFilePaths = localFilePaths.map(p => { + const relativePath = path.relative(fileRoot, p); + return targetPath ? `${targetPath}/${relativePath}` : relativePath; + }); + + const changes = [ + { + files: zipObject( + repoFilePaths, + fileContents.map(buf => buf.toString()), + ), + commit: title, + }, + ]; + + try { + const response = await client.createPullRequest({ + owner, + repo, + title, + changes, + body: description, + head: branchName, + }); + + if (!response) { + throw new GithubResponseError('null response from Github'); + } + + ctx.output('remoteUrl', response.data.html_url); + } catch (e) { + throw new GithubResponseError('Pull request creation failed', e); + } + }, + }); +}; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts index 12fc90cc94..37074d6f57 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts @@ -18,7 +18,7 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { Gitlab } from '@gitbeaker/node'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -import { parseRepoUrl } from './util'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { createTemplateAction } from '../../createTemplateAction'; export function createPublishGitlabAction(options: { @@ -29,6 +29,7 @@ export function createPublishGitlabAction(options: { return createTemplateAction<{ repoUrl: string; repoVisibility: 'private' | 'internal' | 'public'; + sourcePath?: string; }>({ id: 'publish:gitlab', description: @@ -47,6 +48,11 @@ export function createPublishGitlabAction(options: { type: 'string', enum: ['private', 'public', 'internal'], }, + sourcePath: { + title: + 'Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the respository.', + type: 'string', + }, }, }, output: { @@ -106,7 +112,7 @@ export function createPublishGitlabAction(options: { const repoContentsUrl = `${remoteUrl}/-/blob/master`; await initRepoAndPush({ - dir: ctx.workspacePath, + dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl: http_url_to_repo as string, auth: { username: 'oauth2', diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts index 419922704d..537a2b882d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts @@ -15,6 +15,7 @@ */ export { createPublishGithubAction } from './github'; +export { createPublishGithubPullRequestAction } from './githubPullRequest'; export { createPublishAzureAction } from './azure'; export { createPublishGitlabAction } from './gitlab'; export { createPublishBitbucketAction } from './bitbucket'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts index 89bbf24574..07b3823e7b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts @@ -15,6 +15,21 @@ */ import { InputError } from '@backstage/errors'; +import { join as joinPath, normalize as normalizePath } from 'path'; + +export const getRepoSourceDirectory = ( + workspacePath: string, + sourcePath: string | undefined, +) => { + if (sourcePath) { + const safeSuffix = normalizePath(sourcePath).replace( + /^(\.\.(\/|\\|$))+/, + '', + ); + return joinPath(workspacePath, safeSuffix); + } + return workspacePath; +}; export const parseRepoUrl = (repoUrl: string) => { let parsed; diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 4823c19816..6a43bccab3 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder +## 0.8.2 + +### Patch Changes + +- 3f96a9d5a: Support auth by sending cookies in event stream request +- 98dd5da71: Add support for multiple links to post-scaffold task summary page +- Updated dependencies [1279a3325] +- Updated dependencies [4a4681b1b] +- Updated dependencies [97b60de98] +- Updated dependencies [b051e770c] +- Updated dependencies [98dd5da71] + - @backstage/core@0.7.4 + - @backstage/catalog-model@0.7.6 + ## 0.8.1 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 410e12a97d..0de3b5ee53 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.8.1", + "version": "0.8.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.9", - "@backstage/catalog-model": "^0.7.5", + "@backstage/catalog-model": "^0.7.6", "@backstage/config": "^0.1.4", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/integration": "^0.5.1", "@backstage/integration-react": "^0.1.1", "@backstage/plugin-catalog-react": "^0.1.4", @@ -60,9 +60,9 @@ "zen-observable": "^0.8.15" }, "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", diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index e266d06a91..6caa2a4446 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -196,7 +196,7 @@ export class ScaffolderClient implements ScaffolderApi { const url = `${baseUrl}/v2/tasks/${encodeURIComponent( taskId, )}/eventstream`; - const eventSource = new EventSource(url); + const eventSource = new EventSource(url, { withCredentials: true }); eventSource.addEventListener('log', (event: any) => { if (event.data) { try { diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 0353dff798..68bf588d01 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -26,6 +26,7 @@ import { Progress, SupportButton, useApi, + useRouteRef, WarningPanel, } from '@backstage/core'; import { useStarredEntities } from '@backstage/plugin-catalog-react'; @@ -39,6 +40,7 @@ import { ScaffolderFilter } from '../ScaffolderFilter'; import { ButtonGroup } from '../ScaffolderFilter/ScaffolderFilter'; import SearchToolbar from '../SearchToolbar/SearchToolbar'; import { TemplateCard, TemplateCardProps } from '../TemplateCard'; +import { registerComponentRouteRef } from '../../routes'; const useStyles = makeStyles(theme => ({ contentWrapper: { @@ -108,6 +110,8 @@ export const ScaffolderPageContents = () => { `${metadata.title}`.toLocaleUpperCase('en-US').includes(query) || metadata.tags?.join('').toLocaleUpperCase('en-US').indexOf(query) !== -1; + const registerComponentLink = useRouteRef(registerComponentRouteRef); + useEffect(() => { if (search.length === 0) { return setMatchingEntities(filteredEntities); @@ -132,14 +136,16 @@ export const ScaffolderPageContents = () => { /> - + {registerComponentLink && ( + + )} Create new software components using standard templates. Different templates create different kinds of components (services, websites, diff --git a/plugins/scaffolder/src/components/TaskPage/IconLink.test.tsx b/plugins/scaffolder/src/components/TaskPage/IconLink.test.tsx new file mode 100644 index 0000000000..8caf37e70d --- /dev/null +++ b/plugins/scaffolder/src/components/TaskPage/IconLink.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2020 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 { lightTheme } from '@backstage/theme'; +import { ThemeProvider } from '@material-ui/core'; +import CloudIcon from '@material-ui/icons/Cloud'; +import { render } from '@testing-library/react'; +import React from 'react'; +import { IconLink } from './IconLink'; + +describe('IconLink', () => { + it('should render an icon link', () => { + const rendered = render( + + + , + ); + + expect(rendered.queryByText('I am Link')).toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/components/TaskPage/IconLink.tsx b/plugins/scaffolder/src/components/TaskPage/IconLink.tsx new file mode 100644 index 0000000000..9bede214c6 --- /dev/null +++ b/plugins/scaffolder/src/components/TaskPage/IconLink.tsx @@ -0,0 +1,58 @@ +/* + * Copyright 2020 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 { IconComponent, Link } from '@backstage/core'; +import { Grid, LinkProps, makeStyles, Typography } from '@material-ui/core'; +import LanguageIcon from '@material-ui/icons/Language'; + +const useStyles = makeStyles({ + svgIcon: { + display: 'inline-block', + '& svg': { + display: 'inline-block', + fontSize: 'inherit', + verticalAlign: 'baseline', + }, + }, +}); + +export const IconLink = ( + props: { + href: string; + text?: string; + Icon?: IconComponent; + } & LinkProps, +) => { + const { href, text, Icon, ...linkProps } = props; + + const classes = useStyles(); + + return ( + + + + {Icon ? : } + + + + + {text || href} + + + + ); +}; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 96a857dbb5..cc1149ee61 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -22,28 +22,24 @@ import Step from '@material-ui/core/Step'; import StepLabel from '@material-ui/core/StepLabel'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; -import { generatePath, useParams } from 'react-router'; +import { useParams } from 'react-router'; import { useTaskEventStream } from '../hooks/useEventStream'; import LazyLog from 'react-lazylog/build/LazyLog'; -import { Link } from 'react-router-dom'; import { - Box, - Button, CircularProgress, Paper, StepButton, StepIconProps, } from '@material-ui/core'; -import { Status } from '../../types'; +import { Status, TaskOutput } from '../../types'; import { DateTime, Interval } from 'luxon'; import { useInterval } from 'react-use'; import Check from '@material-ui/icons/Check'; import Cancel from '@material-ui/icons/Cancel'; import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; -import { entityRoute } from '@backstage/plugin-catalog-react'; -import { parseEntityName } from '@backstage/catalog-model'; import classNames from 'classnames'; import { BackstageTheme } from '@backstage/theme'; +import { TaskPageLinks } from './TaskPageLinks'; // typings are wrong for this library, so fallback to not parsing types. const humanizeDuration = require('humanize-duration'); @@ -211,6 +207,9 @@ const TaskLogger = memo(({ log }: { log: string }) => { ); }); +const hasLinks = ({ entityRef, remoteUrl, links = [] }: TaskOutput): boolean => + !!(entityRef || remoteUrl || links.length > 0); + export const TaskPage = () => { const [userSelectedStepId, setUserSelectedStepId] = useState< string | undefined @@ -261,8 +260,8 @@ export const TaskPage = () => { taskStream.loading === false && !taskStream.task; - const entityRef = taskStream.output?.entityRef; - const remoteUrl = taskStream.output?.remoteUrl; + const { output } = taskStream; + return (
{ currentStepId={currentStepId} onUserStepChange={setUserSelectedStepId} /> - {(entityRef || remoteUrl) && ( - - {entityRef && ( - - )} - {remoteUrl && ( - - )} - + {output && hasLinks(output) && ( + )} diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx new file mode 100644 index 0000000000..cd51488242 --- /dev/null +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright 2020 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 { TaskPageLinks } from './TaskPageLinks'; +import { renderInTestApp } from '@backstage/test-utils'; +import { entityRouteRef } from '@backstage/plugin-catalog-react'; + +describe('TaskPageLinks', () => { + beforeEach(() => {}); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('renders the entityRef link', async () => { + const output = { entityRef: 'Component:default/my-app' }; + const { findByText } = await renderInTestApp( + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + const element = await findByText('Open in catalog'); + + expect(element).toBeInTheDocument(); + expect(element).toHaveAttribute( + 'href', + '/catalog/default/Component/my-app', + ); + }); + + it('renders the remoteUrl link', async () => { + const output = { remoteUrl: 'https://remote.url' }; + const { findByText } = await renderInTestApp( + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + const element = await findByText('Repo'); + + expect(element).toBeInTheDocument(); + expect(element).toHaveAttribute('href', 'https://remote.url'); + }); + + it('renders further links', async () => { + const output = { + links: [ + { url: 'https://first.url', title: 'Cool link 1' }, + { url: 'https://second.url', title: 'Cool link 2' }, + ], + }; + const { findByText } = await renderInTestApp( + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + let element = await findByText('Cool link 1'); + + expect(element).toBeInTheDocument(); + expect(element).toHaveAttribute('href', 'https://first.url'); + + element = await findByText('Cool link 2'); + + expect(element).toBeInTheDocument(); + expect(element).toHaveAttribute('href', 'https://second.url'); + }); +}); diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx new file mode 100644 index 0000000000..c0b55193c2 --- /dev/null +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx @@ -0,0 +1,69 @@ +/* + * 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 { parseEntityName } from '@backstage/catalog-model'; +import { IconComponent, IconKey, useApp, useRouteRef } from '@backstage/core'; +import { IconLink } from './IconLink'; +import { entityRouteRef } from '@backstage/plugin-catalog-react'; +import { Box } from '@material-ui/core'; +import LanguageIcon from '@material-ui/icons/Language'; +import { TaskOutput } from '../../types'; + +type TaskPageLinksProps = { + output: TaskOutput; +}; + +export const TaskPageLinks = ({ output }: TaskPageLinksProps) => { + const { entityRef, remoteUrl } = output; + let { links = [] } = output; + const app = useApp(); + const entityRoute = useRouteRef(entityRouteRef); + + const iconResolver = (key: IconKey | undefined): IconComponent => + key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon; + + if (remoteUrl) { + links = [{ url: remoteUrl, title: 'Repo' }, ...links]; + } + + if (entityRef) { + const entityName = parseEntityName(entityRef); + const target = entityRoute(entityName); + links = [ + { + url: target, + title: 'Open in catalog', + icon: 'catalog', + }, + ...links, + ]; + } + + return ( + + {links.map(({ url, title, icon }, i) => ( + + ))} + + ); +}; diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index 210fd8cb9b..4d28fabd34 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -16,7 +16,7 @@ import { useImmerReducer } from 'use-immer'; import { useEffect } from 'react'; import { scaffolderApiRef, LogEvent } from '../../api'; -import { ScaffolderTask, Status } from '../../types'; +import { ScaffolderTask, Status, TaskOutput } from '../../types'; import { Subscription, useApi } from '@backstage/core'; type Step = { @@ -26,8 +26,6 @@ type Step = { startedAt?: string; }; -type TaskOutput = { entityRef?: string } & { [key in string]: string }; - export type TaskStream = { loading: boolean; error?: Error; diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index 1aa25d8e18..be47daec46 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -23,7 +23,7 @@ import { } from '@backstage/core'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { scaffolderApiRef, ScaffolderClient } from './api'; -import { rootRouteRef } from './routes'; +import { rootRouteRef, registerComponentRouteRef } from './routes'; export const scaffolderPlugin = createPlugin({ id: 'scaffolder', @@ -42,6 +42,9 @@ export const scaffolderPlugin = createPlugin({ routes: { root: rootRouteRef, }, + externalRoutes: { + registerComponent: registerComponentRouteRef, + }, }); export const ScaffolderPage = scaffolderPlugin.provide( diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 413e8f4194..3cca269b84 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -13,7 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { createRouteRef } from '@backstage/core'; +import { createExternalRouteRef, createRouteRef } from '@backstage/core'; + +export const registerComponentRouteRef = createExternalRouteRef({ + id: 'register-component', + optional: true, +}); export const rootRouteRef = createRouteRef({ title: 'Create new entity', diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 6a2c2eede4..9ba480a441 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -64,3 +64,18 @@ export type ListActionsResponse = Array<{ output?: JSONSchema; }; }>; + +type OutputLink = { + url: string; + title?: string; + icon?: string; +}; + +export type TaskOutput = { + entityRef?: string; + /** @deprecated use the `links` property to link out to relevant resources */ + remoteUrl?: string; + links?: OutputLink[]; +} & { + [key: string]: unknown; +}; diff --git a/plugins/search/package.json b/plugins/search/package.json index d0eab8f808..942035159c 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/catalog-model": "^0.7.3", "@backstage/plugin-catalog-react": "^0.1.2", "@backstage/search-common": "^0.1.1", @@ -44,9 +44,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", diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 4b086c8e5e..cfed11acb8 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -46,9 +46,9 @@ "timeago.js": "^4.0.2" }, "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", diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 412d2fd905..14f3dcfe10 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -34,7 +34,7 @@ "dependencies": { "@backstage/catalog-model": "^0.7.3", "@backstage/plugin-catalog-react": "^0.1.3", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,9 +47,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", diff --git a/plugins/sonarqube/src/components/index.ts b/plugins/sonarqube/src/components/index.ts index 8f0786bb8e..56b9d05885 100644 --- a/plugins/sonarqube/src/components/index.ts +++ b/plugins/sonarqube/src/components/index.ts @@ -15,3 +15,4 @@ */ export * from './SonarQubeCard'; +export { isSonarQubeAvailable } from './useProjectKey'; diff --git a/plugins/sonarqube/src/components/useProjectKey.ts b/plugins/sonarqube/src/components/useProjectKey.ts index dcff79d972..c0f488b00c 100644 --- a/plugins/sonarqube/src/components/useProjectKey.ts +++ b/plugins/sonarqube/src/components/useProjectKey.ts @@ -18,6 +18,9 @@ import { Entity } from '@backstage/catalog-model'; export const SONARQUBE_PROJECT_KEY_ANNOTATION = 'sonarqube.org/project-key'; +export const isSonarQubeAvailable = (entity: Entity) => + Boolean(entity.metadata.annotations?.[SONARQUBE_PROJECT_KEY_ANNOTATION]); + export const useProjectKey = (entity: Entity) => { return entity?.metadata.annotations?.[SONARQUBE_PROJECT_KEY_ANNOTATION] ?? ''; }; diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 6ef9fe22af..4e06c68e3a 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.4", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", @@ -45,9 +45,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", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 5d8da62ca9..e172fae350 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,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", diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index f8e7a184fd..4342b32a3a 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -33,7 +33,7 @@ "dependencies": { "@backstage/config": "^0.1.4", "@backstage/catalog-model": "^0.7.5", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/plugin-catalog-react": "^0.1.4", "@backstage/test-utils": "^0.1.9", "@backstage/theme": "^0.2.5", @@ -49,9 +49,9 @@ "sanitize-html": "^2.3.2" }, "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", diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 5a92e4a256..d253f18fa6 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.1.1", "@backstage/theme": "^0.2.5", @@ -39,9 +39,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", diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 674e8ee57c..fde05caee8 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,9 +41,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", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 85bda39a65..d100bd85b8 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.7.3", + "@backstage/core": "^0.7.4", "@backstage/theme": "^0.2.5", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,9 +41,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", diff --git a/yarn.lock b/yarn.lock index b0b8c04313..0b74a64a59 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1834,194 +1834,6 @@ lodash "^4.17.19" to-fast-properties "^2.0.0" -"@backstage/catalog-model@^0.2.0": - version "0.7.5" - dependencies: - "@backstage/config" "^0.1.3" - "@types/json-schema" "^7.0.5" - "@types/yup" "^0.29.8" - ajv "^7.0.3" - json-schema "^0.2.5" - lodash "^4.17.15" - uuid "^8.0.0" - yup "^0.29.3" - -"@backstage/core@^0.3.0": - version "0.7.3" - dependencies: - "@backstage/config" "^0.1.4" - "@backstage/core-api" "^0.2.15" - "@backstage/errors" "^0.1.1" - "@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" - "@testing-library/react-hooks" "^3.4.2" - "@types/dagre" "^0.7.44" - "@types/prop-types" "^15.7.3" - "@types/react" "^16.9" - "@types/react-sparklines" "^1.7.0" - "@types/react-text-truncate" "^0.14.0" - classnames "^2.2.6" - clsx "^1.1.0" - d3-selection "^2.0.0" - d3-shape "^2.0.0" - d3-zoom "^2.0.0" - dagre "^0.8.5" - immer "^9.0.1" - lodash "^4.17.15" - material-table "^1.69.1" - prop-types "^15.7.2" - qs "^6.9.4" - rc-progress "^3.0.0" - react "^16.12.0" - react-dom "^16.12.0" - react-helmet "6.1.0" - react-hook-form "^6.6.0" - react-markdown "^5.0.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-sparklines "^1.7.0" - react-syntax-highlighter "^15.4.3" - react-text-truncate "^0.16.0" - react-use "^15.3.3" - remark-gfm "^1.0.0" - zen-observable "^0.8.15" - -"@backstage/core@^0.6.0": - version "0.7.3" - dependencies: - "@backstage/config" "^0.1.4" - "@backstage/core-api" "^0.2.15" - "@backstage/errors" "^0.1.1" - "@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" - "@testing-library/react-hooks" "^3.4.2" - "@types/dagre" "^0.7.44" - "@types/prop-types" "^15.7.3" - "@types/react" "^16.9" - "@types/react-sparklines" "^1.7.0" - "@types/react-text-truncate" "^0.14.0" - classnames "^2.2.6" - clsx "^1.1.0" - d3-selection "^2.0.0" - d3-shape "^2.0.0" - d3-zoom "^2.0.0" - dagre "^0.8.5" - immer "^9.0.1" - lodash "^4.17.15" - material-table "^1.69.1" - prop-types "^15.7.2" - qs "^6.9.4" - rc-progress "^3.0.0" - react "^16.12.0" - react-dom "^16.12.0" - react-helmet "6.1.0" - react-hook-form "^6.6.0" - react-markdown "^5.0.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-sparklines "^1.7.0" - react-syntax-highlighter "^15.4.3" - react-text-truncate "^0.16.0" - react-use "^15.3.3" - remark-gfm "^1.0.0" - zen-observable "^0.8.15" - -"@backstage/core@^0.6.1": - version "0.7.3" - dependencies: - "@backstage/config" "^0.1.4" - "@backstage/core-api" "^0.2.15" - "@backstage/errors" "^0.1.1" - "@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" - "@testing-library/react-hooks" "^3.4.2" - "@types/dagre" "^0.7.44" - "@types/prop-types" "^15.7.3" - "@types/react" "^16.9" - "@types/react-sparklines" "^1.7.0" - "@types/react-text-truncate" "^0.14.0" - classnames "^2.2.6" - clsx "^1.1.0" - d3-selection "^2.0.0" - d3-shape "^2.0.0" - d3-zoom "^2.0.0" - dagre "^0.8.5" - immer "^9.0.1" - lodash "^4.17.15" - material-table "^1.69.1" - prop-types "^15.7.2" - qs "^6.9.4" - rc-progress "^3.0.0" - react "^16.12.0" - react-dom "^16.12.0" - react-helmet "6.1.0" - react-hook-form "^6.6.0" - react-markdown "^5.0.2" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-sparklines "^1.7.0" - react-syntax-highlighter "^15.4.3" - react-text-truncate "^0.16.0" - react-use "^15.3.3" - remark-gfm "^1.0.0" - zen-observable "^0.8.15" - -"@backstage/plugin-catalog@^0.2.1": - version "0.5.2" - dependencies: - "@backstage/catalog-client" "^0.3.9" - "@backstage/catalog-model" "^0.7.5" - "@backstage/core" "^0.7.3" - "@backstage/errors" "^0.1.1" - "@backstage/integration" "^0.5.1" - "@backstage/integration-react" "^0.1.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" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - classnames "^2.2.6" - git-url-parse "^11.4.4" - react "^16.13.1" - react-dom "^16.13.1" - react-helmet "6.1.0" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - swr "^0.3.0" - -"@backstage/plugin-catalog@^0.3.1": - version "0.5.2" - dependencies: - "@backstage/catalog-client" "^0.3.9" - "@backstage/catalog-model" "^0.7.5" - "@backstage/core" "^0.7.3" - "@backstage/errors" "^0.1.1" - "@backstage/integration" "^0.5.1" - "@backstage/integration-react" "^0.1.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" - "@material-ui/lab" "4.0.0-alpha.45" - "@types/react" "^16.9" - classnames "^2.2.6" - git-url-parse "^11.4.4" - react "^16.13.1" - react-dom "^16.13.1" - react-helmet "6.1.0" - react-router "6.0.0-beta.0" - react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" - swr "^0.3.0" - "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2570,6 +2382,33 @@ stream-events "^1.0.1" xdg-basedir "^4.0.0" +"@google-cloud/storage@^5.8.0": + version "5.8.0" + resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.8.0.tgz#1f580e276f1d453790b382156421d1bcc4bd3f4b" + integrity sha512-WOShvBPOfkDXUzXMO+3j8Bzus+PFI9r1Ey9dLG2Zf458/PVuFTtaRWntd9ZiDG8g90zl2LmnA1JkDCreGUKr5g== + dependencies: + "@google-cloud/common" "^3.6.0" + "@google-cloud/paginator" "^3.0.0" + "@google-cloud/promisify" "^2.0.0" + arrify "^2.0.0" + async-retry "^1.3.1" + compressible "^2.0.12" + date-and-time "^0.14.2" + duplexify "^4.0.0" + extend "^3.0.2" + gaxios "^4.0.0" + gcs-resumable-upload "^3.1.3" + get-stream "^6.0.0" + hash-stream-validation "^0.2.2" + mime "^2.2.0" + mime-types "^2.0.8" + onetime "^5.1.0" + p-limit "^3.0.1" + pumpify "^2.0.0" + snakeize "^0.1.0" + stream-events "^1.0.1" + xdg-basedir "^4.0.0" + "@graphiql/toolkit@^0.1.0": version "0.1.1" resolved "https://registry.npmjs.org/@graphiql/toolkit/-/toolkit-0.1.1.tgz#a7da3ba460ceae27bcdc8f03831ca4f88f90f3d7" @@ -4302,6 +4141,11 @@ resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-5.3.2.tgz#b8ac43c5c3d00aef61a34cf744e315110c78deb4" integrity sha512-NxF1yfYOUO92rCx3dwvA2onF30Vdlg7YUkMVXkeptqpzA3tRLplThhFleV/UKWFgh7rpKu1yYRbvNDUtzSopKA== +"@octokit/openapi-types@^6.0.0": + version "6.0.0" + resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-6.0.0.tgz#7da8d7d5a72d3282c1a3ff9f951c8133a707480d" + integrity sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ== + "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/@octokit/plugin-enterprise-rest/-/plugin-enterprise-rest-6.0.1.tgz#e07896739618dab8da7d4077c658003775f95437" @@ -4327,6 +4171,14 @@ "@octokit/types" "^6.12.2" deprecation "^2.3.1" +"@octokit/plugin-rest-endpoint-methods@5.0.0": + version "5.0.0" + resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz#cf2cdeb24ea829c31688216a5b165010b61f9a98" + integrity sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg== + dependencies: + "@octokit/types" "^6.13.0" + deprecation "^2.3.1" + "@octokit/request-error@^2.0.0": version "2.0.2" resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.2.tgz#0e76b83f5d8fdda1db99027ea5f617c2e6ba9ed0" @@ -4350,7 +4202,7 @@ once "^1.4.0" universal-user-agent "^6.0.0" -"@octokit/rest@^18.0.0", "@octokit/rest@^18.0.12", "@octokit/rest@^18.1.0": +"@octokit/rest@^18.0.12", "@octokit/rest@^18.1.0": version "18.3.5" resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.3.5.tgz#a89903d46e0b4273bd3234674ec2777a651d68ab" integrity sha512-ZPeRms3WhWxQBEvoIh0zzf8xdU2FX0Capa7+lTca8YHmRsO3QNJzf1H3PcuKKsfgp91/xVDRtX91sTe1kexlbw== @@ -4360,6 +4212,16 @@ "@octokit/plugin-request-log" "^1.0.2" "@octokit/plugin-rest-endpoint-methods" "4.13.5" +"@octokit/rest@^18.1.1": + version "18.5.2" + resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.2.tgz#0369e554b7076e3749005147be94c661c7a5a74b" + integrity sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw== + dependencies: + "@octokit/core" "^3.2.3" + "@octokit/plugin-paginate-rest" "^2.6.2" + "@octokit/plugin-request-log" "^1.0.2" + "@octokit/plugin-rest-endpoint-methods" "5.0.0" + "@octokit/types@^5.0.0", "@octokit/types@^5.0.1": version "5.5.0" resolved "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz#e5f06e8db21246ca102aa28444cdb13ae17a139b" @@ -4367,13 +4229,20 @@ dependencies: "@types/node" ">= 8" -"@octokit/types@^6.0.0", "@octokit/types@^6.0.1", "@octokit/types@^6.0.3", "@octokit/types@^6.12.2", "@octokit/types@^6.8.2": +"@octokit/types@^6.0.0", "@octokit/types@^6.0.1", "@octokit/types@^6.0.3", "@octokit/types@^6.12.2": version "6.12.2" resolved "https://registry.npmjs.org/@octokit/types/-/types-6.12.2.tgz#5b44add079a478b8eb27d78cf384cc47e4411362" integrity sha512-kCkiN8scbCmSq+gwdJV0iLgHc0O/GTPY1/cffo9kECu1MvatLPh9E+qFhfRIktKfHEA6ZYvv6S1B4Wnv3bi3pA== dependencies: "@octokit/openapi-types" "^5.3.2" +"@octokit/types@^6.13.0": + version "6.13.0" + resolved "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0" + integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA== + dependencies: + "@octokit/openapi-types" "^6.0.0" + "@open-draft/until@^1.0.3": version "1.0.3" resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" @@ -4511,55 +4380,57 @@ 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.1.3": - version "0.1.3" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-0.1.3.tgz#5a116bf677dfad22088212dde398cf3e9b48d6e4" - integrity sha512-q+cnAvZmjLu0DcqRKJZJhWTVcKaA9j7cM44tx6JcshEyb3UgzrlECLvx4ethnYyrmhM1/FSpMLa+t3N5A1Xz4g== +"@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== dependencies: - "@backstage/catalog-model" "^0.2.0" - "@backstage/core" "^0.3.0" - "@backstage/plugin-catalog" "^0.2.1" + "@backstage/catalog-model" "^0.7.4" + "@backstage/core" "^0.7.3" + "@backstage/plugin-catalog" "^0.5.1" + "@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" + history "^5.0.0" moment "^2.27.0" react "^16.13.1" react-dom "^16.13.1" react-lazylog "^4.5.2" react-router "6.0.0-beta.0" react-router-dom "6.0.0-beta.0" - react-use "^15.3.3" + react-use "^15.3.6" -"@roadiehq/backstage-plugin-github-insights@^0.3.2": - version "0.3.2" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-0.3.2.tgz#2db83e172ae34738677bd47c3b754c31bb2b5987" - integrity sha512-eIhqQS0jTRcdTCu+GxrRCH1tq+kDrTb6mMJpD/6sGnE9QXgj5nKHIm6KHmFYEfIiukQrGxXY4lABNWstvi7hgA== +"@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== dependencies: - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.0" - "@backstage/theme" "^0.2.3" + "@backstage/catalog-model" "^0.7.4" + "@backstage/core" "^0.7.3" + "@backstage/theme" "^0.2.5" "@date-io/core" "2.10.7" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.57" - "@octokit/rest" "^18.0.0" - "@octokit/types" "^6.8.2" + "@octokit/rest" "^18.1.1" + "@octokit/types" "^6.13.0" history "^5.0.0" react "^16.13.1" react-dom "^16.13.1" react-router "^6.0.0-beta.0" - react-use "^17.1.0" + react-use "^17.2.1" -"@roadiehq/backstage-plugin-github-pull-requests@^0.7.6": - version "0.7.8" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-pull-requests/-/backstage-plugin-github-pull-requests-0.7.8.tgz#14a8e512b83de6cb5304b2a8e62c447b2bd6e311" - integrity sha512-sgBzIYC4GfZGbFpRixQBinf62laVqefRLHPLaoF2LuTMStXGcjUIGlqySmWNOXvpmGjCv3QIxh1NaxZBp6Z1Vw== +"@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== dependencies: - "@backstage/catalog-model" "^0.7.1" - "@backstage/core" "^0.6.1" + "@backstage/catalog-model" "^0.7.4" + "@backstage/core" "^0.7.3" "@material-ui/core" "^4.11.0" "@material-ui/icons" "^4.9.1" - "@octokit/rest" "^18.0.0" + "@octokit/rest" "^18.1.1" "@octokit/types" "^5.0.1" "@types/node-fetch" "^2.5.7" history "^5.0.0" @@ -4570,20 +4441,20 @@ react-router "6.0.0-beta.0" react-use "^15.3.6" -"@roadiehq/backstage-plugin-travis-ci@^0.4.5": - version "0.4.5" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-travis-ci/-/backstage-plugin-travis-ci-0.4.5.tgz#c80eb972951badd110173f5521aae65b8a5cfc53" - integrity sha512-+y5kFtVaPjXgf6naGVU2E3C33nvoXuaBbfYEmCeByg2t0b5jHMHzsTv2tV5/8zeAcus/5+yIkRCq+F1Tcbwutw== +"@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== dependencies: - "@backstage/catalog-model" "^0.7.0" - "@backstage/core" "^0.6.0" - "@backstage/plugin-catalog" "^0.3.1" - "@backstage/theme" "^0.2.1" + "@backstage/catalog-model" "^0.7.4" + "@backstage/core" "^0.7.3" + "@backstage/plugin-catalog" "^0.5.1" + "@backstage/theme" "^0.2.5" "@material-ui/core" "^4.9.1" "@material-ui/icons" "^4.9.1" "@material-ui/lab" "4.0.0-alpha.45" cross-fetch "^3.0.6" - date-fns "^2.16.1" + date-fns "^2.18.0" history "^5.0.0" moment "^2.29.1" react "^16.13.1" @@ -6259,13 +6130,13 @@ "@types/uglify-js" "*" "@types/html-webpack-plugin@*", "@types/html-webpack-plugin@^3.2.2": - version "3.2.4" - resolved "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-3.2.4.tgz#ed770ddfec53ed2aa6b5f4523acca291192235c6" - integrity sha512-WM0s78bfCIXnTlICf+8nWP0IvP+fn4YfiI3uxAX1K1PSRpzs0iysp03j4zR0xTgxSqF67TbOsHs49YXonRAkeQ== + version "3.2.5" + resolved "https://registry.npmjs.org/@types/html-webpack-plugin/-/html-webpack-plugin-3.2.5.tgz#58e94c0d57801903b2b77674d2b9ef6c4a65a6db" + integrity sha512-DhC7NTte+Ikw/zxp2w9qjcWtHqpShbUx7ASPUZ00trn1EOftoRtMmy8nS7F/mW8ASTA2JGMFX2bbuqqxiqs6mQ== dependencies: "@types/html-minifier" "*" - "@types/tapable" "*" - "@types/webpack" "*" + "@types/tapable" "^1" + "@types/webpack" "^4" "@types/http-assert@*": version "1.5.1" @@ -7002,10 +6873,10 @@ dependencies: "@types/react" "*" -"@types/tapable@*", "@types/tapable@^1.0.5": - version "1.0.5" - resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.5.tgz#9adbc12950582aa65ead76bffdf39fe0c27a3c02" - integrity sha512-/gG2M/Imw7cQFp8PGvz/SwocNrmKFjFsm5Pb8HdbHkZ1K8pmuPzOX4VeVoiEecFCVf4CsN1r3/BRvx+6sNqwtQ== +"@types/tapable@^1", "@types/tapable@^1.0.5": + version "1.0.7" + resolved "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.7.tgz#545158342f949e8fd3bfd813224971ecddc3fac4" + integrity sha512-0VBprVqfgFD7Ehb2vd8Lh9TG3jP98gvr8rgehQqzztZNI7o8zS8Ad4jyZneKELphpuE212D8J70LnSNQSyO6bQ== "@types/tar@^4.0.3": version "4.0.4" @@ -7134,14 +7005,14 @@ "@types/source-list-map" "*" source-map "^0.6.1" -"@types/webpack@*", "@types/webpack@^4.41.7", "@types/webpack@^4.41.8": - version "4.41.26" - resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.26.tgz#27a30d7d531e16489f9c7607c747be6bc1a459ef" - integrity sha512-7ZyTfxjCRwexh+EJFwRUM+CDB2XvgHl4vfuqf1ZKrgGvcS5BrNvPQqJh3tsZ0P6h6Aa1qClVHaJZszLPzpqHeA== +"@types/webpack@*", "@types/webpack@^4", "@types/webpack@^4.41.7", "@types/webpack@^4.41.8": + version "4.41.27" + resolved "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.27.tgz#f47da488c8037e7f1b2dbf2714fbbacb61ec0ffc" + integrity sha512-wK/oi5gcHi72VMTbOaQ70VcDxSQ1uX8S2tukBK9ARuGXrYM/+u4ou73roc7trXDNmCxCoerE8zruQqX/wuHszA== dependencies: "@types/anymatch" "*" "@types/node" "*" - "@types/tapable" "*" + "@types/tapable" "^1" "@types/uglify-js" "*" "@types/webpack-sources" "*" source-map "^0.6.0" @@ -8962,10 +8833,10 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -bl@^4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz#12d6287adc29080e22a705e5764b2a9522cdc489" - integrity sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg== +bl@^4.0.3, bl@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== dependencies: buffer "^5.5.0" inherits "^2.0.4" @@ -11408,6 +11279,11 @@ date-fns@^2.0.0-alpha.27, date-fns@^2.16.1: resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.18.0.tgz#08e50aee300ad0d2c5e054e3f0d10d8f9cdfe09e" integrity sha512-NYyAg4wRmGVU4miKq5ivRACOODdZRY3q5WLmOJSq8djyzftYphU7dTHLcEtLqEvfqMKQ0jVv91P4BAwIjsXIcw== +date-fns@^2.18.0: + version "2.19.0" + resolved "https://registry.npmjs.org/date-fns/-/date-fns-2.19.0.tgz#65193348635a28d5d916c43ec7ce6fbd145059e1" + integrity sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg== + dateformat@^3.0.0: version "3.0.3" resolved "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" @@ -15099,6 +14975,17 @@ http-errors@1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-errors@1.7.3, http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + http-errors@^1.7.3: version "1.8.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" @@ -15120,17 +15007,6 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - "http-parser-js@>=0.4.0 <0.4.11": version "0.4.10" resolved "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" @@ -16166,6 +16042,11 @@ is-unc-path@^1.0.0: dependencies: unc-path-regex "^0.1.2" +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + is-upper-case@^2.0.1: version "2.0.2" resolved "https://registry.npmjs.org/is-upper-case/-/is-upper-case-2.0.2.tgz#f1105ced1fe4de906a5f39553e7d3803fd804649" @@ -16861,6 +16742,11 @@ joycon@^2.2.5: resolved "https://registry.npmjs.org/joycon/-/joycon-2.2.5.tgz#8d4cf4cbb2544d7b7583c216fcdfec19f6be1615" integrity sha512-YqvUxoOcVPnCp0VU1/56f+iKSdvIRJYPznH22BdXV3xMk75SFXhWeJkZ8C9XxUWt1b5x2X1SxuFygW1U0FmkEQ== +js-base64@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/js-base64/-/js-base64-3.6.0.tgz#773e1de628f4f298d65a7e9842c50244751f5756" + integrity sha512-wVdUBYQeY2gY73RIlPrysvpYx+2vheGo8Y1SNQv/BzHToWpAZzJU7Z6uheKMAe+GLSBig5/Ps2nxg/8tRB73xg== + js-cookie@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.1.tgz#69e106dc5d5806894562902aa5baec3744e9b2b8" @@ -18003,12 +17889,13 @@ log-symbols@^3.0.0: dependencies: chalk "^2.4.2" -log-symbols@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== +log-symbols@^4.0.0, log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== dependencies: - chalk "^4.0.0" + chalk "^4.1.0" + is-unicode-supported "^0.1.0" log-update@^2.3.0: version "2.3.0" @@ -19877,6 +19764,13 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== +octokit-plugin-create-pull-request@^3.9.3: + version "3.9.3" + resolved "https://registry.npmjs.org/octokit-plugin-create-pull-request/-/octokit-plugin-create-pull-request-3.9.3.tgz#f99f53907ac322a3494cc970514a023d7b659e2b" + integrity sha512-lTyNnCRoT4IvCQx2Cb4eFMqg8aIpsaDd59MNwf4OPnWAJM7hT6g7RW/icImvAzZLR4t5ENSLNzWarv2XqLL+Lg== + dependencies: + "@octokit/types" "^6.8.2" + oidc-token-hash@^5.0.0: version "5.0.0" resolved "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.0.tgz#acdfb1f4310f58e64d5d74a4e8671a426986e888" @@ -19991,16 +19885,17 @@ optionator@^0.9.1: word-wrap "^1.2.3" ora@^5.3.0: - version "5.3.0" - resolved "https://registry.npmjs.org/ora/-/ora-5.3.0.tgz#fb832899d3a1372fe71c8b2c534bbfe74961bb6f" - integrity sha512-zAKMgGXUim0Jyd6CXK9lraBnD3H5yPGBPPOkC23a2BG6hsm4Zu6OQSjQuEtV0BHDf4aKHcUFvJiGRrFuW3MG8g== + version "5.4.0" + resolved "https://registry.npmjs.org/ora/-/ora-5.4.0.tgz#42eda4855835b9cd14d33864c97a3c95a3f56bf4" + integrity sha512-1StwyXQGoU6gdjYkyVcqOLnVlbKj+6yPNNOxJVgpt9t4eksKjiriiHuxktLYkgllwk+D6MbC4ihH84L1udRXPg== dependencies: - bl "^4.0.3" + bl "^4.1.0" chalk "^4.1.0" cli-cursor "^3.1.0" cli-spinners "^2.5.0" is-interactive "^1.0.0" - log-symbols "^4.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" strip-ansi "^6.0.0" wcwidth "^1.0.1" @@ -20750,10 +20645,10 @@ pg-pool@^3.2.2: resolved "https://registry.npmjs.org/pg-pool/-/pg-pool-3.2.2.tgz#a560e433443ed4ad946b84d774b3f22452694dff" integrity sha512-ORJoFxAlmmros8igi608iVEbQNNZlp89diFVx6yV5v+ehmpMY9sK6QgpmgoXbmkNaBAx8cOOZh9g80kJv1ooyA== -pg-protocol@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.3.0.tgz#3c8fb7ca34dbbfcc42776ce34ac5f537d6e34770" - integrity sha512-64/bYByMrhWULUaCd+6/72c9PMWhiVFs3EVxl9Ct6a3v/U8+rKgqP2w+kKg/BIGgMJyB+Bk/eNivT32Al+Jghw== +pg-protocol@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.4.0.tgz#43a71a92f6fe3ac559952555aa3335c8cb4908be" + integrity sha512-El+aXWcwG/8wuFICMQjM5ZSAm6OWiJicFdNYo+VY3QP+8vI4SvLIWVe51PppTzMhikUJR+PsyIFKqfdXPz/yxA== pg-types@1.*: version "1.13.0" @@ -20792,15 +20687,15 @@ pg@^6.1.0: semver "4.3.2" pg@^8.3.0: - version "8.4.2" - resolved "https://registry.npmjs.org/pg/-/pg-8.4.2.tgz#2aa58166a23391e91d56a7ea57c6d99931c0642a" - integrity sha512-E9FlUrrc7w3+sbRmL1CSw99vifACzB2TjhMM9J5w9D1LIg+6un0jKkpHS1EQf2CWhKhec2bhrBLVMmUBDbjPRQ== + version "8.5.1" + resolved "https://registry.npmjs.org/pg/-/pg-8.5.1.tgz#34dcb15f6db4a29c702bf5031ef2e1e25a06a120" + integrity sha512-9wm3yX9lCfjvA98ybCyw2pADUivyNWT/yIP4ZcDVpMN0og70BUWYEGXPCTAQdGTAqnytfRADb7NERrY1qxhIqw== dependencies: buffer-writer "2.0.0" packet-reader "1.0.0" pg-connection-string "^2.4.0" pg-pool "^3.2.2" - pg-protocol "^1.3.0" + pg-protocol "^1.4.0" pg-types "^2.1.0" pgpass "1.x" @@ -21916,6 +21811,16 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" +raw-body@^2.4.1: + version "2.4.1" + resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz#30ac82f98bb5ae8c152e67149dac8d55153b168c" + integrity sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA== + dependencies: + bytes "3.1.0" + http-errors "1.7.3" + iconv-lite "0.4.24" + unpipe "1.0.0" + raw-loader@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" @@ -22476,7 +22381,7 @@ react-use@^15.3.3, react-use@^15.3.6: ts-easing "^0.2.0" tslib "^2.0.0" -react-use@^17.1.0: +react-use@^17.2.1: version "17.2.1" resolved "https://registry.npmjs.org/react-use/-/react-use-17.2.1.tgz#c81e12544115ed049c7deba1e3bb3d977dfee9b8" integrity sha512-9r51/at7/Nr/nEP4CsHz+pl800EAqhIY9R6O68m68kaWc8slDAfx1UrIedQqpsb4ImddFYb+6hF1i5Vj4u4Cnw==