diff --git a/.changeset/five-knives-flash.md b/.changeset/five-knives-flash.md new file mode 100644 index 0000000000..9076f6f456 --- /dev/null +++ b/.changeset/five-knives-flash.md @@ -0,0 +1,10 @@ +--- +'@backstage/backend-common': patch +'@backstage/create-app': patch +'@backstage/integration': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-scaffolder-backend': patch +--- + +The default `@octokit/rest` dependency was bumped to `"^18.5.3"`. diff --git a/.changeset/flat-keys-happen.md b/.changeset/flat-keys-happen.md new file mode 100644 index 0000000000..ce01f86bcf --- /dev/null +++ b/.changeset/flat-keys-happen.md @@ -0,0 +1,6 @@ +--- +'@backstage/cli': patch +'@backstage/core': patch +--- + +Fix some transitive dependency warnings in yarn diff --git a/.changeset/friendly-readers-promise.md b/.changeset/friendly-readers-promise.md new file mode 100644 index 0000000000..30607d5db2 --- /dev/null +++ b/.changeset/friendly-readers-promise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Fetch relations in batches in `useRelatedEntities` diff --git a/.changeset/fuzzy-dancers-complain.md b/.changeset/fuzzy-dancers-complain.md new file mode 100644 index 0000000000..a23e6bc3df --- /dev/null +++ b/.changeset/fuzzy-dancers-complain.md @@ -0,0 +1,6 @@ +--- +'@backstage/core': patch +'@backstage/plugin-catalog-import': patch +--- + +Bump react-hook-form version to be the same for the entire project. diff --git a/.changeset/khaki-rice-accept.md b/.changeset/khaki-rice-accept.md new file mode 100644 index 0000000000..72f1c635ed --- /dev/null +++ b/.changeset/khaki-rice-accept.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Expose `BitbucketRepositoryParser` introduced in [#5295](https://github.com/backstage/backstage/pull/5295) diff --git a/.changeset/nice-shirts-fetch.md b/.changeset/nice-shirts-fetch.md new file mode 100644 index 0000000000..e9537e63b3 --- /dev/null +++ b/.changeset/nice-shirts-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Added lax option to backstage-cli app:build command diff --git a/.changeset/old-horses-brake.md b/.changeset/old-horses-brake.md new file mode 100644 index 0000000000..ea41019b23 --- /dev/null +++ b/.changeset/old-horses-brake.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Fix GithubPR built-in action `credentialsProvider.getCredentials` URL. +Adding Documentation for GitHub PR built-in action. diff --git a/.changeset/silly-tables-build.md b/.changeset/silly-tables-build.md new file mode 100644 index 0000000000..cb2fd73950 --- /dev/null +++ b/.changeset/silly-tables-build.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-kubernetes-backend': patch +--- + +Adds @backstage/plugin-kubernetes-common library to share types between kubernetes frontend and backend. diff --git a/.changeset/small-sheep-smoke.md b/.changeset/small-sheep-smoke.md new file mode 100644 index 0000000000..fd917159d3 --- /dev/null +++ b/.changeset/small-sheep-smoke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +bump `jsonschema` from 1.2.7 to 1.4.0 diff --git a/.changeset/stupid-feet-reply.md b/.changeset/stupid-feet-reply.md new file mode 100644 index 0000000000..d7213b1bdc --- /dev/null +++ b/.changeset/stupid-feet-reply.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Add support for non-organization accounts in GitHub Discovery diff --git a/.changeset/tame-masks-juggle.md b/.changeset/tame-masks-juggle.md new file mode 100644 index 0000000000..91ee43945a --- /dev/null +++ b/.changeset/tame-masks-juggle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Close eventSource upon completion of a scaffolder task diff --git a/.changeset/ten-cups-relax.md b/.changeset/ten-cups-relax.md new file mode 100644 index 0000000000..9e32ad3856 --- /dev/null +++ b/.changeset/ten-cups-relax.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Make git config optional for techdocs feedback links diff --git a/docs/cli/commands.md b/docs/cli/commands.md index 324d17463c..a99d4cd9d0 100644 --- a/docs/cli/commands.md +++ b/docs/cli/commands.md @@ -111,6 +111,7 @@ Usage: backstage-cli app:build Options: --stats Write bundle stats to output directory + --lax Do not require environment variables to be set --config <path> Config files to load instead of app-config.yaml (default: []) -h, --help display help for command ``` @@ -486,6 +487,7 @@ Usage: backstage-cli config:print [options] Options: --package <name> Only load config schema that applies to the given package + --lax Do not require environment variables to be set --frontend Print only the frontend configuration --with-secrets Include secrets in the printed configuration --format <format> Format to print the configuration in, either json or yaml [yaml] @@ -506,6 +508,7 @@ Usage: backstage-cli config:check [options] Options: --package <name> Only load config schema that applies to the given package + --lax Do not require environment variables to be set --config <path> Config files to load instead of app-config.yaml (default: []) -h, --help display help for command ``` diff --git a/docs/features/kubernetes/installation.md b/docs/features/kubernetes/installation.md index 10968b427c..36bb724be6 100644 --- a/docs/features/kubernetes/installation.md +++ b/docs/features/kubernetes/installation.md @@ -8,44 +8,36 @@ The Kubernetes feature is a plugin to Backstage, and it is exposed as a tab when viewing entities in the software catalog. If you haven't setup Backstage already, read the -[Getting Started](../../getting-started/index.md). +[Getting Started](../../getting-started/index.md) guide. ## Adding the Kubernetes frontend plugin -The first step is to add the frontend Kubernetes plugin to your Backstage -application. Navigate to your new Backstage application directory. And then to +The first step is to add the Kubernetes frontend plugin to your Backstage +application. Navigate to your new Backstage application directory, and then to your `packages/app` directory, and install the `@backstage/plugin-kubernetes` package. ```bash -cd my-backstage-app/ +# From your Backstage root directory cd packages/app yarn add @backstage/plugin-kubernetes ``` Once the package has been installed, you need to import the plugin in your app -by adding the "Kubernetes" tab to the catalog entity page. In -`packages/app/src/components/catalog/EntityPage.tsx`, you'll add a router to get -to the tab, and add the tab itself. - -`EntityPage.tsx`: +by adding the "Kubernetes" tab to the respective catalog pages. ```tsx -import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes'; +// In packages/app/src/components/catalog/EntityPage.tsx +import { EntityKubernetesContent } from '@backstage/plugin-kubernetes'; -// ... - -const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( - - // ... - } - /> - // ... - -); +// You can add the tab to any number of pages, the service page is shown as an +// example here +const serviceEntityPage = ( + + {/* other tabs... */} + + + ``` That's it! But now, we need the Kubernetes Backend plugin for the frontend to @@ -57,17 +49,16 @@ Navigate to `packages/backend` of your Backstage app, and install the `@backstage/plugin-kubernetes-backend` package. ```bash -cd my-backstage-app/ +# From your Backstage root directory cd packages/backend yarn add @backstage/plugin-kubernetes-backend ``` Create a file called `kubernetes.ts` inside `packages/backend/src/plugins/` and -add the following - -`kubernetes.ts`: +add the following: ```typescript +// In packages/backend/src/plugins/kubernetes.ts import { createRouter } from '@backstage/plugin-kubernetes-backend'; import { PluginEnvironment } from '../types'; @@ -83,18 +74,15 @@ And import the plugin to `packages/backend/src/index.ts`. There are three lines of code you'll need to add, and they should be added near similar code in your existing Backstage backend. -`index.ts`: - ```typescript +// In packages/backend/src/index.ts import kubernetes from './plugins/kubernetes'; - // ... - -const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes')); - -// ... - -apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv)); +async function main() { + // ... + const kubernetesEnv = useHotMemoize(module, () => createEnv('kubernetes')); + // ... + apiRouter.use('/kubernetes', await kubernetes(kubernetesEnv)); ``` That's it! The Kubernetes frontend and backend have now been added to your diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md index 1afdff418a..320b82d0ad 100644 --- a/docs/features/software-catalog/configuration.md +++ b/docs/features/software-catalog/configuration.md @@ -37,6 +37,13 @@ The locations added through static configuration cannot be removed through the catalog locations API. To remove these locations, you must remove them from the configuration. +Syntax errors or other types of errors present in `catalog-info.yaml` files will +be logged for investigation. Errors do not cause processing to abort. + +When multiple `catalog-info.yaml` files with the same `metadata.name` property +are discovered, one will be processed and all others will be skipped. This +action is logged for further investigation. + ### Integration Processors Integrations may simply provide a mechanism to handle `url` location type for an @@ -111,8 +118,7 @@ catalog: > deleting entities will not work in this mode.** A common use case for this configuration is when organizations have a remote -source that should be mirrored into backstage. If we want backstage to be a -mirror of this remote source we cannot allow users to also register entities -with e.g. +source that should be mirrored into Backstage. To make Backstage a mirror of +this remote source, users cannot also register new entities with e.g. the [catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) plugin. diff --git a/docs/features/software-catalog/installation.md b/docs/features/software-catalog/installation.md index df473f886f..0b622fb093 100644 --- a/docs/features/software-catalog/installation.md +++ b/docs/features/software-catalog/installation.md @@ -18,6 +18,7 @@ The catalog frontend plugin should be installed in your `app` package, which is created as a part of `@backstage/create-app`. To install the package, run: ```bash +# From your Backstage root directory cd packages/app yarn add @backstage/plugin-catalog ``` @@ -103,6 +104,7 @@ The catalog backend should be installed in your `backend` package, which is created as a part of `@backstage/create-app`. To install the package, run: ```bash +# From your Backstage root directory cd packages/backend yarn add @backstage/plugin-catalog-backend ``` diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md index 96eea3c750..5acaf0e499 100644 --- a/docs/features/software-templates/installation.md +++ b/docs/features/software-templates/installation.md @@ -20,6 +20,7 @@ The scaffolder frontend plugin should be installed in your `app` package, which is created as a part of `@backstage/create-app`. To install the package, run: ```bash +# From your Backstage root directory cd packages/app yarn add @backstage/plugin-scaffolder ``` @@ -57,6 +58,7 @@ The scaffolder backend should be installed in your `backend` package, which is created as a part of `@backstage/create-app`. To install the package, run: ```bash +# From your Backstage root directory cd packages/backend yarn add @backstage/plugin-scaffolder-backend ``` @@ -262,6 +264,6 @@ library. By default it will use the [spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/37e35b910afc7d1270855aed0ec4718aba366c91/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile) docker image. -If you are running backstage from a Docker container and you want to avoid +If you are running Backstage from a Docker container and you want to avoid calling a container inside a container, you can set up Cookiecutter in your own image, this will use the local installation instead. diff --git a/docs/features/techdocs/configuring-ci-cd.md b/docs/features/techdocs/configuring-ci-cd.md index a9844ef528..0dec7ec24e 100644 --- a/docs/features/techdocs/configuring-ci-cd.md +++ b/docs/features/techdocs/configuring-ci-cd.md @@ -148,6 +148,23 @@ jobs: - uses: actions/setup-node@v2 - uses: actions/setup-python@v2 + # the 2 steps below can be removed if you aren't using plantuml in your documentation + - name: setup java + uses: actions/setup-java@v2 + with: + distribution: 'zulu' + java-version: '11' + - name: download, validate, install plantuml and its dependencies + run: | + curl -o plantuml.jar -L http://sourceforge.net/projects/plantuml/files/plantuml.1.2021.4.jar/download + echo "be498123d20eaea95a94b174d770ef94adfdca18 plantuml.jar" | sha1sum -c - + mv plantuml.jar /opt/plantuml.jar + mkdir -p "$HOME/.local/bin" + echo $'#!/bin/sh\n\njava -jar '/opt/plantuml.jar' ${@}' >> "$HOME/.local/bin/plantuml" + chmod +x "$HOME/.local/bin/plantuml" + echo "$HOME/.local/bin" >> $GITHUB_PATH + sudo apt-get install -y graphviz + - name: Install techdocs-cli run: sudo npm install -g @techdocs/cli diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md index 223b276d68..520db2245c 100644 --- a/docs/features/techdocs/getting-started.md +++ b/docs/features/techdocs/getting-started.md @@ -22,7 +22,7 @@ Navigate to your new Backstage application directory. And then to your `packages/app` directory, and install the `@backstage/plugin-techdocs` package. ```bash -cd my-backstage-app/ +# From your Backstage root directory cd packages/app yarn add @backstage/plugin-techdocs ``` @@ -54,7 +54,7 @@ Navigate to `packages/backend` of your Backstage app, and install the `@backstage/plugin-techdocs-backend` package. ```bash -cd my-backstage-app/ +# From your Backstage root directory cd packages/backend yarn add @backstage/plugin-techdocs-backend ``` diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index 181f541929..4dbc6bfa48 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -135,3 +135,31 @@ const themeOptions = createThemeOptions({ }, }); ``` + +## Custom Logo + +In addition to a custom theme, you can also customize the logo displayed at the +far top left of the site. + +In your frontend app, locate `src/components/Root/` folder. You'll find two +components: + +- `LogoFull.tsx` - A larger logo used when the Sidebar navigation is opened. +- `LogoIcon.tsx` - A smaller logo used when the sidebar navigation is closed. + +To replace the images, you can simply replace the relevant code in those +components with raw SVG definitions. + +You can also use another web image format such as PNG by importing it. To do +this, place your new image into a new subdirectory such as +`src/components/Root/logo/my-company-logo.png`, and then add this code: + +```jsx +import MyCustomLogoFull from './logo/my-company-logo.png'; + +//... + +const LogoFull = () => { + return ; +}; +``` diff --git a/docs/plugins/integrating-plugin-into-service-catalog.md b/docs/plugins/integrating-plugin-into-service-catalog.md index 01de779635..047b6f14e6 100644 --- a/docs/plugins/integrating-plugin-into-service-catalog.md +++ b/docs/plugins/integrating-plugin-into-service-catalog.md @@ -1,7 +1,7 @@ --- id: integrating-plugin-into-service-catalog title: Integrate into the Service Catalog -description: Documentation on How to integrate plugin into service catalog +description: How to integrate a plugin into service catalog --- > This is an advanced use case and currently is an experimental feature. Expect diff --git a/docs/tutorials/switching-sqlite-postgres.md b/docs/tutorials/switching-sqlite-postgres.md index a759b7dc49..fafd2ad586 100644 --- a/docs/tutorials/switching-sqlite-postgres.md +++ b/docs/tutorials/switching-sqlite-postgres.md @@ -17,11 +17,11 @@ switch between database backends. ## Install PostgreSQL -First, swap out SQLite for PostgreSQL in your `backend` package: +First, add PostgreSQL to your `backend` package: ```shell +# From your Backstage root directory cd packages/backend -yarn remove sqlite3 yarn add pg ``` @@ -46,7 +46,6 @@ backend: + #ssl: require # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) + #ca: # if you have a CA file and want to verify it you can uncomment this section + #$file: /ca/server.crt - ``` If you have an `app-config.local.yaml` for local development, a similar update diff --git a/microsite/README.md b/microsite/README.md index 9589def3e3..285622f1f8 100644 --- a/microsite/README.md +++ b/microsite/README.md @@ -1,3 +1,9 @@ +# Backstage Documentation + +This folder holds the service and configuration that runs Backstage's documentation hosted at https://backstage.io. + +It pulls content in from the [root `/docs`](../docs/) folder and builds it into the resulting HTML web site. + This website was created with [Docusaurus](https://docusaurus.io/). # What's In This Document @@ -10,6 +16,8 @@ This website was created with [Docusaurus](https://docusaurus.io/). # Getting Started +Testing the web site locally is a great way to see what final website will look like after publishing, and is ideal for testing more complex changes, large updates to navigation, or complex page designs that may include special alignment, which may not otherwise be validated through continuous integration. + ## Installation ``` @@ -22,7 +30,7 @@ $ yarn install $ yarn start ``` -This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. +This command starts a local development server and opens up a browser window. Most content changes made to the `docs/` root folder are reflected live without having to restart the server. ## Build @@ -57,9 +65,9 @@ my-docusaurus/ siteConfig.js ``` -# Editing Content +## Editing Content -## Editing an existing docs page +### Editing an existing docs page Edit docs by navigating to `docs/` and editing the corresponding document: @@ -76,7 +84,7 @@ Edit me... For more information about docs, click [here](https://docusaurus.io/docs/en/navigation) -## Editing an existing blog post +### Editing an existing blog post Edit blog posts by navigating to `website/blog` and editing the corresponding post: @@ -93,9 +101,9 @@ Edit me... For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) -# Adding Content +## Adding Content -## Adding a new docs page to an existing sidebar +### Adding a new docs page to an existing sidebar 1. Create the doc as a new markdown file in `/docs`, example `docs/newly-created-doc.md`: @@ -126,7 +134,7 @@ My new content here.. For more information about adding new docs, click [here](https://docusaurus.io/docs/en/navigation) -## Adding a new blog post +### Adding a new blog post 1. Make sure there is a header link to your blog in `website/siteConfig.js`: @@ -157,7 +165,7 @@ Lorem Ipsum... For more information about blog posts, click [here](https://docusaurus.io/docs/en/adding-blog) -## Adding items to your site's top navigation bar +### Adding items to your site's top navigation bar 1. Add links to docs, custom pages or external links by editing the headerLinks field of `website/siteConfig.js`: @@ -181,7 +189,7 @@ For more information about blog posts, click [here](https://docusaurus.io/docs/e For more information about the navigation bar, click [here](https://docusaurus.io/docs/en/navigation) -## Adding custom pages +### Adding custom pages 1. Docusaurus uses React components to build pages. The components are saved as .js files in `website/pages/en`: 1. If you want your page to show up in your navigation header, you will need to update `website/siteConfig.js` to add to the `headerLinks` element: @@ -199,11 +207,11 @@ For more information about the navigation bar, click [here](https://docusaurus.i } ``` -For more information about custom pages, click [here](https://docusaurus.io/docs/en/custom-pages). +Learn more about [Docusaurus custom pages](https://docusaurus.io/docs/en/custom-pages). -# Full Documentation +## Full Documentation -Full documentation can be found on the [website](https://docusaurus.io/). +Full documentation can be found on the [Docusaurus website](https://docusaurus.io/). ## Additional notes diff --git a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md index 65102c919f..7289878bc1 100644 --- a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md +++ b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md @@ -45,7 +45,7 @@ The only thing you need to do is to start the app: ```bash cd my-app -yarn start +yarn dev ``` And you are good to go! 👍 @@ -101,18 +101,12 @@ We provide a collection of public Backstage plugins (look for packages with the Install in your app’s package folder (`/packages/app`) with: ```bash +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin- ``` -Then add it to your app's `plugin.ts` file to import and register it: - -`/packages/app/src/plugin.ts`: - -```js -export { plugin as PluginName } from '@backstage/plugin-'; -``` - -A plugin registers its own `route` in the app — read the documentation for the specific plugin you are installing for more information on that. +After that, you inject the plugin into the application where you want it to be exposed. Please read the documentation for the specific plugin you are installing for more information. ### Creating an internal plugin diff --git a/packages/app/package.json b/packages/app/package.json index 402d09ade4..fb1ea02699 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -39,7 +39,7 @@ "@backstage/theme": "^0.2.6", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", - "@octokit/rest": "^18.0.12", + "@octokit/rest": "^18.5.3", "@roadiehq/backstage-plugin-buildkite": "^1.0.0", "@roadiehq/backstage-plugin-github-insights": "^1.0.0", "@roadiehq/backstage-plugin-github-pull-requests": "^1.0.0", diff --git a/packages/backend-common/README.md b/packages/backend-common/README.md index dc08d89416..dc69790bc3 100644 --- a/packages/backend-common/README.md +++ b/packages/backend-common/README.md @@ -8,6 +8,8 @@ error handling and similar. Add the library to your backend package: ```sh +# From your Backstage root directory +cd packages/backend yarn add @backstage/backend-common ``` diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 58b9aa36d9..9969d0ea27 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -12,7 +12,6 @@ import cors from 'cors'; import Docker from 'dockerode'; import { ErrorRequestHandler } from 'express'; import express from 'express'; -import { Format } from 'logform'; import { GithubCredentialsProvider } from '@backstage/integration'; import { GitHubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; @@ -64,7 +63,7 @@ export class BitbucketUrlReader implements UrlReader { } // @public (undocumented) -export const coloredFormat: Format; +export const coloredFormat: winston.Logform.Format; // @public @deprecated export const createDatabase: typeof createDatabaseClient; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 998ac475d9..64e3137f7c 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -35,7 +35,7 @@ "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.1", "@google-cloud/storage": "^5.8.0", - "@octokit/rest": "^18.0.12", + "@octokit/rest": "^18.5.3", "@types/cors": "^2.8.6", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", diff --git a/packages/backend/package.json b/packages/backend/package.json index 440ceca8e5..0422088d64 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -47,7 +47,7 @@ "@backstage/plugin-techdocs-backend": "^0.7.0", "@backstage/plugin-todo-backend": "^0.1.3", "@gitbeaker/node": "^28.0.2", - "@octokit/rest": "^18.0.12", + "@octokit/rest": "^18.5.3", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.1", "example-app": "^0.2.25", diff --git a/packages/backend/src/plugins/catalog.ts b/packages/backend/src/plugins/catalog.ts index e58d8cd3eb..7651836e2a 100644 --- a/packages/backend/src/plugins/catalog.ts +++ b/packages/backend/src/plugins/catalog.ts @@ -42,7 +42,7 @@ export default async function createPlugin( } = await builder.build(); // TODO(jhaals): run and manage in background. - processingEngine.start(); + await processingEngine.start(); return await createRouter({ entitiesCatalog, diff --git a/packages/backend/src/plugins/kubernetes.ts b/packages/backend/src/plugins/kubernetes.ts index 131c5c8eee..7906765533 100644 --- a/packages/backend/src/plugins/kubernetes.ts +++ b/packages/backend/src/plugins/kubernetes.ts @@ -15,12 +15,11 @@ */ import { createRouter } from '@backstage/plugin-kubernetes-backend'; -import { Router } from 'express'; import { PluginEnvironment } from '../types'; export default async function createPlugin({ logger, config, -}: PluginEnvironment): Promise { +}: PluginEnvironment) { return await createRouter({ logger, config }); } diff --git a/packages/cli/README.md b/packages/cli/README.md index cd6ad8094e..eb5e0e461b 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -7,13 +7,13 @@ This package provides a CLI for developing Backstage plugins and apps. Install the package via npm or Yarn: ```sh -$ npm install --save @backstage/cli +npm install --save @backstage/cli ``` or ```sh -$ yarn add @backstage/cli +yarn add @backstage/cli ``` ## Development diff --git a/packages/cli/package.json b/packages/cli/package.json index 1110efefe6..46e99b762b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -86,6 +86,7 @@ "lodash": "^4.17.19", "mini-css-extract-plugin": "^0.9.0", "ora": "^5.3.0", + "postcss": "^8.1.0", "raw-loader": "^4.0.1", "react": "^16.0.0", "react-dev-utils": "^11.0.4", diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index 21189e9427..0da4112646 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -30,6 +30,7 @@ export default async (cmd: Command) => { ...(await loadCliConfig({ args: cmd.config, fromPackage: name, + mockEnv: cmd.lax, })), }); }; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index c770239464..ae39941106 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -29,6 +29,7 @@ export function registerCommands(program: CommanderStatic) { .command('app:build') .description('Build an app for a production release') .option('--stats', 'Write bundle stats to output directory') + .option('--lax', 'Do not require environment variables to be set') .option(...configOption) .action(lazy(() => import('./app/build').then(m => m.default))); diff --git a/packages/core-api/README.md b/packages/core-api/README.md index f7b8b6c337..a4325a9c5d 100644 --- a/packages/core-api/README.md +++ b/packages/core-api/README.md @@ -7,13 +7,13 @@ This package provides the core API used by Backstage plugins and apps. Do not install this package directly, it is reexported by `@backstage/core`. Install that instead: ```sh -$ npm install --save @backstage/core +npm install --save @backstage/core ``` or ```sh -$ yarn add @backstage/core +yarn add @backstage/core ``` ## Documentation diff --git a/packages/core/README.md b/packages/core/README.md index 6d8519d46f..0f827d6074 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -7,13 +7,13 @@ This package provides the core API used by Backstage plugins and apps. Install the package via npm or Yarn: ```sh -$ npm install --save @backstage/core +npm install --save @backstage/core ``` or ```sh -$ yarn add @backstage/core +yarn add @backstage/core ``` ## Documentation diff --git a/packages/core/package.json b/packages/core/package.json index 59391e122a..21303d52fc 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -48,6 +48,7 @@ "d3-shape": "^2.0.0", "d3-zoom": "^2.0.0", "dagre": "^0.8.5", + "history": "^5.0.0", "immer": "^9.0.1", "lodash": "^4.17.15", "material-table": "^1.69.1", @@ -57,7 +58,7 @@ "react": "^16.12.0", "react-dom": "^16.12.0", "react-helmet": "6.1.0", - "react-hook-form": "^6.6.0", + "react-hook-form": "^6.15.4", "react-markdown": "^5.0.2", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index 138bcaec8d..e2b5b04a60 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -29,7 +29,7 @@ "@backstage/plugin-scaffolder-backend": "^{{version '@backstage/plugin-scaffolder-backend'}}", "@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}", "@gitbeaker/node": "^28.0.2", - "@octokit/rest": "^18.0.12", + "@octokit/rest": "^18.5.3", "dockerode": "^3.2.1", "express": "^4.17.1", "express-promise-router": "^4.1.0", diff --git a/packages/dev-utils/README.md b/packages/dev-utils/README.md index 4b08a5ca6d..85d961ad0b 100644 --- a/packages/dev-utils/README.md +++ b/packages/dev-utils/README.md @@ -9,13 +9,13 @@ This package provides utilities that help in developing plugins for Backstage, l Install the package via npm or Yarn: ```sh -$ npm install --save-dev @backstage/dev-utils +npm install --save-dev @backstage/dev-utils ``` or ```sh -$ yarn add -D @backstage/dev-utils +yarn add -D @backstage/dev-utils ``` ## Documentation diff --git a/packages/integration/package.json b/packages/integration/package.json index dcfae4ea05..b7c8bee957 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -32,8 +32,8 @@ "@backstage/config": "^0.1.2", "cross-fetch": "^3.0.6", "git-url-parse": "^11.4.4", - "@octokit/rest": "^18.0.12", - "@octokit/auth-app": "^2.10.5", + "@octokit/rest": "^18.5.3", + "@octokit/auth-app": "^3.4.0", "luxon": "^1.25.0" }, "devDependencies": { diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 01aabac0de..4a4c1431c5 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -14,7 +14,9 @@ ] }, "dependencies": { - "@backstage/theme": "^0.2.0" + "@backstage/theme": "^0.2.0", + "react": "^16.12.0", + "react-dom": "^16.12.0" }, "devDependencies": { "@storybook/addon-actions": "^6.1.11", diff --git a/packages/test-utils/README.md b/packages/test-utils/README.md index 0f95d4ab26..ace7339733 100644 --- a/packages/test-utils/README.md +++ b/packages/test-utils/README.md @@ -7,13 +7,13 @@ This package provides utilities that can be used to test plugins and apps for Ba Install the package via npm or Yarn: ```sh -$ npm install --save-dev @backstage/test-utils +npm install --save-dev @backstage/test-utils ``` or ```sh -$ yarn add -D @backstage/test-utils +yarn add -D @backstage/test-utils ``` ## Documentation diff --git a/packages/theme/README.md b/packages/theme/README.md index 9855d6730d..de94ff1623 100644 --- a/packages/theme/README.md +++ b/packages/theme/README.md @@ -7,13 +7,13 @@ This package provides the extended Material UI Theme(s) that power Backstage. Install the package via npm or Yarn: ```sh -$ npm install --save @backstage/theme +npm install --save @backstage/theme ``` or ```sh -$ yarn add @backstage/theme +yarn add @backstage/theme ``` ## Documentation diff --git a/plugins/api-docs/README.md b/plugins/api-docs/README.md index dac4b6ff4d..60d8986d9b 100644 --- a/plugins/api-docs/README.md +++ b/plugins/api-docs/README.md @@ -28,15 +28,15 @@ To link that a component provides or consumes an API, see the [`providesApis`](h 1. Install the API docs plugin ```bash -# packages/app - +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-api-docs ``` 2. Add the `ApiExplorerPage` extension to the app: ```tsx -// packages/app/src/App.tsx +// In packages/app/src/App.tsx import { ApiExplorerPage } from '@backstage/plugin-api-docs'; @@ -56,7 +56,7 @@ import { } from '@backstage/plugin-api-docs'; const apiPage = ( - + @@ -80,7 +80,7 @@ const apiPage = ( - + ); // ... diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx index b157e898ca..fa956cd042 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx @@ -96,14 +96,18 @@ describe('', () => { }, ], }; - catalogApi.getEntityByName.mockResolvedValue({ - apiVersion: 'v1', - kind: 'API', - metadata: { - name: 'target-name', - namespace: 'my-namespace', - }, - spec: {}, + catalogApi.getEntities.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'API', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }, + ], }); const { getByText } = await renderInTestApp( diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx index 5db3ec2b48..e721f6e918 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx @@ -96,14 +96,18 @@ describe('', () => { }, ], }; - catalogApi.getEntityByName.mockResolvedValue({ - apiVersion: 'v1', - kind: 'API', - metadata: { - name: 'target-name', - namespace: 'my-namespace', - }, - spec: {}, + catalogApi.getEntities.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'API', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }, + ], }); const { getByText } = await renderInTestApp( diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx index bb9b2e60d9..67d95605e4 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx @@ -96,14 +96,18 @@ describe('', () => { }, ], }; - catalogApi.getEntityByName.mockResolvedValue({ - apiVersion: 'v1', - kind: 'API', - metadata: { - name: 'target-name', - namespace: 'my-namespace', - }, - spec: {}, + catalogApi.getEntities.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'API', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }, + ], }); const { getByText } = await renderInTestApp( diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx index 7944b3f90a..104b360ff6 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx @@ -101,14 +101,18 @@ describe('', () => { }, ], }; - catalogApi.getEntityByName.mockResolvedValue({ - apiVersion: 'v1', - kind: 'Component', - metadata: { - name: 'target-name', - namespace: 'my-namespace', - }, - spec: {}, + catalogApi.getEntities.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }, + ], }); const { getByText } = await renderInTestApp( diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx index ab1751b9ba..c7c6bacc84 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx @@ -101,14 +101,18 @@ describe('', () => { }, ], }; - catalogApi.getEntityByName.mockResolvedValue({ - apiVersion: 'v1', - kind: 'Component', - metadata: { - name: 'target-name', - namespace: 'my-namespace', - }, - spec: {}, + catalogApi.getEntities.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }, + ], }); const { getByText } = await renderInTestApp( diff --git a/plugins/app-backend/README.md b/plugins/app-backend/README.md index 2dc302accb..a35cecfb9c 100644 --- a/plugins/app-backend/README.md +++ b/plugins/app-backend/README.md @@ -7,7 +7,9 @@ This backend plugin can be installed to serve static content of a Backstage app. Add both this package and your local frontend app package as dependencies to your backend, for example ```bash -yarn add @backstage/plugin-app-backend example-app +# From your Backstage root directory +cd packages/backend +yarn add @backstage/plugin-app-backend app ``` By adding the app package as a dependency we ensure that it is built as part of the backend, and that it can be resolved at runtime. diff --git a/plugins/bitrise/README.md b/plugins/bitrise/README.md index 411db3198e..1add03df23 100644 --- a/plugins/bitrise/README.md +++ b/plugins/bitrise/README.md @@ -8,9 +8,9 @@ Welcome to the Bitrise plugin! ## Installation ```sh -# The plugin must be added in the app package -$ cd packages/app -$ yarn add @backstage/plugin-bitrise +# From your Backstage root directory +cd packages/app +yarn add @backstage/plugin-bitrise ``` Bitrise Plugin exposes an entity tab component named `EntityBitriseContent`. You can include it in the @@ -22,7 +22,7 @@ import { EntityBitriseContent } from '@backstage/plugin-bitrise'; // Farther down at the website declaration const websiteEntityPage = ( - + {/* Place the following section where you want the tab to appear */} diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 7649067a44..99df0acd09 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -73,7 +73,8 @@ "@types/yup": "^0.29.8", "msw": "^0.21.2", "sqlite3": "^5.0.0", - "supertest": "^6.1.3" + "supertest": "^6.1.3", + "wait-for-expect": "^3.0.2" }, "files": [ "dist", diff --git a/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.test.ts index 73f4281a51..1f01b77f42 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.test.ts @@ -17,21 +17,73 @@ import { getVoidLogger } from '@backstage/backend-common'; import { BitbucketDiscoveryProcessor } from './BitbucketDiscoveryProcessor'; import { ConfigReader } from '@backstage/config'; import { LocationSpec } from '@backstage/catalog-model'; -import { - BitbucketClient, - BitbucketRepositoryParser, - PagedResponse, -} from './bitbucket'; +import { BitbucketRepositoryParser, PagedResponse } from './bitbucket'; import { results } from './index'; +import { RequestHandler, rest } from 'msw'; +import { setupServer } from 'msw/node'; -function pagedResponse(values: any): PagedResponse { - return { - values: values, - isLastPage: true, - } as PagedResponse; +const server = setupServer(); + +function setupStubs(projects: any[]) { + function pagedResponse(values: any): PagedResponse { + return { + values: values, + isLastPage: true, + } as PagedResponse; + } + + function stubbedProject( + project: string, + repos: string[], + ): RequestHandler { + return rest.get( + `https://bitbucket.mycompany.com/api/rest/1.0/projects/${project}/repos`, + (_, res, ctx) => { + const response = []; + for (const repo of repos) { + response.push({ + slug: repo, + links: { + self: [ + { + href: `https://bitbucket.mycompany.com/projects/${project}/repos/${repo}/browse`, + }, + ], + }, + }); + } + return res(ctx.json(pagedResponse(response))); + }, + ); + } + + server.use( + rest.get( + `https://bitbucket.mycompany.com/api/rest/1.0/projects`, + (_, res, ctx) => { + return res( + ctx.json( + pagedResponse( + projects.map(p => { + return { key: p.key }; + }), + ), + ), + ); + }, + ), + ); + + for (const project of projects) { + server.use(stubbedProject(project.key, project.repos)); + } } describe('BitbucketDiscoveryProcessor', () => { + beforeAll(() => server.listen()); + afterEach(() => server.resetHandlers()); + afterAll(() => server.close()); + afterEach(() => jest.resetAllMocks()); describe('reject unrelated entries', () => { @@ -81,58 +133,29 @@ describe('BitbucketDiscoveryProcessor', () => { const processor = BitbucketDiscoveryProcessor.fromConfig( new ConfigReader({ integrations: { - bitbucket: [{ host: 'bitbucket.mycompany.com', token: 'blob' }], + bitbucket: [ + { + host: 'bitbucket.mycompany.com', + token: 'blob', + apiBaseUrl: 'https://bitbucket.mycompany.com/api/rest/1.0', + }, + ], }, }), { logger: getVoidLogger() }, ); it('output all repositories', async () => { + setupStubs([ + { key: 'backstage', repos: ['backstage'] }, + { key: 'demo', repos: ['demo'] }, + ]); const location: LocationSpec = { type: 'bitbucket-discovery', target: 'https://bitbucket.mycompany.com/projects/*/repos/*/catalog.yaml', }; - jest - .spyOn(BitbucketClient.prototype, 'listProjects') - .mockResolvedValue( - pagedResponse([{ key: 'backstage' }, { key: 'demo' }]), - ); - jest - .spyOn(BitbucketClient.prototype, 'listRepositories') - .mockResolvedValueOnce( - pagedResponse([ - { - slug: 'backstage', - links: { - self: [ - { - href: - 'https://bitbucket.mycompany.com/projects/backstage/repos/backstage/browse', - }, - ], - }, - }, - ]), - ); - jest - .spyOn(BitbucketClient.prototype, 'listRepositories') - .mockResolvedValueOnce( - pagedResponse([ - { - slug: 'demo', - links: { - self: [ - { - href: - 'https://bitbucket.mycompany.com/projects/demo/repos/demo/browse', - }, - ], - }, - }, - ]), - ); const emitter = jest.fn(); await processor.readLocation(location, false, emitter); @@ -158,44 +181,16 @@ describe('BitbucketDiscoveryProcessor', () => { }); it('output repositories with wildcards', async () => { + setupStubs([ + { key: 'backstage', repos: ['backstage', 'techdocs-cli'] }, + { key: 'demo', repos: ['demo'] }, + ]); const location: LocationSpec = { type: 'bitbucket-discovery', target: 'https://bitbucket.mycompany.com/projects/backstage/repos/techdocs-*/catalog.yaml', }; - jest - .spyOn(BitbucketClient.prototype, 'listProjects') - .mockResolvedValue(pagedResponse([{ key: 'backstage' }])); - jest - .spyOn(BitbucketClient.prototype, 'listRepositories') - .mockResolvedValueOnce( - pagedResponse([ - { slug: 'backstage' }, - { - slug: 'techdocs-cli', - links: { - self: [ - { - href: - 'https://bitbucket.mycompany.com/projects/backstage/repos/techdocs-cli/browse', - }, - ], - }, - }, - { - slug: 'techdocs-container', - links: { - self: [ - { - href: - 'https://bitbucket.mycompany.com/projects/backstage/repos/techdocs-container/browse', - }, - ], - }, - }, - ]), - ); const emitter = jest.fn(); await processor.readLocation(location, false, emitter); @@ -208,46 +203,15 @@ describe('BitbucketDiscoveryProcessor', () => { }, optional: true, }); - expect(emitter).toHaveBeenCalledWith({ - type: 'location', - location: { - type: 'url', - target: - 'https://bitbucket.mycompany.com/projects/backstage/repos/techdocs-container/browse/catalog.yaml', - }, - optional: true, - }); }); it('filter unrelated repositories', async () => { + setupStubs([{ key: 'backstage', repos: ['test', 'abctest', 'testxyz'] }]); const location: LocationSpec = { type: 'bitbucket-discovery', target: 'https://bitbucket.mycompany.com/projects/backstage/repos/test/catalog.yaml', }; - jest - .spyOn(BitbucketClient.prototype, 'listProjects') - .mockResolvedValue(pagedResponse([{ key: 'backstage' }])); - jest - .spyOn(BitbucketClient.prototype, 'listRepositories') - .mockResolvedValue( - pagedResponse([ - { slug: 'abstest' }, - { slug: 'testxyz' }, - { - slug: 'test', - links: { - self: [ - { - href: - 'https://bitbucket.mycompany.com/projects/backstage/repos/test', - }, - ], - }, - }, - ]), - ); - const emitter = jest.fn(); await processor.readLocation(location, false, emitter); @@ -256,7 +220,7 @@ describe('BitbucketDiscoveryProcessor', () => { location: { type: 'url', target: - 'https://bitbucket.mycompany.com/projects/backstage/repos/test/catalog.yaml', + 'https://bitbucket.mycompany.com/projects/backstage/repos/test/browse/catalog.yaml', }, optional: true, }); @@ -277,26 +241,27 @@ describe('BitbucketDiscoveryProcessor', () => { const processor = BitbucketDiscoveryProcessor.fromConfig( new ConfigReader({ integrations: { - bitbucket: [{ host: 'bitbucket.mycompany.com', token: 'blob' }], + bitbucket: [ + { + host: 'bitbucket.mycompany.com', + token: 'blob', + apiBaseUrl: 'https://bitbucket.mycompany.com/api/rest/1.0', + }, + ], }, }), { parser: customRepositoryParser, logger: getVoidLogger() }, ); it('use custom repository parser', async () => { + setupStubs([{ key: 'backstage', repos: ['test'] }]); + const location: LocationSpec = { type: 'bitbucket-discovery', target: 'https://bitbucket.mycompany.com/projects/backstage/repos/test/catalog.yaml', }; - jest - .spyOn(BitbucketClient.prototype, 'listProjects') - .mockResolvedValue(pagedResponse([{ key: 'backstage' }])); - jest - .spyOn(BitbucketClient.prototype, 'listRepositories') - .mockResolvedValue(pagedResponse([{ slug: 'test' }])); - const emitter = jest.fn(); await processor.readLocation(location, false, emitter); diff --git a/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.ts index f92a5c6f7c..399adfbc14 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.ts @@ -22,11 +22,11 @@ import { } from '@backstage/integration'; import { LocationSpec } from '@backstage/catalog-model'; import { - Repository, BitbucketRepositoryParser, BitbucketClient, defaultRepositoryParser, paginated, + BitbucketRepository, } from './bitbucket'; import { CatalogProcessor, CatalogProcessorEmit } from './types'; @@ -66,20 +66,19 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { return false; } - const bitbucketConfig = this.integrations.bitbucket.byUrl(location.target) - ?.config; - if (!bitbucketConfig) { + const integration = this.integrations.bitbucket.byUrl(location.target); + if (!integration) { throw new Error( `There is no Bitbucket integration that matches ${location.target}. Please add a configuration entry for it under integrations.bitbucket`, ); - } else if (bitbucketConfig.host === 'bitbucket.org') { + } else if (integration.config.host === 'bitbucket.org') { throw new Error( `Component discovery for Bitbucket Cloud is not yet supported`, ); } const client = new BitbucketClient({ - config: bitbucketConfig, + config: integration.config, }); const startTimestamp = Date.now(); this.logger.info(`Reading Bitbucket repositories from ${location.target}`); @@ -90,9 +89,9 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { for (const repository of result.matches) { for await (const entity of this.parser({ - client: client, - repository: repository, - path: catalogPath, + integration: integration, + target: `${repository.links.self[0].href}${catalogPath}`, + logger: this.logger, })) { emit(entity); } @@ -159,5 +158,5 @@ function escapeRegExp(str: string): RegExp { type Result = { scanned: number; - matches: Repository[]; + matches: BitbucketRepository[]; }; diff --git a/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.test.ts b/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.test.ts index ab5080f446..3d42d9c4ba 100644 --- a/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.test.ts @@ -14,9 +14,9 @@ * limitations under the License. */ import { defaultRepositoryParser } from './BitbucketRepositoryParser'; -import { Project, Repository } from './types'; -import { BitbucketClient } from './client'; import { results } from '../index'; +import { getVoidLogger } from '@backstage/backend-common'; +import { BitbucketIntegration } from '@backstage/integration'; describe('BitbucketRepositoryParser', () => { describe('defaultRepositoryParser', () => { @@ -34,15 +34,9 @@ describe('BitbucketRepositoryParser', () => { ), ]; const actual = await defaultRepositoryParser({ - client: {} as BitbucketClient, - repository: { - project: {} as Project, - slug: 'repo-slug', - links: { - self: [{ href: browseUrl }], - }, - } as Repository, - path: path, + integration: {} as BitbucketIntegration, + target: `${browseUrl}${path}`, + logger: getVoidLogger(), }); let i = 0; diff --git a/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.ts b/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.ts index f786b6dac8..9ad038f9ec 100644 --- a/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.ts +++ b/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.ts @@ -13,25 +13,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Repository } from './types'; import { CatalogProcessorResult } from '../types'; import { results } from '../index'; -import { BitbucketClient } from './client'; +import { Logger } from 'winston'; +import { BitbucketIntegration } from '@backstage/integration'; export type BitbucketRepositoryParser = (options: { - client: BitbucketClient; - repository: Repository; - path: string; + integration: BitbucketIntegration; + target: string; + logger: Logger; }) => AsyncIterable; export const defaultRepositoryParser: BitbucketRepositoryParser = async function* defaultRepositoryParser({ - repository, - path, + target, }) { yield results.location( { type: 'url', - target: `${repository.links.self[0].href}${path}`, + target: target, }, // Not all locations may actually exist, since the user defined them as a wildcard pattern. // Thus, we emit them as optional and let the downstream processor find them while not outputting diff --git a/plugins/catalog-backend/src/ingestion/processors/bitbucket/client.ts b/plugins/catalog-backend/src/ingestion/processors/bitbucket/client.ts index 601461dcf5..c3c27aedfc 100644 --- a/plugins/catalog-backend/src/ingestion/processors/bitbucket/client.ts +++ b/plugins/catalog-backend/src/ingestion/processors/bitbucket/client.ts @@ -41,15 +41,6 @@ export class BitbucketClient { ); } - async getRaw( - projectKey: string, - repo: string, - path: string, - ): Promise { - const request = `${this.config.apiBaseUrl}/projects/${projectKey}/repos/${repo}/raw/${path}`; - return fetch(request, getBitbucketRequestOptions(this.config)); - } - private async pagedRequest( endpoint: string, options?: ListOptions, diff --git a/plugins/catalog-backend/src/ingestion/processors/bitbucket/index.ts b/plugins/catalog-backend/src/ingestion/processors/bitbucket/index.ts index ba2a2b3afe..06effffcd2 100644 --- a/plugins/catalog-backend/src/ingestion/processors/bitbucket/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/bitbucket/index.ts @@ -14,7 +14,7 @@ * limitations under the License. */ export { BitbucketClient, paginated } from './client'; -export type { PagedResponse } from './client'; -export * from './types'; -export type { BitbucketRepositoryParser } from './BitbucketRepositoryParser'; export { defaultRepositoryParser } from './BitbucketRepositoryParser'; +export type { PagedResponse } from './client'; +export type { BitbucketRepository } from './types'; +export type { BitbucketRepositoryParser } from './BitbucketRepositoryParser'; diff --git a/plugins/catalog-backend/src/ingestion/processors/bitbucket/types.ts b/plugins/catalog-backend/src/ingestion/processors/bitbucket/types.ts index 75dd372faa..32dc28eb98 100644 --- a/plugins/catalog-backend/src/ingestion/processors/bitbucket/types.ts +++ b/plugins/catalog-backend/src/ingestion/processors/bitbucket/types.ts @@ -13,16 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export type Project = { - key: string; -}; - -export type Repository = { - project: Project; +export type BitbucketRepository = { + project: { + key: string; + }; slug: string; - links: Record; -}; - -export type Link = { - href: string; + links: Record< + string, + { + href: string; + }[] + >; }; diff --git a/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts b/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts index 15280b96b8..88bddc93fb 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts @@ -156,7 +156,7 @@ describe('github', () => { describe('getOrganizationRepositories', () => { it('read repositories', async () => { const input: QueryResponse = { - organization: { + repositoryOwner: { repositories: { nodes: [ { diff --git a/plugins/catalog-backend/src/ingestion/processors/github/github.ts b/plugins/catalog-backend/src/ingestion/processors/github/github.ts index e07ea8917b..f6cfcb8901 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/github.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/github.ts @@ -20,7 +20,8 @@ import { graphql } from '@octokit/graphql'; // Graphql types export type QueryResponse = { - organization: Organization; + organization?: Organization; + repositoryOwner?: Organization | User; }; export type Organization = { @@ -41,6 +42,7 @@ export type User = { avatarUrl?: string; email?: string; name?: string; + repositories?: Connection; }; export type Team = { @@ -228,28 +230,27 @@ export async function getOrganizationRepositories( org: string, ): Promise<{ repositories: Repository[] }> { const query = ` - query repositories($org: String!, $cursor: String) { - organization(login: $org) { - name - repositories(first: 100, after: $cursor) { - nodes { - name - url - isArchived - } - pageInfo { - hasNextPage - endCursor + query repositories($org: String!, $cursor: String) { + repositoryOwner(login: $org) { + login + repositories(first: 100, after: $cursor) { + nodes { + name + url + isArchived + } + pageInfo { + hasNextPage + endCursor + } } } - } - } - `; + }`; const repositories = await queryWithPaging( client, query, - r => r.organization?.repositories, + r => r.repositoryOwner?.repositories, x => x, { org }, ); diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts index 8aedc8889b..a92cc9ed3b 100644 --- a/plugins/catalog-backend/src/ingestion/processors/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/index.ts @@ -35,3 +35,5 @@ export * from './types'; export { UrlReaderProcessor } from './UrlReaderProcessor'; export { parseEntityYaml } from './util/parse'; export { results }; + +export type { BitbucketRepositoryParser } from './bitbucket'; diff --git a/plugins/catalog-backend/src/next/Context/TransactionValue.test.ts b/plugins/catalog-backend/src/next/Context/TransactionValue.test.ts new file mode 100644 index 0000000000..97e554d0f6 --- /dev/null +++ b/plugins/catalog-backend/src/next/Context/TransactionValue.test.ts @@ -0,0 +1,37 @@ +/* + * 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 { TransactionValue } from './TransactionValue'; +import { Knex } from 'knex'; +import { BackgroundContext } from './BackgroundContext'; + +describe('TransactionValue Context', () => { + it('should be able to store tx values and retrieve them from a context', () => { + const tx = {} as Knex.Transaction; + const ctx = new BackgroundContext(); + + const nextCtx = TransactionValue.in(ctx, tx); + + expect(TransactionValue.from(nextCtx)).toBe(tx); + }); + + it('should throw when there is no tx value in the context', () => { + const ctx = new BackgroundContext(); + + expect(() => TransactionValue.from(ctx)).toThrow( + /No transaction available in context/, + ); + }); +}); diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts new file mode 100644 index 0000000000..376af6e748 --- /dev/null +++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts @@ -0,0 +1,161 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase'; + +import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine'; +import { Stitcher } from './Stitcher'; +import { CatalogProcessingOrchestrator } from './types'; +import waitForExpect from 'wait-for-expect'; + +describe('DefaultCatalogProcessingEngine', () => { + const db = ({ + transaction: jest.fn(), + getProcessableEntities: jest.fn(), + updateProcessedEntity: jest.fn(), + } as unknown) as jest.Mocked; + const orchestrator: jest.Mocked = { + process: jest.fn(), + }; + const stitcher = ({ + stitch: jest.fn(), + } as unknown) as jest.Mocked; + + beforeEach(() => { + jest.resetAllMocks(); + }); + it('should process stuff', async () => { + orchestrator.process.mockResolvedValue({ + ok: true, + completedEntity: { + apiVersion: '1', + kind: 'Location', + metadata: { name: 'test' }, + }, + relations: [], + errors: [], + deferredEntities: [], + state: new Map(), + }); + const engine = new DefaultCatalogProcessingEngine( + getVoidLogger(), + [], + db, + orchestrator, + stitcher, + ); + + db.transaction.mockImplementation(cb => cb((() => {}) as any)); + + db.getProcessableEntities + .mockImplementation(async () => { + await engine.stop(); + return { items: [] }; + }) + .mockResolvedValueOnce({ + items: [ + { + entityRef: 'foo', + id: '1', + unprocessedEntity: { + apiVersion: '1', + kind: 'Location', + metadata: { name: 'test' }, + }, + state: new Map(), + nextUpdateAt: '', + lastDiscoveryAt: '', + }, + ], + }); + + await engine.start(); + await waitForExpect(() => { + expect(orchestrator.process).toBeCalledTimes(1); + expect(orchestrator.process).toBeCalledWith({ + entity: { + apiVersion: '1', + kind: 'Location', + metadata: { name: 'test' }, + }, + state: expect.anything(), + }); + }); + await engine.stop(); + }); + + it('should process stuff even if the first attempt fail', async () => { + orchestrator.process.mockResolvedValue({ + ok: true, + completedEntity: { + apiVersion: '1', + kind: 'Location', + metadata: { name: 'test' }, + }, + relations: [], + errors: [], + deferredEntities: [], + state: new Map(), + }); + const engine = new DefaultCatalogProcessingEngine( + getVoidLogger(), + [], + db, + orchestrator, + stitcher, + ); + + db.transaction.mockImplementation(cb => cb((() => {}) as any)); + + db.getProcessableEntities + .mockImplementation(async () => { + await engine.stop(); + return { items: [] }; + }) + .mockRejectedValueOnce(new Error('I FAILED')) + .mockResolvedValueOnce({ + items: [ + { + entityRef: 'foo', + id: '1', + unprocessedEntity: { + apiVersion: '1', + kind: 'Location', + metadata: { name: 'test' }, + }, + state: new Map(), + nextUpdateAt: '', + lastDiscoveryAt: '', + }, + ], + }); + + await engine.start(); + await waitForExpect(() => { + expect(orchestrator.process).toBeCalledTimes(1); + expect(orchestrator.process).toBeCalledWith({ + entity: { + apiVersion: '1', + kind: 'Location', + metadata: { name: 'test' }, + }, + state: expect.anything(), + }); + }); + await engine.stop(); + }); +}); diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts index e724fe79d8..55cb8e2029 100644 --- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts @@ -16,6 +16,7 @@ import { stringifyEntityRef } from '@backstage/catalog-model'; import { Logger } from 'winston'; +import { ProcessingDatabase } from './database/types'; import { Stitcher } from './Stitcher'; import { CatalogProcessingEngine, @@ -23,44 +24,46 @@ import { EntityProvider, EntityProviderConnection, EntityProviderMutation, - ProcessingStateManager, } from './types'; class Connection implements EntityProviderConnection { constructor( private readonly config: { - stateManager: ProcessingStateManager; + processingDatabase: ProcessingDatabase; id: string; }, ) {} async applyMutation(mutation: EntityProviderMutation): Promise { + const db = this.config.processingDatabase; if (mutation.type === 'full') { - await this.config.stateManager.replaceProcessingItems({ - sourceKey: this.config.id, - type: 'full', - items: mutation.entities, + await db.transaction(async tx => { + await db.replaceUnprocessedEntities(tx, { + sourceKey: this.config.id, + type: 'full', + items: mutation.entities, + }); }); - return; } - - await this.config.stateManager.replaceProcessingItems({ - sourceKey: this.config.id, - type: 'delta', - added: mutation.added, - removed: mutation.removed, + await db.transaction(async tx => { + await db.replaceUnprocessedEntities(tx, { + sourceKey: this.config.id, + type: 'delta', + added: mutation.added, + removed: mutation.removed, + }); }); } } export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { - private running: boolean = false; + private running = false; constructor( private readonly logger: Logger, private readonly entityProviders: EntityProvider[], - private readonly stateManager: ProcessingStateManager, + private readonly processingDatabase: ProcessingDatabase, private readonly orchestrator: CatalogProcessingOrchestrator, private readonly stitcher: Stitcher, ) {} @@ -69,52 +72,77 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { for (const provider of this.entityProviders) { await provider.connect( new Connection({ - stateManager: this.stateManager, + processingDatabase: this.processingDatabase, id: provider.getProviderName(), }), ); } - this.running = true; + this.run(); + } + private async run() { while (this.running) { - const { - id, - entity, - state: initialState, - } = await this.stateManager.getNextProcessingItem(); + try { + // TODO: We want to disconnect the queue popping and message processing + // so that if the queue popping fails we exponentially back off in order to give the DB room to sort itself out. + await this.process(); + } catch (e) { + this.logger.warn('Processing failed with:', e); + // TODO: this can be a little smarter as mentioned in the above comment. + // But for now, if something fails, wait a brief time to pick up the next message. + await this.wait(); + } + } + } - const result = await this.orchestrator.process({ - entity, - state: initialState, + private async process() { + const { items } = await this.processingDatabase.transaction(async tx => { + return this.processingDatabase.getProcessableEntities(tx, { + processBatchSize: 1, }); + }); - for (const error of result.errors) { - this.logger.warn(error.message); - } + if (!items.length) { + // No items to process, wait and try again. + await this.wait(); + return; + } - if (!result.ok) { - return; - } + const { id, state, unprocessedEntity } = items[0]; - result.completedEntity.metadata.uid = id; - await this.stateManager.setProcessingItemResult({ + const result = await this.orchestrator.process({ + entity: unprocessedEntity, + state, + }); + for (const error of result.errors) { + this.logger.warn(error.message); + } + if (!result.ok) { + return; + } + + result.completedEntity.metadata.uid = id; + await this.processingDatabase.transaction(async tx => { + await this.processingDatabase.updateProcessedEntity(tx, { id, - entity: result.completedEntity, + processedEntity: result.completedEntity, state: result.state, - errors: result.errors, + errors: JSON.stringify(result.errors), relations: result.relations, deferredEntities: result.deferredEntities, }); + }); - const setOfThingsToStitch = new Set([ - stringifyEntityRef(result.completedEntity), - ...result.relations.map(relation => - stringifyEntityRef(relation.source), - ), - ]); - await this.stitcher.stitch(setOfThingsToStitch); - } + const setOfThingsToStitch = new Set([ + stringifyEntityRef(result.completedEntity), + ...result.relations.map(relation => stringifyEntityRef(relation.source)), + ]); + await this.stitcher.stitch(setOfThingsToStitch); + } + + private async wait() { + await new Promise(resolve => setTimeout(resolve, 1000)); } async stop() { diff --git a/plugins/catalog-backend/src/next/DefaultProcessingStateManager.ts b/plugins/catalog-backend/src/next/DefaultProcessingStateManager.ts deleted file mode 100644 index 9c1d20ae9c..0000000000 --- a/plugins/catalog-backend/src/next/DefaultProcessingStateManager.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2021 Spotify AB - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { ProcessingDatabase } from './database/types'; -import { - ProcessingItem, - ProcessingItemResult, - ProcessingStateManager, - ReplaceProcessingItemsRequest, -} from './types'; - -export class DefaultProcessingStateManager implements ProcessingStateManager { - constructor(private readonly db: ProcessingDatabase) {} - - replaceProcessingItems( - request: ReplaceProcessingItemsRequest, - ): Promise { - return this.db.transaction(async tx => { - await this.db.replaceUnprocessedEntities(tx, request); - }); - } - - async setProcessingItemResult(result: ProcessingItemResult) { - return this.db.transaction(async tx => { - await this.db.updateProcessedEntity(tx, { - id: result.id, - processedEntity: result.entity, - errors: JSON.stringify(result.errors), - state: result.state, - relations: result.relations, - deferredEntities: result.deferredEntities, - }); - }); - } - - async getNextProcessingItem(): Promise { - for (;;) { - const { items } = await this.db.transaction(async tx => { - return this.db.getProcessableEntities(tx, { - processBatchSize: 1, - }); - }); - - if (items.length) { - const { id, state, unprocessedEntity } = items[0]; - return { - id, - entity: unprocessedEntity, - state, - }; - } - - await new Promise(resolve => setTimeout(resolve, 1000)); - } - } -} diff --git a/plugins/catalog-backend/src/next/NextCatalogBuilder.ts b/plugins/catalog-backend/src/next/NextCatalogBuilder.ts index 10500fdc15..0158cc9fc9 100644 --- a/plugins/catalog-backend/src/next/NextCatalogBuilder.ts +++ b/plugins/catalog-backend/src/next/NextCatalogBuilder.ts @@ -69,7 +69,6 @@ import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase' import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine'; import { DefaultCatalogProcessingOrchestrator } from './DefaultCatalogProcessingOrchestrator'; import { DefaultLocationStore } from './DefaultLocationStore'; -import { DefaultProcessingStateManager } from './DefaultProcessingStateManager'; import { NextEntitiesCatalog } from './NextEntitiesCatalog'; import { Stitcher } from './Stitcher'; @@ -252,7 +251,6 @@ export class NextCatalogBuilder { const db = new CommonDatabase(dbClient, logger); const processingDatabase = new DefaultProcessingDatabase(dbClient, logger); - const stateManager = new DefaultProcessingStateManager(processingDatabase); const integrations = ScmIntegrations.fromConfig(config); const orchestrator = new DefaultCatalogProcessingOrchestrator({ processors, @@ -269,7 +267,7 @@ export class NextCatalogBuilder { const processingEngine = new DefaultCatalogProcessingEngine( logger, [locationStore, configLocationProvider], - stateManager, + processingDatabase, orchestrator, stitcher, ); diff --git a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts index d76bd74260..7a0094dbf0 100644 --- a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts +++ b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts @@ -23,15 +23,26 @@ import { DefaultProcessingDatabase, } from './DefaultProcessingDatabase'; -import { Entity, EntityRelationSpec } from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import * as uuid from 'uuid'; import { getVoidLogger } from '@backstage/backend-common'; +import { JsonObject } from '@backstage/config'; describe('Default Processing Database', () => { let db: Knex; let processingDatabase: DefaultProcessingDatabase; const logger = getVoidLogger(); + const insertRefRow = async (ref: DbRefreshStateReferencesRow) => { + return db('refresh_state_references').insert( + ref, + ); + }; + + const insertRefreshStateRow = async (ref: DbRefreshStateRow) => { + await db('refresh_state').insert(ref); + }; + beforeEach(async () => { db = await DatabaseManager.createTestDatabaseConnection(); await DatabaseManager.createDatabase(db); @@ -39,17 +50,162 @@ describe('Default Processing Database', () => { processingDatabase = new DefaultProcessingDatabase(db, logger); }); - describe('replaceUnprocessedEntities', () => { - const insertRefRow = async (ref: DbRefreshStateReferencesRow) => { - return db('refresh_state_references').insert( - ref, + describe('updateProcessedEntity', () => { + let id: string; + let processedEntity: Entity; + + beforeEach(() => { + id = uuid.v4(); + processedEntity = { + apiVersion: '1', + kind: 'Location', + metadata: { + name: 'fakelocation', + }, + spec: { + type: 'url', + target: 'somethingelse', + }, + }; + }); + + it('fails when there is no processing state for the entity', async () => { + await processingDatabase.transaction(async tx => { + await expect(() => + processingDatabase.updateProcessedEntity(tx, { + id, + processedEntity, + state: new Map(), + relations: [], + deferredEntities: [], + }), + ).rejects.toThrow(`Processing state not found for ${id}`); + }); + }); + + it('updates the refresh state entry with the cache, processed entity and errors', async () => { + await insertRefreshStateRow({ + entity_id: id, + entity_ref: 'location:default/fakelocation', + unprocessed_entity: '{}', + processed_entity: '{}', + errors: '[]', + next_update_at: '2021-04-01 13:37:00', + last_discovery_at: '2021-04-01 13:37:00', + }); + + const state = new Map(); + state.set('hello', { t: 'something' }); + + await processingDatabase.transaction(tx => + processingDatabase.updateProcessedEntity(tx, { + id, + processedEntity, + state, + relations: [], + deferredEntities: [], + errors: "['something broke']", + }), ); - }; - const insertRefreshStateRow = async (ref: DbRefreshStateRow) => { - await db('refresh_state').insert(ref); - }; + const entities = await db('refresh_state').select(); + expect(entities.length).toBe(1); + expect(entities[0].processed_entity).toEqual( + JSON.stringify(processedEntity), + ); + expect(entities[0].cache).toEqual(JSON.stringify(state)); + expect(entities[0].errors).toEqual("['something broke']"); + }); + it('removes old relations and stores the new relationships', async () => { + await insertRefreshStateRow({ + entity_id: id, + entity_ref: 'location:default/fakelocation', + unprocessed_entity: '{}', + processed_entity: '{}', + errors: '[]', + next_update_at: '2021-04-01 13:37:00', + last_discovery_at: '2021-04-01 13:37:00', + }); + + const relations = [ + { + source: { + kind: 'Component', + namespace: 'Default', + name: 'foo', + }, + target: { + kind: 'Component', + namespace: 'Default', + name: 'foo', + }, + type: 'memberOf', + }, + ]; + + await processingDatabase.transaction(tx => + processingDatabase.updateProcessedEntity(tx, { + id, + processedEntity, + state: new Map(), + relations: relations, + deferredEntities: [], + }), + ); + + const savedRelations = await db('relations') + .where({ originating_entity_id: id }) + .select(); + expect(savedRelations.length).toBe(1); + expect(savedRelations[0]).toEqual({ + originating_entity_id: id, + source_entity_ref: 'component:default/foo', + type: 'memberOf', + target_entity_ref: 'component:default/foo', + }); + }); + + it('adds deferred entities to the the refresh_state table to be picked up later', async () => { + await insertRefreshStateRow({ + entity_id: id, + entity_ref: 'location:default/fakelocation', + unprocessed_entity: '{}', + processed_entity: '{}', + errors: '[]', + next_update_at: '2021-04-01 13:37:00', + last_discovery_at: '2021-04-01 13:37:00', + }); + + const deferredEntities = [ + { + apiVersion: '1', + kind: 'Location', + metadata: { + name: 'next', + }, + }, + ]; + + await processingDatabase.transaction(tx => + processingDatabase.updateProcessedEntity(tx, { + id, + processedEntity, + state: new Map(), + relations: [], + deferredEntities, + }), + ); + + const refreshStateEntries = await db('refresh_state') + .where({ entity_ref: stringifyEntityRef(deferredEntities[0]) }) + .select(); + + expect(refreshStateEntries).toHaveLength(1); + }); + }); + + describe('replaceUnprocessedEntities', () => { const createLocations = async (entityRefs: string[]) => { for (const ref of entityRefs) { await insertRefreshStateRow({ @@ -57,9 +213,9 @@ describe('Default Processing Database', () => { entity_ref: ref, unprocessed_entity: '{}', processed_entity: '{}', - errors: '', - next_update_at: 'now()', - last_discovery_at: 'now()', + errors: '[]', + next_update_at: '2021-04-01 13:37:00', + last_discovery_at: '2021-04-01 13:37:00', }); } }; @@ -101,8 +257,8 @@ describe('Default Processing Database', () => { target_entity_ref: 'location:default/root-2', }); - await processingDatabase.transaction(async tx => { - await processingDatabase.replaceUnprocessedEntities(tx, { + await processingDatabase.transaction(tx => + processingDatabase.replaceUnprocessedEntities(tx, { type: 'full', sourceKey: 'config', items: [ @@ -114,8 +270,8 @@ describe('Default Processing Database', () => { kind: 'Location', } as Entity, ], - }); - }); + }), + ); const currentRefreshState = await db( 'refresh_state', @@ -431,86 +587,48 @@ describe('Default Processing Database', () => { }); }); - describe('updateProcessedEntity', () => { - it('should throw if the entity does not exist', async () => { - await processingDatabase.transaction(async tx => { - await expect( - processingDatabase.updateProcessedEntity(tx, { - id: '9', - processedEntity: { - apiVersion: '1.0.0', - metadata: { - name: 'new-root', - }, - kind: 'Location', - } as Entity, - deferredEntities: [], - relations: [], - }), - ).rejects.toThrow('Processing state not found for 9'); - }); - }); - - it('should update a processed entity', async () => { - await db('refresh_state').insert({ - entity_id: '321', - entity_ref: 'location:default/new-root', - unprocessed_entity: '', - errors: '', - next_update_at: 'now()', - last_discovery_at: 'now()', - }); - - const deferredEntity = { + describe('getProcessableEntities', () => { + it('should return entities to process', async () => { + const entity = JSON.stringify({ + kind: 'Location', apiVersion: '1.0.0', metadata: { - name: 'deferred', + name: 'xyz', }, - kind: 'Location', - } as Entity; + } as Entity); - const relation: EntityRelationSpec = { - source: { - kind: 'Component', - namespace: 'Default', - name: 'foo', - }, - target: { - kind: 'Component', - namespace: 'Default', - name: 'foo', - }, - type: 'url', - }; - - await processingDatabase.transaction(async tx => { - await processingDatabase.updateProcessedEntity(tx, { - id: '321', - processedEntity: { - apiVersion: '1.0.0', - metadata: { - name: 'new-root', - }, - kind: 'Location', - } as Entity, - deferredEntities: [deferredEntity], - relations: [relation], - }); + await db('refresh_state').insert({ + entity_id: '2', + entity_ref: 'location:default/new-root', + unprocessed_entity: entity, + errors: '[]', + next_update_at: '2019-01-01 23:00:00', + last_discovery_at: '2021-04-01 13:37:00', }); - const deferredResult = await db('refresh_state') - .where({ entity_ref: 'location:default/deferred' }) - .select(); - expect(deferredResult.length).toBe(1); + await db('refresh_state').insert({ + entity_id: '1', + entity_ref: 'location:default/foobar', + unprocessed_entity: entity, + errors: '[]', + next_update_at: '2042-01-01 23:00:00', + last_discovery_at: '2021-04-01 13:37:00', + }); - const [relations] = await db('relations') - .where({ originating_entity_id: '321' }) - .select(); - expect(relations).toEqual({ - originating_entity_id: '321', - source_entity_ref: 'component:default/foo', - type: 'url', - target_entity_ref: 'component:default/foo', + await processingDatabase.transaction(async tx => { + // request two items but only one can be processed. + const result = await processingDatabase.getProcessableEntities(tx, { + processBatchSize: 2, + }); + expect(result.items.length).toEqual(1); + expect(result.items[0].entityRef).toEqual('location:default/new-root'); + + // should not return the same item as there's nothing left to process. + await expect( + processingDatabase.getProcessableEntities(tx, { + processBatchSize: 2, + }), + ).resolves.toEqual({ items: [] }); }); }); }); diff --git a/plugins/catalog-backend/src/next/types.ts b/plugins/catalog-backend/src/next/types.ts index 202576fddd..410066eafd 100644 --- a/plugins/catalog-backend/src/next/types.ts +++ b/plugins/catalog-backend/src/next/types.ts @@ -80,37 +80,3 @@ export type EntityProcessingResult = export interface CatalogProcessingOrchestrator { process(request: EntityProcessingRequest): Promise; } - -export type ProcessingItemResult = { - id: string; - entity: Entity; - state: Map; - errors: Error[]; - relations: EntityRelationSpec[]; - deferredEntities: Entity[]; -}; - -export type ProcessingItem = { - id: string; - entity: Entity; - state: Map; -}; - -export type ReplaceProcessingItemsRequest = - | { - sourceKey: string; - items: Entity[]; - type: 'full'; - } - | { - sourceKey: string; - added: Entity[]; - removed: Entity[]; - type: 'delta'; - }; - -export interface ProcessingStateManager { - setProcessingItemResult(result: ProcessingItemResult): Promise; - getNextProcessingItem(): Promise; - replaceProcessingItems(request: ReplaceProcessingItemsRequest): Promise; -} diff --git a/plugins/catalog-import/README.md b/plugins/catalog-import/README.md index 5e773ad288..bad48a8486 100644 --- a/plugins/catalog-import/README.md +++ b/plugins/catalog-import/README.md @@ -18,8 +18,8 @@ Some features are not yet available for all supported Git providers. 1. Install the Catalog Import Plugin: ```bash -# packages/app - +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-catalog-import ``` diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index ca540fe8be..ccc2dd24a7 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -40,7 +40,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "@octokit/rest": "^18.0.12", + "@octokit/rest": "^18.5.3", "@types/react": "^16.9", "git-url-parse": "^11.4.4", "js-base64": "^3.6.0", diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 91201a49a1..57a9479a3c 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -33,6 +33,7 @@ "@backstage/core": "^0.7.3", "@material-ui/core": "^4.11.0", "@types/react": "^16.9", + "lodash": "^4.17.15", "react": "^16.13.1", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", diff --git a/plugins/catalog-react/src/hooks/useRelatedEntities.ts b/plugins/catalog-react/src/hooks/useRelatedEntities.ts index baf6cf5fc7..b47d941ff6 100644 --- a/plugins/catalog-react/src/hooks/useRelatedEntities.ts +++ b/plugins/catalog-react/src/hooks/useRelatedEntities.ts @@ -13,11 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, EntityRelation } from '@backstage/catalog-model'; import { useApi } from '@backstage/core'; +import { chunk, groupBy } from 'lodash'; import { useAsync } from 'react-use'; import { catalogApiRef } from '../api'; +const BATCH_SIZE = 20; + export function useRelatedEntities( entity: Entity, { type, kind }: { type?: string; kind?: string }, @@ -40,16 +43,50 @@ export function useRelatedEntities( return []; } - // TODO: This code could be more efficient if there was an endpoint in the - // backend that either returns the relations of entity (filtered by type) - // or if there is a way to perform a batch request by entity name. However, - // such an implementation would probably be better placed in the graphql API. - const results = await Promise.all( - relations?.map(r => catalogApi.getEntityByName(r.target)), + // Group the relations by kind and namespace to reduce the size of the request query string. + // Without this grouping, the kind and namespace would need to be specified for each relation, e.g. + // `filter=kind=component,namespace=default,name=example1&filter=kind=component,namespace=default,name=example2` + // with grouping, we can generate a query a string like + // `filter=kind=component,namespace=default,name=example1,example2` + const relationsByKindAndNamespace: EntityRelation[][] = Object.values( + groupBy(relations, ({ target }) => { + return `${target.kind}:${target.namespace}`.toLowerCase(); + }), ); - // Skip entities that where not found, for example if a relation references - // an entity that doesn't exist. - return results.filter(e => e) as Entity[]; + + // Split the names within each group into batches to further reduce the query string length. + const batchedRelationsByKindAndNamespace: { + kind: string; + namespace: string; + nameBatches: string[][]; + }[] = []; + for (const rs of relationsByKindAndNamespace) { + batchedRelationsByKindAndNamespace.push({ + // All relations in a group have the same kind and namespace, so its arbitrary which we pick + kind: rs[0].target.kind, + namespace: rs[0].target.namespace, + nameBatches: chunk( + rs.map(r => r.target.name), + BATCH_SIZE, + ), + }); + } + + const results = await Promise.all( + batchedRelationsByKindAndNamespace.flatMap(rs => { + return rs.nameBatches.map(names => { + return catalogApi.getEntities({ + filter: { + kind: rs.kind, + 'metadata.namespace': rs.namespace, + 'metadata.name': names, + }, + }); + }); + }), + ); + + return results.flatMap(r => r.items); }, [entity, type]); return { diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx index 58df0a53f8..8e0a61b05a 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx @@ -91,14 +91,18 @@ describe('', () => { }, ], }; - catalogApi.getEntityByName.mockResolvedValue({ - apiVersion: 'v1', - kind: 'Component', - metadata: { - name: 'target-name', - namespace: 'my-namespace', - }, - spec: {}, + catalogApi.getEntities.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }, + ], }); const { getByText } = await renderInTestApp( diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx index 79b69d35ff..9ad69c9a0c 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx @@ -91,14 +91,18 @@ describe('', () => { }, ], }; - catalogApi.getEntityByName.mockResolvedValue({ - apiVersion: 'v1', - kind: 'Component', - metadata: { - name: 'target-name', - namespace: 'my-namespace', - }, - spec: {}, + catalogApi.getEntities.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'Component', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }, + ], }); const { getByText } = await renderInTestApp( diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx index 08923a59c8..9b7fb0adae 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx @@ -89,14 +89,18 @@ describe('', () => { }, ], }; - catalogApi.getEntityByName.mockResolvedValue({ - apiVersion: 'v1', - kind: 'System', - metadata: { - name: 'target-name', - namespace: 'my-namespace', - }, - spec: {}, + catalogApi.getEntities.mockResolvedValue({ + items: [ + { + apiVersion: 'v1', + kind: 'System', + metadata: { + name: 'target-name', + namespace: 'my-namespace', + }, + spec: {}, + }, + ], }); const { getByText } = await renderInTestApp( diff --git a/plugins/circleci/README.md b/plugins/circleci/README.md index 868ae6e26a..c878c66e56 100644 --- a/plugins/circleci/README.md +++ b/plugins/circleci/README.md @@ -7,34 +7,35 @@ Website: [https://circleci.com/](https://circleci.com/) ## Setup -1. If you have standalone app (you didn't clone this repo), then do +1. If you have a standalone app (you didn't clone this repo), then do ```bash +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-circleci ``` -2. Add the `EntityCircleCIContent` extension to the entity page in the app: +2. Add the `EntityCircleCIContent` extension to the entity page in your app: ```tsx -// packages/app/src/components/catalog/EntityPage.tsx -import { EntityCircleCIContent } from '@backstage/plugin-circleci'; +// In packages/app/src/components/catalog/EntityPage.tsx +import { + EntityCircleCIContent, + isCircleCIAvailable, +} from '@backstage/plugin-circleci'; -// ... -const serviceEntityPage = ( - - ... - +// For example in the CI/CD section +const cicdContent = ( + + - - ... - -); + ``` 4. Add proxy config: ```yaml -// app-config.yaml +# In app-config.yaml proxy: '/circleci/api': target: https://circleci.com/api/v1.1 @@ -42,10 +43,11 @@ proxy: Circle-Token: ${CIRCLECI_AUTH_TOKEN} ``` -5. Get and provide `CIRCLECI_AUTH_TOKEN` as env variable (https://circleci.com/docs/api/#add-an-api-token) -6. Add `circleci.com/project-slug` annotation to your catalog-info.yaml file in format // (https://backstage.io/docs/architecture-decisions/adrs-adr002#format) +5. Get and provide a `CIRCLECI_AUTH_TOKEN` as an environment variable (see the [CircleCI docs](https://circleci.com/docs/api/#add-an-api-token)). +6. Add a `circleci.com/project-slug` annotation to your respective `catalog-info.yaml` files, on the format // (https://backstage.io/docs/architecture-decisions/adrs-adr002#format). ```yaml +# Example catalog-info.yaml entity definition file apiVersion: backstage.io/v1alpha1 kind: Component metadata: diff --git a/plugins/cost-insights/README.md b/plugins/cost-insights/README.md index f737c022f3..3fee89e77a 100644 --- a/plugins/cost-insights/README.md +++ b/plugins/cost-insights/README.md @@ -16,6 +16,8 @@ Learn more with the Backstage blog post [New Cost Insights plugin: The engineer' ## Install ```bash +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-cost-insights ``` diff --git a/plugins/cost-insights/contrib/aws-cost-explorer-api.md b/plugins/cost-insights/contrib/aws-cost-explorer-api.md index 10622b7917..d6c5250759 100644 --- a/plugins/cost-insights/contrib/aws-cost-explorer-api.md +++ b/plugins/cost-insights/contrib/aws-cost-explorer-api.md @@ -33,6 +33,8 @@ Cost Explorer permission policy: Install the AWS Cost Explorer SDK. The AWS docs recommend using the SDK over making calls to the API directly as it simplifies authentication and provides direct access to commands. ```bash +# From your Backstage root directory +cd packages/app yarn add @aws-sdk/client-cost-explorer ``` diff --git a/plugins/fossa/README.md b/plugins/fossa/README.md index 11f3e90e1c..6ca2e1a96f 100644 --- a/plugins/fossa/README.md +++ b/plugins/fossa/README.md @@ -9,8 +9,8 @@ The FOSSA Plugin displays code statistics from [FOSSA](https://fossa.com/). 1. Install the FOSSA Plugin: ```bash -# packages/app - +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-fossa ``` diff --git a/plugins/github-actions/README.md b/plugins/github-actions/README.md index 87c68839c1..d027368c8b 100644 --- a/plugins/github-actions/README.md +++ b/plugins/github-actions/README.md @@ -11,15 +11,15 @@ TBD ### Generic Requirements 1. Provide OAuth credentials: - 1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with callback URL set to `http://localhost:7000/api/auth/github`. - 2. Take Client ID and Client Secret from the newly created app's settings page and put them into `AUTH_GITHUB_CLIENT_ID` and `AUTH_GITHUB_CLIENT_SECRET` env variables. + 1. [Create an OAuth App](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/) with the callback URL set to `http://localhost:7000/api/auth/github`. + 2. Take the Client ID and Client Secret from the newly created app's settings page and put them into `AUTH_GITHUB_CLIENT_ID` and `AUTH_GITHUB_CLIENT_SECRET` environment variables. 2. Annotate your component with a correct GitHub Actions repository and owner: The annotation key is `github.com/project-slug`. Example: - ``` + ```yaml apiVersion: backstage.io/v1alpha1 kind: Component metadata: @@ -38,6 +38,7 @@ TBD 1. Install the plugin dependency in your Backstage app package: ```bash +# From your Backstage root directory cd packages/app yarn add @backstage/plugin-github-actions ``` @@ -45,22 +46,24 @@ yarn add @backstage/plugin-github-actions 2. Add to the app `EntityPage` component: ```tsx -// packages/app/src/components/catalog/EntityPage.tsx -import { EntityGithubActionsContent } from '@backstage/plugin-github-actions'; +// In packages/app/src/components/catalog/EntityPage.tsx +import { + EntityGithubActionsContent, + isGithubActionsAvailable, +} from '@backstage/plugin-github-actions'; -// ... +// You can add the tab to any number of pages, the service page is shown as an +// example here const serviceEntityPage = ( - - ... + + {/* other tabs... */} - ... - -); ``` -2. Run the app with `yarn start` and the backend with `yarn --cwd packages/backend start`, navigate to `/github-actions/`. +3. Run the app with `yarn start` and the backend with `yarn start-backend`. + Then navigate to `/github-actions/` under any entity. ## Features diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 2c26061cf3..89b569bd95 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -40,7 +40,7 @@ "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "@octokit/rest": "^18.0.12", + "@octokit/rest": "^18.5.3", "moment": "^2.27.0", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index 38d84a4b6d..0624438ffe 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -202,13 +202,13 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { Message - {details.value?.head_commit.message} + {details.value?.head_commit?.message} Commit ID - {details.value?.head_commit.id} + {details.value?.head_commit?.id} @@ -231,7 +231,7 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { Author - {`${details.value?.head_commit.author?.name} (${details.value?.head_commit.author?.email})`} + {`${details.value?.head_commit?.author?.name} (${details.value?.head_commit?.author?.email})`} diff --git a/plugins/github-deployments/README.md b/plugins/github-deployments/README.md index 1bb8846105..8284ceba56 100644 --- a/plugins/github-deployments/README.md +++ b/plugins/github-deployments/README.md @@ -13,8 +13,8 @@ The GitHub Deployments Plugin displays recent deployments from GitHub. 1. Install the GitHub Deployments Plugin. ```bash -# packages/app - +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-github-deployments ``` diff --git a/plugins/graphiql/README.md b/plugins/graphiql/README.md index 39372890d1..b95f055a56 100644 --- a/plugins/graphiql/README.md +++ b/plugins/graphiql/README.md @@ -12,6 +12,7 @@ By exposing GraphiQL as a plugin instead of a standalone app, it's possible to p Start out by installing the plugin in your Backstage app: ```bash +# From your Backstage root directory cd packages/app yarn add @backstage/plugin-graphiql ``` diff --git a/plugins/jenkins/README.md b/plugins/jenkins/README.md index f406393190..69304cb847 100644 --- a/plugins/jenkins/README.md +++ b/plugins/jenkins/README.md @@ -11,25 +11,25 @@ Website: [https://jenkins.io/](https://jenkins.io/) 1. If you have a standalone app (you didn't clone this repo), then do ```bash +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-jenkins ``` 2. Add the `EntityJenkinsContent` extension to the entity page in the app: ```tsx -// packages/app/src/components/catalog/EntityPage.tsx -import { EntityJenkinsContent } from '@backstage/plugin-circleci'; +// In packages/app/src/components/catalog/EntityPage.tsx +import { EntityJenkinsContent } from '@backstage/plugin-jenkins'; -// ... +// You can add the tab to any number of pages, the service page is shown as an +// example here const serviceEntityPage = ( - - ... + + {/* other tabs... */} - ... - -); ``` 3. Add proxy configuration to `app-config.yaml` @@ -43,14 +43,18 @@ proxy: Authorization: Basic ${JENKINS_BASIC_AUTH_HEADER} ``` -4. Add an environment variable which contains the Jenkins credentials, (note: use an API token not your password). Here user is the name of the user created in Jenkins. +4. Add an environment variable which contains the Jenkins credentials (NOTE: + use an API token, not your password). Here `user` is the name of the user + created in Jenkins. ```shell export JENKINS_BASIC_AUTH_HEADER=$(echo -n user:api-token | base64) ``` -5. Run app with `yarn start` -6. Add the Jenkins folder annotation to your `catalog-info.yaml`, (note: currently this plugin only supports folders and Git SCM) +5. Run the app with `yarn start` + +6. Add the Jenkins folder annotation to your `catalog-info.yaml`, (NOTE: + currently this plugin only supports folders and Git SCM) ```yaml apiVersion: backstage.io/v1alpha1 @@ -68,11 +72,11 @@ spec: 7. Register your component -8. Click the component in the catalog you should now see Jenkins builds, and a last build result for your master build. +8. Click the component in the catalog. You should now see Jenkins builds, and a + last build result for your master build. -Note: - -If you are not using environment variable then you can directly type API token in app-config.yaml +Note: If you are not using environment variables, you can directly type the API +token into `app-config.yaml`. ```yaml proxy: @@ -83,7 +87,8 @@ proxy: Authorization: Basic YWRtaW46MTFlYzI1NmU0Mzg1MDFjM2Y1Yzc2Yjc1MWE3ZTQ3YWY4Mw== ``` -YWRtaW46MTFlYzI1NmU0Mzg1MDFjM2Y1Yzc2Yjc1MWE3ZTQ3YWY4Mw== is the base64 of user and it's API token e.g. admin:11ec256e438501c3f5c76b751a7e47af83 +The string starting with `YWR...` is the base64 encoding of the user and their +API token, e.g. `admin:11ec256e438501c3f5c76b751a7e47af83`. ## Features @@ -94,4 +99,5 @@ YWRtaW46MTFlYzI1NmU0Mzg1MDFjM2Y1Yzc2Yjc1MWE3ZTQ3YWY4Mw== is the base64 of user a ## Limitations - Only works with organization folder projects backed by GitHub -- No pagination support currently, limited to 50 projects - don't run this on a Jenkins with lots of builds +- No pagination support currently, limited to 50 projects - don't run this on a + Jenkins instance with lots of builds diff --git a/plugins/kafka-backend/README.md b/plugins/kafka-backend/README.md index 14914e5ba6..d18a232fa4 100644 --- a/plugins/kafka-backend/README.md +++ b/plugins/kafka-backend/README.md @@ -1,6 +1,7 @@ # Kafka Backend -This is the backend part of the Kafka plugin. It responds to Kafka requests from the frontend. +This is the backend part of the Kafka plugin. It responds to Kafka requests +from the frontend. ## Configuration @@ -16,7 +17,9 @@ A list of the brokers' host names and ports to connect to. ### `ssl` (optional) -Configure TLS connection to the Kafka cluster. The options are passed directly to [tls.connect] and used to create the TLS secure context. Normally these would include `key` and `cert`. +Configure TLS connection to the Kafka cluster. The options are passed directly +to [tls.connect] and used to create the TLS secure context. Normally these +would include `key` and `cert`. Example: @@ -39,7 +42,7 @@ kafka: clusters: - name: prod brokers: - - my-cluser:9092 + - my-cluster:9092 ssl: true sasl: mechanism: plain # or 'scram-sha-256' or 'scram-sha-512' diff --git a/plugins/kafka/README.md b/plugins/kafka/README.md index a2ea01cf7d..7da44ae9b6 100644 --- a/plugins/kafka/README.md +++ b/plugins/kafka/README.md @@ -7,7 +7,9 @@ 1. Run: ```bash -yarn add @backstage/plugin-kafka @backstage/plugin-kafka-backend +# From your Backstage root directory +yarn --cwd packages/app add @backstage/plugin-kafka +yarn --cwd packages/backend add @backstage/plugin-kafka-backend ``` 2. Add the plugin backend: @@ -29,41 +31,31 @@ export default async function createPlugin({ And then add to `packages/backend/src/index.ts`: ```js -// ... +// In packages/backend/src/index.ts import kafka from './plugins/kafka'; // ... async function main() { // ... const kafkaEnv = useHotMemoize(module, () => createEnv('kafka')); // ... - - const apiRouter = Router(); - // ... apiRouter.use('/kafka', await kafka(kafkaEnv)); - // ... ``` -3. Add the plugin frontend to `packages/app/src/plugin.ts`: - -```js -export { plugin as Kafka } from '@backstage/plugin-kafka'; -``` - -4. Register the plugin frontend router in `packages/app/src/components/catalog/EntityPage.tsx`: +3. Add the plugin as a tab to your service entities: ```jsx -import { Router as KafkaRouter } from '@backstage/plugin-kafka'; +// In packages/app/src/components/catalog/EntityPage.tsx +import { EntityKafkaContent } from '@backstage/plugin-kafka'; -// Then, somewhere inside - -} -/>; +const serviceEntityPage = ( + + {/* other tabs... */} + + + ``` -5. Add broker configs for the backend in your `app-config.yaml` (see +4. Add broker configs for the backend in your `app-config.yaml` (see [kafka-backend](https://github.com/backstage/backstage/blob/master/plugins/kafka-backend/README.md) for more options): @@ -76,7 +68,7 @@ kafka: - localhost:9092 ``` -6. Add `kafka.apache.org/consumer-groups` annotation to your services: +5. Add the `kafka.apache.org/consumer-groups` annotation to your services: ```yaml apiVersion: backstage.io/v1alpha1 diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 7a8b16bc8a..3c8380b661 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -34,6 +34,7 @@ "@backstage/backend-common": "^0.6.3", "@backstage/catalog-model": "^0.7.7", "@backstage/config": "^0.1.4", + "@backstage/plugin-kubernetes-common": "^0.1.0", "@google-cloud/container": "^2.2.0", "@kubernetes/client-node": "^0.14.0", "@types/express": "^4.17.6", diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts index 0dd2a4bcc2..9dc4519966 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/GoogleKubernetesAuthTranslator.ts @@ -15,7 +15,8 @@ */ import { KubernetesAuthTranslator } from './types'; -import { KubernetesRequestBody, ClusterDetails } from '../types/types'; +import { ClusterDetails } from '../types/types'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export class GoogleKubernetesAuthTranslator implements KubernetesAuthTranslator { diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts index 6433e41546..3610bd4d9f 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/ServiceAccountKubernetesAuthTranslator.ts @@ -15,7 +15,8 @@ */ import { KubernetesAuthTranslator } from './types'; -import { KubernetesRequestBody, ClusterDetails } from '../types/types'; +import { ClusterDetails } from '../types/types'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export class ServiceAccountKubernetesAuthTranslator implements KubernetesAuthTranslator { diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts index c01a57889c..7a04e230c6 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/types.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { KubernetesRequestBody, ClusterDetails } from '../types/types'; +import { ClusterDetails } from '../types/types'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export interface KubernetesAuthTranslator { decorateClusterDetailsWithAuth( diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 6db6e2ad4a..75e2f8233c 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -20,9 +20,9 @@ import { CustomResource, KubernetesFetcher, KubernetesObjectTypes, - KubernetesRequestBody, KubernetesServiceLocator, } from '../types/types'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 10e5640bab..6958bd3387 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -32,15 +32,17 @@ import lodash, { Dictionary } from 'lodash'; import { Logger } from 'winston'; import { ClusterDetails, - FetchResponse, FetchResponseWrapper, - KubernetesErrorTypes, KubernetesFetcher, - KubernetesFetchError, KubernetesObjectTypes, ObjectFetchParams, CustomResource, } from '../types/types'; +import { + FetchResponse, + KubernetesFetchError, + KubernetesErrorTypes, +} from '@backstage/plugin-kubernetes-common'; import { KubernetesClientProvider } from './KubernetesClientProvider'; export interface Clients { diff --git a/plugins/kubernetes-backend/src/service/router.ts b/plugins/kubernetes-backend/src/service/router.ts index 189f2eb9c5..ab7bb5befa 100644 --- a/plugins/kubernetes-backend/src/service/router.ts +++ b/plugins/kubernetes-backend/src/service/router.ts @@ -23,11 +23,11 @@ import { MultiTenantServiceLocator } from '../service-locator/MultiTenantService import { ClusterDetails, KubernetesClustersSupplier, - KubernetesRequestBody, KubernetesServiceLocator, ServiceLocatorMethod, CustomResource, } from '../types/types'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { KubernetesClientProvider } from './KubernetesClientProvider'; import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index c597c718c5..0be0b447af 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -14,146 +14,14 @@ * limitations under the License. */ -import { - ExtensionsV1beta1Ingress, - V1ConfigMap, - V1Deployment, - V1HorizontalPodAutoscaler, - V1Pod, - V1ReplicaSet, - V1Service, -} from '@kubernetes/client-node'; -import { Entity } from '@backstage/catalog-model'; +import type { + FetchResponse, + KubernetesFetchError, +} from '@backstage/plugin-kubernetes-common'; -export interface ClusterDetails { - name: string; - url: string; - authProvider: string; - serviceAccountToken?: string | undefined; - skipTLSVerify?: boolean; -} - -export interface KubernetesRequestBody { - auth?: { - google?: string; - }; - entity: Entity; -} - -export interface ClusterObjects { - cluster: { name: string }; - resources: FetchResponse[]; - errors: KubernetesFetchError[]; -} - -export interface ObjectsByEntityResponse { - items: ClusterObjects[]; -} - -export interface FetchResponseWrapper { - errors: KubernetesFetchError[]; - responses: FetchResponse[]; -} - -export type FetchResponse = - | PodFetchResponse - | ServiceFetchResponse - | ConfigMapFetchResponse - | DeploymentFetchResponse - | ReplicaSetsFetchResponse - | HorizontalPodAutoscalersFetchResponse - | IngressesFetchResponse - | CustomResourceFetchResponse; - -// TODO fairly sure there's a easier way to do this - -export type KubernetesObjectTypes = - | 'pods' - | 'services' - | 'configmaps' - | 'deployments' - | 'replicasets' - | 'horizontalpodautoscalers' - | 'ingresses' - | 'customresources'; - -export interface PodFetchResponse { - type: 'pods'; - resources: Array; -} - -export interface ServiceFetchResponse { - type: 'services'; - resources: Array; -} - -export interface ConfigMapFetchResponse { - type: 'configmaps'; - resources: Array; -} - -export interface DeploymentFetchResponse { - type: 'deployments'; - resources: Array; -} - -export interface ReplicaSetsFetchResponse { - type: 'replicasets'; - resources: Array; -} - -export interface HorizontalPodAutoscalersFetchResponse { - type: 'horizontalpodautoscalers'; - resources: Array; -} - -export interface IngressesFetchResponse { - type: 'ingresses'; - resources: Array; -} - -export interface CustomResourceFetchResponse { - type: 'customresources'; - resources: Array; -} - -export interface ObjectFetchParams { - serviceId: string; - clusterDetails: ClusterDetails; - objectTypesToFetch: Set; - labelSelector: string; - customResources: CustomResource[]; -} - -// Fetches information from a kubernetes cluster using the cluster details object -// to target a specific cluster -export interface KubernetesFetcher { - fetchObjectsForService( - params: ObjectFetchParams, - ): Promise; -} - -// Used to locate which cluster(s) a service is running on -export interface KubernetesServiceLocator { - getClustersByServiceId(serviceId: string): Promise; -} - -// Used to load cluster details from different sources -export interface KubernetesClustersSupplier { - getClusters(): Promise; -} - -export type KubernetesErrorTypes = - | 'BAD_REQUEST' - | 'UNAUTHORIZED_ERROR' - | 'SYSTEM_ERROR' - | 'UNKNOWN_ERROR'; - -export interface KubernetesFetchError { - errorType: KubernetesErrorTypes; - statusCode?: number; - resourcePath?: string; -} +export type ClusterLocatorMethod = + | ConfigClusterLocatorMethod + | GKEClusterLocatorMethod; export interface ConfigClusterLocatorMethod { /** @@ -195,15 +63,61 @@ export interface GKEClusterLocatorMethod { region?: string; } -export type ClusterLocatorMethod = - | ConfigClusterLocatorMethod - | GKEClusterLocatorMethod; - -export type ServiceLocatorMethod = 'multiTenant' | 'http'; // TODO implement http -export type AuthProviderType = 'google' | 'serviceAccount' | 'aws'; - export interface CustomResource { group: string; apiVersion: string; plural: string; } + +export interface ObjectFetchParams { + serviceId: string; + clusterDetails: ClusterDetails; + objectTypesToFetch: Set; + labelSelector: string; + customResources: CustomResource[]; +} + +// Fetches information from a kubernetes cluster using the cluster details object +// to target a specific cluster +export interface KubernetesFetcher { + fetchObjectsForService( + params: ObjectFetchParams, + ): Promise; +} + +export interface FetchResponseWrapper { + errors: KubernetesFetchError[]; + responses: FetchResponse[]; +} + +// TODO fairly sure there's a easier way to do this + +export type KubernetesObjectTypes = + | 'pods' + | 'services' + | 'configmaps' + | 'deployments' + | 'replicasets' + | 'horizontalpodautoscalers' + | 'ingresses' + | 'customresources'; + +// Used to load cluster details from different sources +export interface KubernetesClustersSupplier { + getClusters(): Promise; +} + +// Used to locate which cluster(s) a service is running on +export interface KubernetesServiceLocator { + getClustersByServiceId(serviceId: string): Promise; +} + +export type ServiceLocatorMethod = 'multiTenant' | 'http'; // TODO implement http + +export interface ClusterDetails { + name: string; + url: string; + authProvider: string; + serviceAccountToken?: string | undefined; + skipTLSVerify?: boolean; +} diff --git a/plugins/kubernetes-common/.eslintrc.js b/plugins/kubernetes-common/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/kubernetes-common/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md new file mode 100644 index 0000000000..44c9066ada --- /dev/null +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -0,0 +1,7 @@ +# @backstage/plugin-kubernetes-common + +## 0.1.0 + +### Minor Changes + +- Adds types to be shared by the backend and the front end. diff --git a/plugins/kubernetes-common/README.md b/plugins/kubernetes-common/README.md new file mode 100644 index 0000000000..c366d0493c --- /dev/null +++ b/plugins/kubernetes-common/README.md @@ -0,0 +1,3 @@ +# @backstage/plugin-kubernetes-common + +Common types and functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend. diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json new file mode 100644 index 0000000000..3551eb6413 --- /dev/null +++ b/plugins/kubernetes-common/package.json @@ -0,0 +1,49 @@ +{ + "name": "@backstage/plugin-kubernetes-common", + "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugin/kubernetes-common" + }, + "keywords": [ + "techdocs", + "kubernetes" + ], + "files": [ + "dist" + ], + "scripts": { + "build": "backstage-cli build", + "lint": "backstage-cli lint", + "test": "backstage-cli test --passWithNoTests", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "bugs": { + "url": "https://github.com/backstage/backstage/issues" + }, + "dependencies": { + "@backstage/catalog-model": "^0.7.6", + "@kubernetes/client-node": "^0.14.0" + }, + "devDependencies": { + "@backstage/cli": "^0.6.5" + }, + "jest": { + "roots": [ + ".." + ] + } +} diff --git a/plugins/kubernetes/schema.d.ts b/plugins/kubernetes-common/src/index.ts similarity index 55% rename from plugins/kubernetes/schema.d.ts rename to plugins/kubernetes-common/src/index.ts index 196f5ba76d..50e9534751 100644 --- a/plugins/kubernetes/schema.d.ts +++ b/plugins/kubernetes-common/src/index.ts @@ -14,29 +14,4 @@ * limitations under the License. */ -import { - ClusterLocatorMethod, - CustomResource, -} from '@backstage/plugin-kubernetes-backend'; - -export interface Config { - kubernetes?: { - /** - * @visibility frontend - */ - serviceLocatorMethod: { - /** - * @visibility frontend - */ - type: 'multiTenant'; - }; - /** - * @visibility frontend - */ - clusterLocatorMethods: ClusterLocatorMethod[]; - /** - * @visibility frontend - */ - customResources?: CustomResource[]; - }; -} +export * from './types'; diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts new file mode 100644 index 0000000000..23dc4f0f3c --- /dev/null +++ b/plugins/kubernetes-common/src/types.ts @@ -0,0 +1,107 @@ +/* + * 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 { + ExtensionsV1beta1Ingress, + V1ConfigMap, + V1Deployment, + V1HorizontalPodAutoscaler, + V1Pod, + V1ReplicaSet, + V1Service, +} from '@kubernetes/client-node'; +import { Entity } from '@backstage/catalog-model'; + +export interface KubernetesRequestBody { + auth?: { + google?: string; + }; + entity: Entity; +} + +export interface ClusterObjects { + cluster: { name: string }; + resources: FetchResponse[]; + errors: KubernetesFetchError[]; +} + +export interface ObjectsByEntityResponse { + items: ClusterObjects[]; +} + +export type AuthProviderType = 'google' | 'serviceAccount' | 'aws'; + +export type FetchResponse = + | PodFetchResponse + | ServiceFetchResponse + | ConfigMapFetchResponse + | DeploymentFetchResponse + | ReplicaSetsFetchResponse + | HorizontalPodAutoscalersFetchResponse + | IngressesFetchResponse + | CustomResourceFetchResponse; + +export interface PodFetchResponse { + type: 'pods'; + resources: Array; +} + +export interface ServiceFetchResponse { + type: 'services'; + resources: Array; +} + +export interface ConfigMapFetchResponse { + type: 'configmaps'; + resources: Array; +} + +export interface DeploymentFetchResponse { + type: 'deployments'; + resources: Array; +} + +export interface ReplicaSetsFetchResponse { + type: 'replicasets'; + resources: Array; +} + +export interface HorizontalPodAutoscalersFetchResponse { + type: 'horizontalpodautoscalers'; + resources: Array; +} + +export interface IngressesFetchResponse { + type: 'ingresses'; + resources: Array; +} + +export interface CustomResourceFetchResponse { + type: 'customresources'; + resources: Array; +} + +export interface KubernetesFetchError { + errorType: KubernetesErrorTypes; + statusCode?: number; + resourcePath?: string; +} + +export type KubernetesErrorTypes = + | 'BAD_REQUEST' + | 'UNAUTHORIZED_ERROR' + | 'SYSTEM_ERROR' + | 'UNKNOWN_ERROR'; diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 8d21c79a24..331e121d6c 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -19,7 +19,6 @@ "backstage", "kubernetes" ], - "configSchema": "schema.d.ts", "scripts": { "build": "backstage-cli plugin:build", "start": "backstage-cli plugin:serve", @@ -35,7 +34,7 @@ "@backstage/config": "^0.1.4", "@backstage/core": "^0.7.7", "@backstage/plugin-catalog-react": "^0.1.3", - "@backstage/plugin-kubernetes-backend": "^0.3.2", + "@backstage/plugin-kubernetes-common": "^0.1.0", "@backstage/theme": "^0.2.6", "@kubernetes/client-node": "^0.14.0", "@material-ui/core": "^4.11.0", @@ -63,7 +62,6 @@ "msw": "^0.21.2" }, "files": [ - "dist", - "schema.d.ts" + "dist" ] } diff --git a/plugins/kubernetes/src/api/KubernetesBackendClient.ts b/plugins/kubernetes/src/api/KubernetesBackendClient.ts index 1eadec3fb0..19f1602143 100644 --- a/plugins/kubernetes/src/api/KubernetesBackendClient.ts +++ b/plugins/kubernetes/src/api/KubernetesBackendClient.ts @@ -19,7 +19,7 @@ import { KubernetesApi } from './types'; import { KubernetesRequestBody, ObjectsByEntityResponse, -} from '@backstage/plugin-kubernetes-backend'; +} from '@backstage/plugin-kubernetes-common'; export class KubernetesBackendClient implements KubernetesApi { private readonly discoveryApi: DiscoveryApi; diff --git a/plugins/kubernetes/src/api/types.ts b/plugins/kubernetes/src/api/types.ts index da1b36989a..2e91783132 100644 --- a/plugins/kubernetes/src/api/types.ts +++ b/plugins/kubernetes/src/api/types.ts @@ -18,7 +18,7 @@ import { createApiRef } from '@backstage/core'; import { KubernetesRequestBody, ObjectsByEntityResponse, -} from '@backstage/plugin-kubernetes-backend'; +} from '@backstage/plugin-kubernetes-common'; export const kubernetesApiRef = createApiRef({ id: 'plugin.kubernetes.service', diff --git a/plugins/kubernetes/src/components/KubernetesContent/ErrorPanel.tsx b/plugins/kubernetes/src/components/KubernetesContent/ErrorPanel.tsx index 650e6ddff1..ba0026ac47 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/ErrorPanel.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/ErrorPanel.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { WarningPanel } from '@backstage/core'; import { Typography } from '@material-ui/core'; -import { ClusterObjects } from '@backstage/plugin-kubernetes-backend'; +import { ClusterObjects } from '@backstage/plugin-kubernetes-common'; const clustersWithErrorsToErrorMessage = ( clustersWithErrors: ClusterObjects[], diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx index cda8dd7e6b..1461f52112 100644 --- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx +++ b/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx @@ -31,7 +31,7 @@ import { StatusOK, } from '@backstage/core'; import { Entity } from '@backstage/catalog-model'; -import { ClusterObjects } from '@backstage/plugin-kubernetes-backend'; +import { ClusterObjects } from '@backstage/plugin-kubernetes-common'; import { ErrorPanel } from './ErrorPanel'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { DeploymentsAccordions } from '../DeploymentsAccordions'; diff --git a/plugins/kubernetes/src/error-detection/error-detection.test.ts b/plugins/kubernetes/src/error-detection/error-detection.test.ts index 8a4575c8be..5079ed9245 100644 --- a/plugins/kubernetes/src/error-detection/error-detection.test.ts +++ b/plugins/kubernetes/src/error-detection/error-detection.test.ts @@ -30,7 +30,7 @@ import * as maxedOutHpa from './__fixtures__/hpa-maxed-out.json'; import { FetchResponse, ObjectsByEntityResponse, -} from '@backstage/plugin-kubernetes-backend'; +} from '@backstage/plugin-kubernetes-common'; const CLUSTER_NAME = 'cluster-a'; diff --git a/plugins/kubernetes/src/error-detection/error-detection.ts b/plugins/kubernetes/src/error-detection/error-detection.ts index a44747969d..91544fe623 100644 --- a/plugins/kubernetes/src/error-detection/error-detection.ts +++ b/plugins/kubernetes/src/error-detection/error-detection.ts @@ -15,7 +15,7 @@ */ import { DetectedError, DetectedErrorsByCluster } from './types'; -import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-backend'; +import { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; import { groupResponses } from '../utils/response'; import { detectErrorsInPods } from './pods'; import { detectErrorsInDeployments } from './deployments'; diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts index 7c4de9ff1a..1dfe85fe38 100644 --- a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts +++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts @@ -22,7 +22,7 @@ import { useEffect, useState } from 'react'; import { KubernetesRequestBody, ObjectsByEntityResponse, -} from '@backstage/plugin-kubernetes-backend'; +} from '@backstage/plugin-kubernetes-common'; export interface KubernetesObjects { kubernetesObjects: ObjectsByEntityResponse | undefined; diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts index cab81f3622..ef541b0173 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/AwsKubernetesAuthProvider.ts @@ -15,7 +15,7 @@ */ import { KubernetesAuthProvider } from './types'; -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export class AwsKubernetesAuthProvider implements KubernetesAuthProvider { async decorateRequestBodyForAuth( diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts index 570806a1a4..9c9db0b9fc 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/GoogleKubernetesAuthProvider.ts @@ -16,7 +16,7 @@ import { OAuthApi } from '@backstage/core'; import { KubernetesAuthProvider } from './types'; -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export class GoogleKubernetesAuthProvider implements KubernetesAuthProvider { authProvider: OAuthApi; diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts index dc9d9b5e0d..a96565f0c0 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts @@ -15,7 +15,7 @@ */ import { OAuthApi } from '@backstage/core'; -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; import { KubernetesAuthProvider, KubernetesAuthProvidersApi } from './types'; import { GoogleKubernetesAuthProvider } from './GoogleKubernetesAuthProvider'; import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetesAuthProvider'; diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts index 5d33521ae4..b88fd7679e 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/ServiceAccountKubernetesAuthProvider.ts @@ -15,7 +15,7 @@ */ import { KubernetesAuthProvider } from './types'; -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export class ServiceAccountKubernetesAuthProvider implements KubernetesAuthProvider { diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/types.ts b/plugins/kubernetes/src/kubernetes-auth-provider/types.ts index 2bed2838b9..8d4cead11a 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/types.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/types.ts @@ -15,7 +15,7 @@ */ import { createApiRef } from '@backstage/core'; -import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-backend'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; export interface KubernetesAuthProvider { decorateRequestBodyForAuth( diff --git a/plugins/kubernetes/src/utils/response.ts b/plugins/kubernetes/src/utils/response.ts index a9b10e7759..1d860ca226 100644 --- a/plugins/kubernetes/src/utils/response.ts +++ b/plugins/kubernetes/src/utils/response.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { FetchResponse } from '@backstage/plugin-kubernetes-backend'; +import { FetchResponse } from '@backstage/plugin-kubernetes-common'; import { GroupedResponses } from '../types/types'; // TODO this could probably be a lodash groupBy diff --git a/plugins/lighthouse/README.md b/plugins/lighthouse/README.md index 028547ea3e..06c9a1da12 100644 --- a/plugins/lighthouse/README.md +++ b/plugins/lighthouse/README.md @@ -28,20 +28,21 @@ _It's likely you will need to [enable CORS](https://developer.mozilla.org/en-US/ When you have an instance running that Backstage can hook into, first install the plugin into your app: ```sh -$ yarn add @backstage/plugin-lighthouse +# From your Backstage root directory +cd packages/app +yarn add @backstage/plugin-lighthouse ``` Modify your app routes in `App.tsx` to include the `LighthousePage` component exported from the plugin, for example: ```tsx -// At the top imports +// In packages/app/src/App.tsx import { LighthousePage } from '@backstage/plugin-lighthouse'; - - // ... - } /> - // ... -; +const routes = ( + + {/* ...other routes */} + } /> ``` Then configure the `lighthouse-audit-service` URL in your [`app-config.yaml`](https://github.com/backstage/backstage/blob/master/app-config.yaml). @@ -63,55 +64,45 @@ kind: Component metadata: # ... annotations: - # ... lighthouse.com/website-url: # A single website url e.g. https://backstage.io/ ``` > NOTE: The plugin only supports one website URL per component at this time. -Add a **Lighthouse tab** to the EntityPage: +Add a Lighthouse tab to the entity page: ```tsx -// packages/app/src/components/catalog/EntityPage.tsx -import { EmbeddedRouter as LighthouseRouter } from '@backstage/plugin-lighthouse'; +// In packages/app/src/components/catalog/EntityPage.tsx +import { EntityLighthouseContent } from '@backstage/plugin-lighthouse'; -// ... -const WebsiteEntityPage = ({ entity }: { entity: Entity }) => ( - - // ... - } - /> - -); +const websiteEntityPage = ( + + {/* other tabs... */} + + + ``` -> NOTE: The embedded router renders page content without a header section allowing it to be rendered within a -> catalog plugin page. +> NOTE: The embedded router renders page content without a header section +> allowing it to be rendered within a catalog plugin page. Add a **Lighthouse card** to the overview tab on the EntityPage: ```tsx -// packages/app/src/components/catalog/EntityPage.tsx +// In packages/app/src/components/catalog/EntityPage.tsx import { - LastLighthouseAuditCard, - isPluginApplicableToEntity as isLighthouseAvailable, + EntityLastLighthouseAuditCard, + isLighthouseAvailable, } from '@backstage/plugin-lighthouse'; -// ... - -const OverviewContent = ({ entity }: { entity: Entity }) => ( - - // ... - {isLighthouseAvailable(entity) && ( - - - - )} - -); +const overviewContent = ( + + {/* ...other content */} + + + + + + + ``` - -Link Lighthouse diff --git a/plugins/lighthouse/src/components/Intro/index.tsx b/plugins/lighthouse/src/components/Intro/index.tsx index a89518a5ed..6b0307f9ec 100644 --- a/plugins/lighthouse/src/components/Intro/index.tsx +++ b/plugins/lighthouse/src/components/Intro/index.tsx @@ -46,7 +46,8 @@ with the environment variable \`LAS_CORS\` set to \`true\`._ When you have an instance running that Backstage can hook into, first install the plugin into your app: \`\`\`sh -$ yarn add @backstage/plugin-lighthouse +cd packages/app +yarn add @backstage/plugin-lighthouse \`\`\` Modify your app routes in \`App.tsx\` to include the \`LighthousePage\` component exported from the plugin, for example: diff --git a/plugins/pagerduty/README.md b/plugins/pagerduty/README.md index 36f380fdd5..e1ec7f89c7 100644 --- a/plugins/pagerduty/README.md +++ b/plugins/pagerduty/README.md @@ -19,6 +19,8 @@ This plugin provides: Install the plugin: ```bash +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-pagerduty ``` diff --git a/plugins/register-component/README.md b/plugins/register-component/README.md index 9c5304385e..47db1a7a55 100644 --- a/plugins/register-component/README.md +++ b/plugins/register-component/README.md @@ -19,6 +19,7 @@ When installed it is accessible on [localhost:3000/register-component](localhost 1. Install plugin and its dependency `plugin-catalog` ```bash +# From your Backstage root directory cd packages/app yarn add @backstage/plugin-register-component ``` diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 38d1e3202e..bd51a7db0b 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -39,7 +39,7 @@ "@material-ui/lab": "4.0.0-alpha.45", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-hook-form": "^6.6.0", + "react-hook-form": "^6.15.4", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3" diff --git a/plugins/rollbar/README.md b/plugins/rollbar/README.md index 3a33fe223c..bdf0541fa0 100644 --- a/plugins/rollbar/README.md +++ b/plugins/rollbar/README.md @@ -9,25 +9,23 @@ Website: [https://rollbar.com/](https://rollbar.com/) 2. If you have standalone app (you didn't clone this repo), then do ```bash +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-rollbar ``` 3. Add to the app `EntityPage` component: ```tsx -// packages/app/src/components/catalog/EntityPage.tsx +// In packages/app/src/components/catalog/EntityPage.tsx import { EntityRollbarContent } from '@backstage/plugin-rollbar'; -// ... const serviceEntityPage = ( - - ... + + {/* other tabs... */} - ... - -); ``` 4. Setup the `app-config.yaml` and account token environment variable diff --git a/plugins/scaffolder-backend/README.md b/plugins/scaffolder-backend/README.md index 185938b006..304d43a750 100644 --- a/plugins/scaffolder-backend/README.md +++ b/plugins/scaffolder-backend/README.md @@ -1,7 +1,25 @@ -# Title +# Scaffolder Backend Welcome to the scaffolder plugin! -## Sub-section 1 +## Jobs -## Sub-section 2 +Documentation for `Jobs` here + +## Stages + +Documentation for `Stages` here + +## Tasks + +Documentation for `Tasks` here + +## Actions + +### Built-in: + +- #### GitHub Pull Request + - Minimum permissions required for GitHub App for creating a Pull Request with the built-in action: + - Read and Write permissions for `Contents`. + - Read and write permissions for `Pull Requests` and `Issues`. + - Read permissions on `Metadata`. diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 355bed4de0..bbeeecceb0 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -37,7 +37,7 @@ "@backstage/integration": "^0.5.1", "@gitbeaker/core": "^28.0.2", "@gitbeaker/node": "^28.0.2", - "@octokit/rest": "^18.0.12", + "@octokit/rest": "^18.5.3", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index e2e4f17efb..5dd4046811 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -87,7 +87,9 @@ export const defaultClientFactory = async ({ } const { token } = await credentialsProvider.getCredentials({ - url: `${host}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`, + url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent( + repo, + )}`, }); if (!token) { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 9e4deab31a..1d0d5ad3a3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -143,9 +143,10 @@ export class TaskWorker { }); if (action.schema?.input) { - const validateResult = validateJsonSchema(input, action.schema, { - propertyName: 'input', - }); + const validateResult = validateJsonSchema( + input, + action.schema.input, + ); if (!validateResult.valid) { const errors = validateResult.errors.join(', '); throw new InputError( diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 6caa2a4446..0c5eb2b268 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -214,6 +214,7 @@ export class ScaffolderClient implements ScaffolderApi { subscriber.error(ex); } } + eventSource.close(); subscriber.complete(); }); eventSource.addEventListener('error', event => { diff --git a/plugins/sentry/README.md b/plugins/sentry/README.md index 9bdb2dc16d..e0c56e842c 100644 --- a/plugins/sentry/README.md +++ b/plugins/sentry/README.md @@ -9,8 +9,8 @@ The Sentry Plugin displays issues from [Sentry](https://sentry.io). 1. Install the Sentry Plugin: ```bash -# packages/app - +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-sentry ``` diff --git a/plugins/sonarqube/README.md b/plugins/sonarqube/README.md index 5c12832e57..d0251e91ef 100644 --- a/plugins/sonarqube/README.md +++ b/plugins/sonarqube/README.md @@ -9,7 +9,8 @@ The SonarQube Plugin displays code statistics from [SonarCloud](https://sonarclo 1. Install the SonarQube Plugin: ```bash -# packages/app +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-sonarqube ``` diff --git a/plugins/splunk-on-call/README.md b/plugins/splunk-on-call/README.md index 5c1dda33fc..33cfa091ca 100644 --- a/plugins/splunk-on-call/README.md +++ b/plugins/splunk-on-call/README.md @@ -20,6 +20,8 @@ This plugin provides: Install the plugin: ```bash +# From your Backstage root directory +cd packages/app yarn add @backstage/plugin-splunk-on-call ``` diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md index 7f97f88ed4..814ab6cba5 100644 --- a/plugins/tech-radar/README.md +++ b/plugins/tech-radar/README.md @@ -26,6 +26,7 @@ The Tech Radar can be used in two ways: For either simple or advanced installations, you'll need to add the dependency using Yarn: ```sh +# From your Backstage root directory cd packages/app yarn add @backstage/plugin-tech-radar ``` @@ -35,12 +36,12 @@ yarn add @backstage/plugin-tech-radar Modify your app routes to include the Router component exported from the tech radar, for example: ```tsx -// in packages/app/src/App.tsx +// In packages/app/src/App.tsx import { TechRadarPage } from '@backstage/plugin-tech-radar'; const routes = ( - {/* ... */} + {/* ...other routes */} } diff --git a/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md b/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md index 9f8e1bd739..f41ab1d506 100644 --- a/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md +++ b/plugins/techdocs-backend/examples/documented-component/docs/code/code-sample.md @@ -5,19 +5,19 @@ This page provides some sample code which may be used in your example component. This code uses TypeScript, and the Markdown code fence to wrap the code. ```typescript -const DefaultEntityPage = ({ entity }: { entity: Entity }) => ( - - } - /> - } - /> - +const serviceEntityPage = ( + + + + + + + + + + + + ); ``` diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 632feac3c1..55e5dadb54 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -34,6 +34,8 @@ "@backstage/config": "^0.1.4", "@backstage/catalog-model": "^0.7.7", "@backstage/core": "^0.7.7", + "@backstage/integration": "^0.5.0", + "@backstage/integration-react": "^0.1.1", "@backstage/plugin-catalog-react": "^0.1.4", "@backstage/theme": "^0.2.6", "@backstage/errors": "^0.1.1", diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index 97dd6f2150..e25f245fb4 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -13,21 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* - * 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 { configApiRef, diff --git a/plugins/techdocs/src/reader/components/Reader.test.tsx b/plugins/techdocs/src/reader/components/Reader.test.tsx index b39134b0cd..fbdac95658 100644 --- a/plugins/techdocs/src/reader/components/Reader.test.tsx +++ b/plugins/techdocs/src/reader/components/Reader.test.tsx @@ -14,7 +14,12 @@ * limitations under the License. */ +import { ConfigReader } from '@backstage/config'; import { ApiProvider, ApiRegistry } from '@backstage/core'; +import { + ScmIntegrationsApi, + scmIntegrationsApiRef, +} from '@backstage/integration-react'; import { wrapInTestApp } from '@backstage/test-utils'; import { act, render } from '@testing-library/react'; import React from 'react'; @@ -39,9 +44,15 @@ describe('', () => { entityId: 'Component::backstage', }); + const scmIntegrationsApi: ScmIntegrationsApi = ScmIntegrationsApi.fromConfig( + new ConfigReader({ + integrations: {}, + }), + ); const techdocsStorageApi: Partial = {}; const apiRegistry = ApiRegistry.from([ + [scmIntegrationsApiRef, scmIntegrationsApi], [techdocsStorageApiRef, techdocsStorageApi], ]); diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index b16a566afa..82b5245cfa 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -14,7 +14,8 @@ * limitations under the License. */ import { EntityName } from '@backstage/catalog-model'; -import { configApiRef, useApi } from '@backstage/core'; +import { useApi } from '@backstage/core'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { BackstageTheme } from '@backstage/theme'; import { useTheme } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; @@ -54,7 +55,7 @@ export const Reader = ({ entityId, onReady }: Props) => { const [loadedPath, setLoadedPath] = useState(''); const [atInitialLoad, setAtInitialLoad] = useState(true); const [newerDocsExist, setNewerDocsExist] = useState(false); - const configApi = useApi(configApiRef); + const scmIntegrationsApi = useApi(scmIntegrationsApiRef); const { value: isSynced, @@ -145,7 +146,7 @@ export const Reader = ({ entityId, onReady }: Props) => { rewriteDocLinks(), removeMkdocsHeader(), simplifyMkdocsFooter(), - addGitFeedbackLink(configApi), + addGitFeedbackLink(scmIntegrationsApi), injectCss({ css: ` body { @@ -327,7 +328,7 @@ export const Reader = ({ entityId, onReady }: Props) => { theme.palette.background.default, newerDocsExist, isSynced, - configApi, + scmIntegrationsApi, ]); // docLoadError not considered an error state if sync request is still ongoing diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx index 52ddd3794e..5b638ff668 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx @@ -16,6 +16,11 @@ import React from 'react'; import { TechDocsPage } from './TechDocsPage'; import { render, act } from '@testing-library/react'; +import { ConfigReader } from '@backstage/config'; +import { + ScmIntegrationsApi, + scmIntegrationsApiRef, +} from '@backstage/integration-react'; import { wrapInTestApp } from '@backstage/test-utils'; import { ApiRegistry, ApiProvider } from '@backstage/core'; import { @@ -50,6 +55,11 @@ describe('', () => { entityId: 'Component::backstage', }); + const scmIntegrationsApi: ScmIntegrationsApi = ScmIntegrationsApi.fromConfig( + new ConfigReader({ + integrations: {}, + }), + ); const techdocsApi: Partial = { getEntityMetadata: () => Promise.resolve({ @@ -72,6 +82,7 @@ describe('', () => { }; const apiRegistry = ApiRegistry.from([ + [scmIntegrationsApiRef, scmIntegrationsApi], [techdocsApiRef, techdocsApi], [techdocsStorageApiRef, techdocsStorageApi], ]); diff --git a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts index 4c9ea04fe9..1b53b0141e 100644 --- a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts +++ b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.test.ts @@ -14,16 +14,18 @@ * limitations under the License. */ +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; import { createTestShadowDom } from '../../test-utils'; import { addGitFeedbackLink } from './addGitFeedbackLink'; -const configApi = { - getConfigArray: function getConfigArray(key: string) { - return key === 'integrations.github' - ? [{ data: { host: 'self-hosted-git-hub-provider.com' } }] - : []; - }, -}; +const integrations = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + github: [{ host: 'self-hosted-git-hub-provider.com' }], + }, + }), +); describe('addGitFeedbackLink', () => { it('adds a feedback link when a Gitlab source edit link is available', () => { @@ -38,7 +40,7 @@ describe('addGitFeedbackLink', () => { `, { - preTransformers: [addGitFeedbackLink(configApi)], + preTransformers: [addGitFeedbackLink(integrations)], postTransformers: [], }, ); @@ -63,7 +65,7 @@ describe('addGitFeedbackLink', () => { `, { - preTransformers: [addGitFeedbackLink(configApi)], + preTransformers: [addGitFeedbackLink(integrations)], postTransformers: [], }, ); @@ -87,7 +89,7 @@ describe('addGitFeedbackLink', () => { `, { - preTransformers: [addGitFeedbackLink(configApi)], + preTransformers: [addGitFeedbackLink(integrations)], postTransformers: [], }, ); @@ -107,7 +109,7 @@ describe('addGitFeedbackLink', () => { `, { - preTransformers: [addGitFeedbackLink(configApi)], + preTransformers: [addGitFeedbackLink(integrations)], postTransformers: [], }, ); @@ -127,7 +129,7 @@ describe('addGitFeedbackLink', () => { `, { - preTransformers: [addGitFeedbackLink(configApi)], + preTransformers: [addGitFeedbackLink(integrations)], postTransformers: [], }, ); diff --git a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts index d19d90228c..0fc25d8cf3 100644 --- a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts +++ b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts @@ -15,12 +15,15 @@ */ import type { Transformer } from './index'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined'; import React from 'react'; import ReactDOM from 'react-dom'; // requires repo -export const addGitFeedbackLink = (configApi: any): Transformer => { +export const addGitFeedbackLink = ( + scmIntegrationsApi: ScmIntegrationRegistry, +): Transformer => { return dom => { // attempting to use selectors that are more likely to be static as MkDocs updates over time const sourceAnchor = dom.querySelector( @@ -31,28 +34,12 @@ export const addGitFeedbackLink = (configApi: any): Transformer => { if (!sourceAnchor || !sourceAnchor.href) { return dom; } - let gitHost = ''; const sourceURL = new URL(sourceAnchor.href); - const githubHosts = configApi - .getConfigArray('integrations.github') - .map((integration: any) => integration.data.host); - const gitlabHosts = configApi - .getConfigArray('integrations.gitlab') - .map((integration: any) => integration.data.host); + const integration = scmIntegrationsApi.byUrl(sourceURL); // don't show if can't identify edit link hostname as a gitlab/github hosting - if ( - githubHosts.includes(sourceURL.hostname) || - sourceURL.origin.includes('github') - ) { - gitHost = 'github'; - } else if ( - gitlabHosts.includes(sourceURL.hostname) || - sourceURL.origin.includes('gitlab') - ) { - gitHost = 'gitlab'; - } else { + if (integration?.type !== 'github' && integration?.type !== 'gitlab') { return dom; } @@ -66,7 +53,7 @@ export const addGitFeedbackLink = (configApi: any): Transformer => { const repoPath = sourceURL.pathname.split('/').slice(0, 3).join('/'); const feedbackLink = sourceAnchor.cloneNode() as HTMLAnchorElement; - switch (gitHost) { + switch (integration?.type) { case 'gitlab': feedbackLink.href = `${sourceURL.origin}${repoPath}/issues/new?issue[title]=${issueTitle}&issue[description]=${issueDesc}`; break; diff --git a/yarn.lock b/yarn.lock index b03b6d2a32..b006c1c761 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1608,10 +1608,10 @@ core-js "^2.6.5" regenerator-runtime "^0.13.4" -"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.11.2", "@babel/runtime-corejs3@^7.13.10": - version "7.13.10" - resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.13.10.tgz#14c3f4c85de22ba88e8e86685d13e8861a82fe86" - integrity sha512-x/XYVQ1h684pp1mJwOV4CyvqZXqbc8CMsMGUnAbuc82ZCdv1U63w5RSUzgDSXQHG5Rps/kiksH6g2D5BuaKyXg== +"@babel/runtime-corejs3@^7.10.2", "@babel/runtime-corejs3@^7.11.2", "@babel/runtime-corejs3@^7.13.17": + version "7.14.0" + resolved "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.14.0.tgz#6bf5fbc0b961f8e3202888cb2cd0fb7a0a9a3f66" + integrity sha512-0R0HTZWHLk6G8jIk0FtoX+AatCtKnswS98VhXwGImFc759PJRp4Tru0PQYZofyijTFUr+gT8Mu7sgXVJLQ0ceg== dependencies: core-js-pure "^3.0.0" regenerator-runtime "^0.13.4" @@ -1925,6 +1925,15 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@dabh/diagnostics@^2.0.2": + version "2.0.2" + resolved "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.2.tgz#290d08f7b381b8f94607dc8f471a12c675f9db31" + integrity sha512-+A1YivoVDNNVCdfozHSR8v/jyuuLTMXwjWuxPFlFlUapXoGc+Gj9mDlTDDfrwl7rXCl2tNZ0kE8sIBO6YOn96Q== + dependencies: + colorspace "1.1.x" + enabled "2.0.x" + kuler "^2.0.0" + "@date-io/core@1.x", "@date-io/core@^1.3.13": version "1.3.13" resolved "https://registry.npmjs.org/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa" @@ -3955,11 +3964,13 @@ puka "^1.0.1" read-package-json-fast "^2.0.1" -"@octokit/auth-app@^2.10.5": - version "2.10.5" - resolved "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-2.10.5.tgz#85d69cb96818f5da34bf0b81bb637d3675ad4e9a" - integrity sha512-6yXyjtcBWpuPYSdZN8z8IIjGSqkPmiJzdmCdod8at41ANB1FtaKbUIDL5+IkG+svv68NIYs+XORbhBRFXYB3bw== +"@octokit/auth-app@^3.4.0": + version "3.4.0" + resolved "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-3.4.0.tgz#af9f68512e7b8dd071b49e1470a1ddf88ff6a3a3" + integrity sha512-zBVgTnLJb0uoNMGCpcDkkAbPeavHX7oAjJkaDv2nqMmsXSsCw4AbUhjl99EtJQG/JqFY/kLFHM9330Wn0k70+g== dependencies: + "@octokit/auth-oauth-app" "^4.1.0" + "@octokit/auth-oauth-user" "^1.2.3" "@octokit/request" "^5.4.11" "@octokit/request-error" "^2.0.0" "@octokit/types" "^6.0.3" @@ -3969,6 +3980,41 @@ universal-github-app-jwt "^1.0.1" universal-user-agent "^6.0.0" +"@octokit/auth-oauth-app@^4.1.0": + version "4.1.2" + resolved "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-4.1.2.tgz#bf3ff30c260e6e9f10b950386f279befb8fe907d" + integrity sha512-bdNGNRmuDJjKoHla3mUGtkk/xcxKngnQfBEnyk+7VwMqrABKvQB1wQRSrwSWkPPUX7Lcj2ttkPAPG7+iBkMRnw== + dependencies: + "@octokit/auth-oauth-device" "^3.1.1" + "@octokit/auth-oauth-user" "^1.2.1" + "@octokit/request" "^5.3.0" + "@octokit/types" "^6.0.3" + "@types/btoa-lite" "^1.0.0" + btoa-lite "^1.0.0" + universal-user-agent "^6.0.0" + +"@octokit/auth-oauth-device@^3.1.1": + version "3.1.1" + resolved "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-3.1.1.tgz#380499f9a850425e2c7bdeb62afc070181c536a9" + integrity sha512-ykDZROilszXZJ6pYdl6SZ15UZniCs0zDcKgwOZpMz3U0QDHPUhFGXjHToBCAIHwbncMu+jLt4/Nw4lq3FwAw/w== + dependencies: + "@octokit/oauth-methods" "^1.1.0" + "@octokit/request" "^5.4.14" + "@octokit/types" "^6.10.0" + universal-user-agent "^6.0.0" + +"@octokit/auth-oauth-user@^1.2.1", "@octokit/auth-oauth-user@^1.2.3": + version "1.2.4" + resolved "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-1.2.4.tgz#3594eb7d40cb462240e7e90849781dfa0045aed5" + integrity sha512-efOajupCZBP1veqx5w59Qey0lIud1rDUgxTRjjkQDU3eOBmkAasY1pXemDsQwW0I85jb1P/gn2dMejedVxf9kw== + dependencies: + "@octokit/auth-oauth-device" "^3.1.1" + "@octokit/oauth-methods" "^1.1.0" + "@octokit/request" "^5.4.14" + "@octokit/types" "^6.12.2" + btoa-lite "^1.0.0" + universal-user-agent "^6.0.0" + "@octokit/auth-token@^2.4.4": version "2.4.4" resolved "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.4.tgz#ee31c69b01d0378c12fd3ffe406030f3d94d3b56" @@ -4006,20 +4052,31 @@ "@octokit/types" "^6.0.3" universal-user-agent "^6.0.0" +"@octokit/oauth-authorization-url@^4.3.1": + version "4.3.1" + resolved "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-4.3.1.tgz#008d09bf427a7f61c70b5283040d60a456011a51" + integrity sha512-sI/SOEAvzRhqdzj+kJl+2ifblRve2XU6ZB36Lq25Su8R31zE3GoKToSLh64nWFnKePNi2RrdcMm94UEIQZslOw== + +"@octokit/oauth-methods@^1.1.0": + version "1.2.2" + resolved "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-1.2.2.tgz#3d98c548aa2ace36ad8d0ce6593fd49dcbe103cc" + integrity sha512-CFMUMn9DdPLMcpffhKgkwIIClfv0ZToJM4qcg4O0egCoHMYkVlxl22bBoo9qCnuF1U/xn871KEXuozKIX+bA2w== + dependencies: + "@octokit/oauth-authorization-url" "^4.3.1" + "@octokit/request" "^5.4.14" + "@octokit/request-error" "^2.0.5" + "@octokit/types" "^6.12.2" + btoa-lite "^1.0.0" + "@octokit/openapi-types@^2.2.0": version "2.2.0" resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-2.2.0.tgz#123e0438a0bc718ccdac3b5a2e69b3dd00daa85b" integrity sha512-274lNUDonw10kT8wHg8fCcUc1ZjZHbWv0/TbAwb0ojhBQqZYc1cQ/4yqTVTtPMDeZ//g7xVEYe/s3vURkRghPg== -"@octokit/openapi-types@^5.3.2": - version "5.3.2" - 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/openapi-types@^7.0.0": + version "7.0.0" + resolved "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-7.0.0.tgz#0f6992db9854af15eca77d71ab0ec7fad2f20411" + integrity sha512-gV/8DJhAL/04zjTI95a7FhQwS6jlEE0W/7xeYAzuArD0KVAVWDLP2f3vi98hs3HLTczxXdRK/mF0tRoQPpolEw== "@octokit/plugin-enterprise-rest@^6.0.1": version "6.0.1" @@ -4038,64 +4095,44 @@ resolved "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz#394d59ec734cd2f122431fbaf05099861ece3c44" integrity sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg== -"@octokit/plugin-rest-endpoint-methods@4.13.5": - version "4.13.5" - resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.13.5.tgz#ad76285b82fe05fbb4adf2774a9c887f3534a880" - integrity sha512-kYKcWkFm4Ldk8bZai2RVEP1z97k1C/Ay2FN9FNTBg7JIyKoiiJjks4OtT6cuKeZX39tqa+C3J9xeYc6G+6g8uQ== +"@octokit/plugin-rest-endpoint-methods@5.0.1": + version "5.0.1" + resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.1.tgz#631b8d4edc6798b03489911252a25f2a4e58c594" + integrity sha512-vvWbPtPqLyIzJ7A4IPdTl+8IeuKAwMJ4LjvmqWOOdfSuqWQYZXq2CEd0hsnkidff2YfKlguzujHs/reBdAx8Sg== dependencies: - "@octokit/types" "^6.12.2" + "@octokit/types" "^6.13.1" 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== +"@octokit/request-error@^2.0.0", "@octokit/request-error@^2.0.5": + version "2.0.5" + resolved "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143" + integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg== 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" - integrity sha512-2BrmnvVSV1MXQvEkrb9zwzP0wXFNbPJij922kYBTLIlIafukrGOb+ABBT2+c6wZiuyWDH1K1zmjGQ0toN/wMWw== - dependencies: - "@octokit/types" "^5.0.1" + "@octokit/types" "^6.0.3" deprecation "^2.0.0" once "^1.4.0" -"@octokit/request@^5.3.0", "@octokit/request@^5.4.11", "@octokit/request@^5.4.12": - version "5.4.13" - resolved "https://registry.npmjs.org/@octokit/request/-/request-5.4.13.tgz#eec5987b3e96f984fc5f41967e001170c6d23a18" - integrity sha512-WcNRH5XPPtg7i1g9Da5U9dvZ6YbTffw9BN2rVezYiE7couoSyaRsw0e+Tl8uk1fArHE7Dn14U7YqUDy59WaqEw== +"@octokit/request@^5.3.0", "@octokit/request@^5.4.11", "@octokit/request@^5.4.12", "@octokit/request@^5.4.14": + version "5.4.15" + resolved "https://registry.npmjs.org/@octokit/request/-/request-5.4.15.tgz#829da413dc7dd3aa5e2cdbb1c7d0ebe1f146a128" + integrity sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag== dependencies: "@octokit/endpoint" "^6.0.1" "@octokit/request-error" "^2.0.0" - "@octokit/types" "^6.0.3" - deprecation "^2.0.0" + "@octokit/types" "^6.7.1" is-plain-object "^5.0.0" node-fetch "^2.6.1" - once "^1.4.0" universal-user-agent "^6.0.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== +"@octokit/rest@^18.1.0", "@octokit/rest@^18.1.1", "@octokit/rest@^18.5.3": + version "18.5.3" + resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.5.3.tgz#6a2e6006a87ebbc34079c419258dd29ec9ff659d" + integrity sha512-KPAsUCr1DOdLVbZJgGNuE/QVLWEaVBpFQwDAz/2Cnya6uW2wJ/P5RVGk0itx7yyN1aGa8uXm2pri4umEqG1JBA== 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" "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/plugin-rest-endpoint-methods" "5.0.1" "@octokit/types@^5.0.0", "@octokit/types@^5.0.1": version "5.5.0" @@ -4104,19 +4141,12 @@ dependencies: "@types/node" ">= 8" -"@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== +"@octokit/types@^6.0.0", "@octokit/types@^6.0.1", "@octokit/types@^6.0.3", "@octokit/types@^6.10.0", "@octokit/types@^6.12.2", "@octokit/types@^6.13.0", "@octokit/types@^6.13.1", "@octokit/types@^6.7.1", "@octokit/types@^6.8.2": + version "6.14.2" + resolved "https://registry.npmjs.org/@octokit/types/-/types-6.14.2.tgz#64c9457f38fb8522bdbba3c8cc814590a2d61bf5" + integrity sha512-wiQtW9ZSy4OvgQ09iQOdyXYNN60GqjCL/UdMsepDr1Gr0QzpW6irIKbH3REuAHXAhxkEk9/F2a3Gcs1P6kW5jA== dependencies: - "@octokit/openapi-types" "^5.3.2" - -"@octokit/types@^6.13.0", "@octokit/types@^6.8.2": - version "6.13.0" - resolved "https://registry.npmjs.org/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0" - integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA== - dependencies: - "@octokit/openapi-types" "^6.0.0" + "@octokit/openapi-types" "^7.0.0" "@open-draft/until@^1.0.3": version "1.0.3" @@ -5711,6 +5741,11 @@ resolved "https://registry.npmjs.org/@types/braces/-/braces-3.0.0.tgz#7da1c0d44ff1c7eb660a36ec078ea61ba7eb42cb" integrity sha512-TbH79tcyi9FHwbyboOKeRachRq63mSuWYXOflsNO9ZyE5ClQ/JaozNKl+aWUq87qPNsXasXxi2AbgfwIJ+8GQw== +"@types/btoa-lite@^1.0.0": + version "1.0.0" + resolved "https://registry.npmjs.org/@types/btoa-lite/-/btoa-lite-1.0.0.tgz#e190a5a548e0b348adb0df9ac7fa5f1151c7cca4" + integrity sha512-wJsiX1tosQ+J5+bY5LrSahHxr2wT+uME5UDwdN1kg4frt40euqA+wzECkmq4t5QbveHiJepfdThgQrPw6KiSlg== + "@types/cacheable-request@^6.0.1": version "6.0.1" resolved "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" @@ -6268,9 +6303,9 @@ "@types/node" "*" "@types/ldapjs@^1.0.9": - version "1.0.9" - resolved "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-1.0.9.tgz#1224192d14cc5ab5218fcea72ebb04489c52cb95" - integrity sha512-3PvY7Drp1zoLbcGlothCAkoc5o6Jp9KvUPwHadlHyKp3yPvyeIh7w2zQc9UXMzgDRkoeGXUEODtbEs5XCh9ZyA== + version "1.0.10" + resolved "https://registry.npmjs.org/@types/ldapjs/-/ldapjs-1.0.10.tgz#bac705c9e154b97d69496b5213cc28dbe9715a37" + integrity sha512-AMkMxkK/wjYtWebNH2O+rARfo7scBpW3T23g6zmGCwDgbyDbR79XWpcSqhPWdU+fChaF+I3dVyl9X2dT1CyI9w== dependencies: "@types/node" "*" @@ -6566,14 +6601,14 @@ "@types/reactcss" "*" "@types/react-dev-utils@^9.0.4": - version "9.0.4" - resolved "https://registry.npmjs.org/@types/react-dev-utils/-/react-dev-utils-9.0.4.tgz#3e4bee79b7536777cef219427ab1d38adc24f3f2" - integrity sha512-8cv9rAeSP1EmyRQAbZ/i6uYtai1VoKHGSBwDyCLM82wCkqoh3WPjJgI1pfi2kiLc0C5hNU7DLo7/c4hylfHLWg== + version "9.0.5" + resolved "https://registry.npmjs.org/@types/react-dev-utils/-/react-dev-utils-9.0.5.tgz#3265699edaba0078256d91b7793f98c4fb9bf6cf" + integrity sha512-uUsdRdc4LU1WQrfw3htnRIRchyOOsBnmnByK6H3oZMBdjmeyH1f7Cfy64YixQ3T3Dzy5gwGilcJVSDyOFjVBWQ== dependencies: "@types/eslint" "*" "@types/express" "*" "@types/html-webpack-plugin" "*" - "@types/webpack" "*" + "@types/webpack" "^4" "@types/webpack-dev-server" "*" "@types/react-dom@^16.9.8": @@ -8133,7 +8168,7 @@ async@^2.0.1, async@^2.6.1, async@^2.6.2: dependencies: lodash "^4.17.14" -async@^3.2.0: +async@^3.1.0, async@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720" integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw== @@ -9032,6 +9067,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +btoa-lite@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz#337766da15801210fdd956c22e9c6891ab9d0337" + integrity sha1-M3dm2hWAEhD92VbCLpxokaudAzc= + btoa@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz#01a9909f8b2c93f6bf680ba26131eb30f7fa3d73" @@ -9677,10 +9717,10 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.5, classnames@^2.2.6: - version "2.2.6" - resolved "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" - integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== +classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" + integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== clean-css@^4.2.3: version "4.2.3" @@ -9993,10 +10033,10 @@ colorette@1.2.1, colorette@^1.2.1: resolved "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== -colornames@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" - integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= +colorette@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" + integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== colors@^1.1.2, colors@^1.2.1: version "1.4.0" @@ -10451,9 +10491,9 @@ core-js-pure@^3.0.0, core-js-pure@^3.0.1: integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== core-js@3, core-js@^3.0.1, core-js@^3.0.4, core-js@^3.5.0, core-js@^3.6.0, core-js@^3.6.5: - version "3.11.0" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.11.0.tgz#05dac6aa70c0a4ad842261f8957b961d36eb8926" - integrity sha512-bd79DPpx+1Ilh9+30aT5O1sgpQd4Ttg8oqkqi51ZzhedMM1omD2e6IOF48Z/DzDCZ2svp49tN/3vneTK6ZBkXw== + version "3.11.2" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.11.2.tgz#af087a43373fc6e72942917c4a4c3de43ed574d6" + integrity sha512-3tfrrO1JpJSYGKnd9LKTBPqgUES/UYiCzMKeqwR1+jF16q4kD1BY2NvqkfuzXwQ6+CIWm55V9cjD7PQd+hijdw== core-js@^2.4.0, core-js@^2.5.7, core-js@^2.6.10, core-js@^2.6.5: version "2.6.11" @@ -11591,15 +11631,6 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" -diagnostics@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" - integrity sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ== - dependencies: - colorspace "1.1.x" - enabled "1.0.x" - kuler "1.0.x" - dicer@0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz#eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872" @@ -11832,10 +11863,10 @@ domhandler@^4.0.0: dependencies: domelementtype "^2.1.0" -dompurify@^2.0.12, dompurify@^2.1.1, dompurify@^2.2.7: - version "2.2.7" - resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.2.7.tgz#a5f055a2a471638680e779bd08fc334962d11fd8" - integrity sha512-jdtDffdGNY+C76jvodNTu9jt5yYj59vuTUyx+wXdzcSwAGTYZDAQkQ7Iwx9zcGrA4ixC1syU4H3RZROqRxokxg== +dompurify@^2.0.12, dompurify@^2.1.1, dompurify@^2.2.8: + version "2.2.8" + resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.2.8.tgz#ce88e395f6d00b6dc53f80d6b2a6fdf5446873c6" + integrity sha512-9H0UL59EkDLgY3dUFjLV6IEUaHm5qp3mxSqWw7Yyx4Zhk2Jn2cmLe+CNPP3xy13zl8Bqg+0NehQzkdMoVhGRww== domutils@1.5.1: version "1.5.1" @@ -12119,12 +12150,10 @@ emotion-theming@^10.0.19: "@emotion/weak-memoize" "0.2.5" hoist-non-react-statics "^3.3.0" -enabled@1.0.x: - version "1.0.2" - resolved "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" - integrity sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M= - dependencies: - env-variable "0.0.x" +enabled@2.0.x: + version "2.0.0" + resolved "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" + integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== encodeurl@~1.0.2: version "1.0.2" @@ -12190,11 +12219,6 @@ env-paths@^2.2.0: resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43" integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA== -env-variable@0.0.x: - version "0.0.6" - resolved "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz#74ab20b3786c545b62b4a4813ab8cf22726c9808" - integrity sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg== - envinfo@^7.7.4: version "7.7.4" resolved "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz#c6311cdd38a0e86808c1c9343f667e4267c4a320" @@ -13491,6 +13515,11 @@ fn-name@~3.0.0: resolved "https://registry.npmjs.org/fn-name/-/fn-name-3.0.0.tgz#0596707f635929634d791f452309ab41558e3c5c" integrity sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA== +fn.name@1.x.x: + version "1.1.0" + resolved "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz#26cad8017967aea8731bc42961d04a3d5988accc" + integrity sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw== + follow-redirects@^1.0.0, follow-redirects@^1.10.0: version "1.13.0" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" @@ -17071,9 +17100,9 @@ jsonpointer@^4.0.1: integrity sha1-T9kss04OnbPInIYi7PUfm5eMbLk= jsonschema@^1.2.6: - version "1.2.7" - resolved "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.7.tgz#4e6d6dc4d83dc80707055ba22c00ec6152c0e6e9" - integrity sha512-3dFMg9hmI9LdHag/BRIhMefCfbq1hicvYMy8YhZQorAdzOzWz7NjniSpn39yjpzUAMIWtGyyZuH2KNBloH7ZLw== + version "1.4.0" + resolved "https://registry.npmjs.org/jsonschema/-/jsonschema-1.4.0.tgz#1afa34c4bc22190d8e42271ec17ac8b3404f87b2" + integrity sha512-/YgW6pRMr6M7C+4o8kS+B/2myEpHCrxO4PEWnqJNBFMjn7EWXqlQ4tGwL6xTHeRplwuZmcAncdvfOad1nT2yMw== jsonwebtoken@^8.5.1: version "8.5.1" @@ -17341,12 +17370,10 @@ knex@^0.95.1: tarn "^3.0.1" tildify "2.0.0" -kuler@1.0.x: - version "1.0.1" - resolved "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6" - integrity sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ== - dependencies: - colornames "^1.1.1" +kuler@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" + integrity sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A== language-subtag-registry@~0.3.2: version "0.3.21" @@ -17946,7 +17973,7 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" -logform@^2.1.1: +logform@^2.1.1, logform@^2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/logform/-/logform-2.2.0.tgz#40f036d19161fc76b68ab50fdc7fe495544492f2" integrity sha512-N0qPlqfypFx7UHNn4B3lzS/b0uLqt2hmuoa+PpuXNYgozdJYAyauF5Ky0BWVjrxDlMWiT3qN4zPq3vVAfZy7Yg== @@ -19079,10 +19106,10 @@ nanoid@^2.1.0: resolved "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280" integrity sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA== -nanoid@^3.1.20: - version "3.1.20" - resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" - integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw== +nanoid@^3.1.22: + version "3.1.22" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844" + integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ== nanomatch@^1.2.9: version "1.2.13" @@ -19823,10 +19850,12 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -one-time@0.0.4: - version "0.0.4" - resolved "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" - integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= +one-time@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz#e06bc174aed214ed58edede573b433bbf827cb45" + integrity sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g== + dependencies: + fn.name "1.x.x" onetime@^1.0.0: version "1.1.0" @@ -21302,13 +21331,13 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^ source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.0.2: - version "8.2.6" - resolved "https://registry.npmjs.org/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe" - integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg== +postcss@^8.0.2, postcss@^8.1.0: + version "8.2.13" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.2.13.tgz#dbe043e26e3c068e45113b1ed6375d2d37e2129f" + integrity sha512-FCE5xLH+hjbzRdpbRb1IMCvPv9yZx2QnDarBEYSN0N0HYk+TcXsEhwdFcFb+SRWOKzKGErhIEbBK2ogyLdTtfQ== dependencies: - colorette "^1.2.1" - nanoid "^3.1.20" + colorette "^1.2.2" + nanoid "^3.1.22" source-map "^0.6.1" postgres-array@~1.0.0: @@ -22074,7 +22103,7 @@ react-helmet@6.1.0: react-fast-compare "^3.1.1" react-side-effect "^2.1.0" -react-hook-form@^6.15.4, react-hook-form@^6.6.0: +react-hook-form@^6.15.4: version "6.15.4" resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.15.4.tgz#328003e1ccc096cd158899ffe7e3b33735a9b024" integrity sha512-K+Sw33DtTMengs8OdqFJI3glzNl1wBzSefD/ksQw/hJf9CnOHQAU6qy82eOrh0IRNt2G53sjr7qnnw1JDjvx1w== @@ -22590,7 +22619,7 @@ read@1, read@~1.0.1: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -23583,12 +23612,7 @@ sentence-case@^3.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" -serialize-error@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" - integrity sha1-ULZ51WNc34Rme9yOWa9OW4HV9go= - -serialize-error@^8.0.1: +serialize-error@^8.0.1, serialize-error@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz#3a069970c712f78634942ddd50fbbc0eaebe2f67" integrity sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ== @@ -24859,10 +24883,10 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -swagger-client@^3.13.1: - version "3.13.1" - resolved "https://registry.npmjs.org/swagger-client/-/swagger-client-3.13.1.tgz#d2531ede4a498911aa084384e387e5008e7b6a70" - integrity sha512-Hmy4+wVVa3kveWzC7PIeUwiAY5qcYbm4XlC4uZ7e5kAePfB2cprXImiqrZHIzL+ndU0YTN7I+9w/ZayTisn3Jg== +swagger-client@^3.13.2: + version "3.13.2" + resolved "https://registry.npmjs.org/swagger-client/-/swagger-client-3.13.2.tgz#df1a1fe92e6db87ab5f76cee6381dcb7da1ad117" + integrity sha512-kamtyXtmbZiA2C5YTVqJYgoPJgzqtM5RbeP23Rt/YPYjMArTKZ2fjx1UTsI0aSbws0GluU5pVHiGp8YMciSUfw== dependencies: "@babel/runtime-corejs3" "^7.11.2" btoa "^1.2.1" @@ -24880,19 +24904,19 @@ swagger-client@^3.13.1: url "~0.11.0" swagger-ui-react@^3.37.2: - version "3.45.1" - resolved "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-3.45.1.tgz#6c8e551c5c1d443e7195acd9614f2f6bc4557daf" - integrity sha512-ATXMVIb5YaxxmHJdvPq1nnXxsJ1FCo02fQI2gExEtZCQkouMThZh50DRxdKYNae53Ovz3w8glxxwmsMuWtmKaQ== + version "3.48.0" + resolved "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-3.48.0.tgz#f53e1b7de5bc2628635a2d832e254043badb3a91" + integrity sha512-39cibLdJYeujP5CAzBg+LiNFyIuHomi4Blru/1hyFyiTkFx9yYGvcFpZrKFKq1Me4GSUCQJ1DzUyUMblo+VFcA== dependencies: - "@babel/runtime-corejs3" "^7.13.10" + "@babel/runtime-corejs3" "^7.13.17" "@braintree/sanitize-url" "^5.0.0" "@kyleshockey/object-assign-deep" "^0.4.2" "@kyleshockey/xml" "^1.0.2" base64-js "^1.5.1" - classnames "^2.2.6" + classnames "^2.3.1" css.escape "1.5.1" deep-extend "0.6.0" - dompurify "^2.2.7" + dompurify "^2.2.8" ieee754 "^1.2.1" immutable "^3.x.x" js-file-download "^0.4.12" @@ -24913,9 +24937,9 @@ swagger-ui-react@^3.37.2: redux-immutable "3.1.0" remarkable "^2.0.1" reselect "^4.0.0" - serialize-error "^2.1.0" + serialize-error "^8.1.0" sha.js "^2.4.11" - swagger-client "^3.13.1" + swagger-client "^3.13.2" url-parse "^1.5.1" xml-but-prettier "^1.0.1" zenscroll "^4.0.2" @@ -26420,6 +26444,11 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" +wait-for-expect@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-3.0.2.tgz#d2f14b2f7b778c9b82144109c8fa89ceaadaa463" + integrity sha512-cfS1+DZxuav1aBYbaO/kE06EOS8yRw7qOFoD3XtjTkYvCvh3zUvNST8DXK/nPaeqIzIv3P3kL3lRJn8iwOiSag== + wait-on@5.3.0: version "5.3.0" resolved "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz#584e17d4b3fe7b46ac2b9f8e5e102c005c2776c7" @@ -26786,28 +26815,28 @@ windows-release@^3.1.0: dependencies: execa "^1.0.0" -winston-transport@^4.3.0: - version "4.3.0" - resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz#df68c0c202482c448d9b47313c07304c2d7c2c66" - integrity sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A== +winston-transport@^4.4.0: + version "4.4.0" + resolved "https://registry.npmjs.org/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59" + integrity sha512-Lc7/p3GtqtqPBYYtS6KCN3c77/2QCev51DvcJKbkFPQNoj1sinkGwLGFDxkXY9J6p9+EPnYs+D90uwbnaiURTw== dependencies: - readable-stream "^2.3.6" + readable-stream "^2.3.7" triple-beam "^1.2.0" winston@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz#63061377976c73584028be2490a1846055f77f07" - integrity sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw== + version "3.3.3" + resolved "https://registry.npmjs.org/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" + integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== dependencies: - async "^2.6.1" - diagnostics "^1.1.1" - is-stream "^1.1.0" - logform "^2.1.1" - one-time "0.0.4" - readable-stream "^3.1.1" + "@dabh/diagnostics" "^2.0.2" + async "^3.1.0" + is-stream "^2.0.0" + logform "^2.2.0" + one-time "^1.0.0" + readable-stream "^3.4.0" stack-trace "0.0.x" triple-beam "^1.3.0" - winston-transport "^4.3.0" + winston-transport "^4.4.0" word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3"