diff --git a/.changeset/analyze-software-creation.md b/.changeset/analyze-software-creation.md new file mode 100644 index 0000000000..312987d3d1 --- /dev/null +++ b/.changeset/analyze-software-creation.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Basic analytics instrumentation is now in place: + +- As users make their way through template steps, a `click` event is fired, including the step number. +- After a user clicks "Create" a `create` event is fired, including the name of the software that was just created. The template used at creation is set on the `entityRef` context key. diff --git a/.changeset/analyze-software-exploration.md b/.changeset/analyze-software-exploration.md new file mode 100644 index 0000000000..f535f60127 --- /dev/null +++ b/.changeset/analyze-software-exploration.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Both `EntityProvider` and `AsyncEntityProvider` contexts now wrap all children with an `AnalyticsContext` containing the corresponding `entityRef`; this opens up the possibility for all events underneath these contexts to be associated with and aggregated by the corresponding entity. diff --git a/.changeset/beige-gorillas-sip.md b/.changeset/beige-gorillas-sip.md new file mode 100644 index 0000000000..b0dcaadfc6 --- /dev/null +++ b/.changeset/beige-gorillas-sip.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fix logic bug that broke techdocs-cli-embedded-app diff --git a/.changeset/big-islands-add.md b/.changeset/big-islands-add.md new file mode 100644 index 0000000000..4a6511562b --- /dev/null +++ b/.changeset/big-islands-add.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Set the `searchTooltip` to "Filter" to follow how the `searchPlaceholder` is set making this more consistent diff --git a/.changeset/blue-lions-greet.md b/.changeset/blue-lions-greet.md deleted file mode 100644 index c4f4d2a337..0000000000 --- a/.changeset/blue-lions-greet.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Handle errors that may occur when the user logs out diff --git a/.changeset/brave-eels-allow.md b/.changeset/brave-eels-allow.md new file mode 100644 index 0000000000..8fbe7ba8d2 --- /dev/null +++ b/.changeset/brave-eels-allow.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': minor +--- + +Updated the React Router wiring to make use of the new `basename` property of the router components in React Router v6 stable. To implement this, a new optional `basename` property has been added to the `Router` app component, which can be forwarded to the concrete router implementation in order to support this new behavior. This is done by default in any app that does not have a `Router` component override. diff --git a/.changeset/brave-goats-rush.md b/.changeset/brave-goats-rush.md deleted file mode 100644 index 8a14f8ea00..0000000000 --- a/.changeset/brave-goats-rush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-node': minor ---- - -Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` so make sure imports are updated. diff --git a/.changeset/brave-peaches-brush.md b/.changeset/brave-peaches-brush.md deleted file mode 100644 index f0661ba818..0000000000 --- a/.changeset/brave-peaches-brush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Making the description of the GitLab repoUrl owner field more clearer by focusing it refers to the GitLab namespace. diff --git a/.changeset/breezy-pots-worry.md b/.changeset/breezy-pots-worry.md deleted file mode 100644 index 48d5ae6c80..0000000000 --- a/.changeset/breezy-pots-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -consistently show parent and child relations in group profile card diff --git a/.changeset/bright-rules-shout.md b/.changeset/bright-rules-shout.md deleted file mode 100644 index f7cdae3b4f..0000000000 --- a/.changeset/bright-rules-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Defer `ajv` compilation of schema validators to improve module-import performance diff --git a/.changeset/brown-days-pretend.md b/.changeset/brown-days-pretend.md new file mode 100644 index 0000000000..4b6a328724 --- /dev/null +++ b/.changeset/brown-days-pretend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights': patch +--- + +Fixed bug when sending data by Post in `runChecks` and `runBulkChecks` functions of the `TechInsightsClient` class, the default `Content-Type` used was `plain/text` diff --git a/.changeset/brown-grapes-battle.md b/.changeset/brown-grapes-battle.md deleted file mode 100644 index f94c938b02..0000000000 --- a/.changeset/brown-grapes-battle.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-bitbucket-cloud': minor ---- - -Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. - -Please find how to configure the schedule at the config at -https://backstage.io/docs/integrations/bitbucketCloud/discovery diff --git a/.changeset/calm-bottles-happen.md b/.changeset/calm-bottles-happen.md new file mode 100644 index 0000000000..e76db28065 --- /dev/null +++ b/.changeset/calm-bottles-happen.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-common': minor +--- + +**BREAKING CHANGE**: The `UrlReader` interface has been updated to require that `readUrl` is implemented. `readUrl` has previously been optional to implement but a warning has been logged when calling its predecessor `read`. +The `read` method is now deprecated and will be removed in a future release. diff --git a/.changeset/calm-moose-fetch.md b/.changeset/calm-moose-fetch.md deleted file mode 100644 index 96a364c801..0000000000 --- a/.changeset/calm-moose-fetch.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-react': minor -'@backstage/plugin-catalog': patch ---- - -Implemented the visual parts of `EntityKindPicker` so that it can be shown alongside the other filters on the left side of your catalog pages. diff --git a/.changeset/calm-pianos-burn.md b/.changeset/calm-pianos-burn.md deleted file mode 100644 index 6a44d42cfe..0000000000 --- a/.changeset/calm-pianos-burn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Added two new `EntityRefLinks` props, the first being `getTitle` that allows for customization of the title used for each link. The second one is `fetchEntities`, which triggers a fetching of all entities so that the full entity definition is available in the `getTitle` callback. diff --git a/.changeset/chatty-planets-flash.md b/.changeset/chatty-planets-flash.md new file mode 100644 index 0000000000..d6a5c6ad42 --- /dev/null +++ b/.changeset/chatty-planets-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Add `gitlabDiscoveryEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/clean-camels-sneeze.md b/.changeset/clean-camels-sneeze.md deleted file mode 100644 index 0fccb52e0b..0000000000 --- a/.changeset/clean-camels-sneeze.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Newly created Backstage repositories now use the stable version 6 of -`react-router`, just like the main repo does. Please let us know if you find any -issues with this. - -Migrating to the stable version of `react-router` is optional for the time -being. But if you want to do the same for your existing repository, please -follow [this -guide](https://backstage.io/docs/tutorials/react-router-stable-migration). diff --git a/.changeset/clean-feet-remain.md b/.changeset/clean-feet-remain.md new file mode 100644 index 0000000000..c712ef9e1e --- /dev/null +++ b/.changeset/clean-feet-remain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-github-issues': patch +--- + +Stripping specific issues URL already present to target base issues URL. diff --git a/.changeset/clean-planets-rhyme.md b/.changeset/clean-planets-rhyme.md new file mode 100644 index 0000000000..a6878b491b --- /dev/null +++ b/.changeset/clean-planets-rhyme.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-azure': patch +--- + +`AzureDevOpsEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/azure/discovery diff --git a/.changeset/clever-ties-burn.md b/.changeset/clever-ties-burn.md deleted file mode 100644 index cdd861e069..0000000000 --- a/.changeset/clever-ties-burn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Prevent `.set()` to execute a request to the StorageClient if the user is `guest` diff --git a/.changeset/cuddly-bikes-tease.md b/.changeset/cuddly-bikes-tease.md deleted file mode 100644 index d3fc8bd1bb..0000000000 --- a/.changeset/cuddly-bikes-tease.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -Fixed a bug where gathered index routes would fail to bind routable extensions. This would typically show up when placing a routable extension in the entity page overview tab. diff --git a/.changeset/curly-rats-itch.md b/.changeset/curly-rats-itch.md deleted file mode 100644 index b6fba9dc1e..0000000000 --- a/.changeset/curly-rats-itch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Tweak the Jest Caching loader to only operate when in `watch` mode diff --git a/.changeset/curvy-kiwis-fold.md b/.changeset/curvy-kiwis-fold.md deleted file mode 100644 index 8d94f38343..0000000000 --- a/.changeset/curvy-kiwis-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Update versions of packages used in the create-app template, to match those in the main repo diff --git a/.changeset/curvy-lemons-change.md b/.changeset/curvy-lemons-change.md deleted file mode 100644 index 29e31a7b60..0000000000 --- a/.changeset/curvy-lemons-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -Fixed handling of root scoped services in `startTestBackend`. diff --git a/.changeset/curvy-pets-wash.md b/.changeset/curvy-pets-wash.md deleted file mode 100644 index 4c824f9ef4..0000000000 --- a/.changeset/curvy-pets-wash.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': minor ---- - -Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. - -Moved the following types from this package to `@backstage/plugin-catalog-backend`. - -- AnalyzeLocationResponse -- AnalyzeLocationRequest -- AnalyzeLocationExistingEntity -- AnalyzeLocationGenerateEntity -- AnalyzeLocationEntityField diff --git a/.changeset/dirty-birds-burn.md b/.changeset/dirty-birds-burn.md new file mode 100644 index 0000000000..98f2c5967d --- /dev/null +++ b/.changeset/dirty-birds-burn.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +`MicrosoftGraphOrgEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-msgraph#readme diff --git a/.changeset/dry-shirts-attack.md b/.changeset/dry-shirts-attack.md deleted file mode 100644 index ba6c7379f5..0000000000 --- a/.changeset/dry-shirts-attack.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Support custom layouts in dry run editor diff --git a/.changeset/dull-oranges-tap.md b/.changeset/dull-oranges-tap.md new file mode 100644 index 0000000000..946656f610 --- /dev/null +++ b/.changeset/dull-oranges-tap.md @@ -0,0 +1,25 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +Add ability to configure filters when using EntityListDocsGrid + +The following example will render two sections of cards grid: + +- One section for documentations tagged as `recommended` +- One section for documentations tagged as `runbook` + +```js + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: "RunBooks Documentation", + filterPredicate: entity => + entity?.metadata?.tags?.includes('runbook') ?? false, + } +]}} /> +``` diff --git a/.changeset/dull-rocks-warn.md b/.changeset/dull-rocks-warn.md deleted file mode 100644 index 0ccf4f20cb..0000000000 --- a/.changeset/dull-rocks-warn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Added a new property called `additionalTemplateGlobals` which allows you to add global functions to the scaffolder nunjucks templates. diff --git a/.changeset/early-rocks-smell.md b/.changeset/early-rocks-smell.md deleted file mode 100644 index e35554f03a..0000000000 --- a/.changeset/early-rocks-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-bazaar': patch ---- - -Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. diff --git a/.changeset/eight-pears-attack.md b/.changeset/eight-pears-attack.md new file mode 100644 index 0000000000..ca18abb2db --- /dev/null +++ b/.changeset/eight-pears-attack.md @@ -0,0 +1,17 @@ +--- +'@backstage/plugin-catalog-backend-module-github': minor +--- + +BREAKING: Support authenticated backends by including a server token for catalog requests. The constructor of `GithubLocationAnalyzer` now requires an instance of `TokenManager` to be supplied: + +```diff +... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, ++ tokenManager: env.tokenManager, + }), + ); +... +``` diff --git a/.changeset/eleven-apples-accept.md b/.changeset/eleven-apples-accept.md deleted file mode 100644 index f46bf4fe0c..0000000000 --- a/.changeset/eleven-apples-accept.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Deep-import LightAsync component to improve module-import speed diff --git a/.changeset/eleven-pets-sneeze.md b/.changeset/eleven-pets-sneeze.md new file mode 100644 index 0000000000..7c0d8147cc --- /dev/null +++ b/.changeset/eleven-pets-sneeze.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': minor +--- + +The app `Router` component now accepts an optional `basename` property. diff --git a/.changeset/fair-tools-melt.md b/.changeset/fair-tools-melt.md deleted file mode 100644 index e63e4d5181..0000000000 --- a/.changeset/fair-tools-melt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Added possibility to configure index plugin of the HTTP router service. diff --git a/.changeset/few-books-remember.md b/.changeset/few-books-remember.md new file mode 100644 index 0000000000..4607202ff6 --- /dev/null +++ b/.changeset/few-books-remember.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-techdocs-node': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. diff --git a/.changeset/fifty-berries-learn.md b/.changeset/fifty-berries-learn.md deleted file mode 100644 index 647246a482..0000000000 --- a/.changeset/fifty-berries-learn.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'@backstage/plugin-catalog': minor ---- - -Added new column `Label` to `CatalogTable.columns`, this new column allows you make use of labels from metadata. -For example: category and visibility are type of labels associated with API entity illustrated below. - -YAML code snippet for API entity - -```yaml -apiVersion: backstage.io/v1alpha1 -kind: API -metadata: - name: sample-api - description: API for sample - links: - - url: http://localhost:8080/swagger-ui.html - title: Swagger UI - tags: - - http - labels: - category: legacy - visibility: protected -``` - -Consumers can customise columns to include label column and show in api-docs list - -```typescript -const columns = [ - CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), - CatalogTable.columns.createLabelColumn('category', { title: 'Category' }), - CatalogTable.columns.createLabelColumn('visibility', { - title: 'Visibility', - defaultValue: 'public', - }), -]; -``` diff --git a/.changeset/five-tables-grow.md b/.changeset/five-tables-grow.md deleted file mode 100644 index 982b17b97b..0000000000 --- a/.changeset/five-tables-grow.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/plugin-catalog-common': patch ---- - -Moved the following types from `@backstage/plugin-catalog-backend` to this package. - -- AnalyzeLocationResponse -- AnalyzeLocationRequest -- AnalyzeLocationExistingEntity -- AnalyzeLocationGenerateEntity -- AnalyzeLocationEntityField diff --git a/.changeset/flat-items-perform.md b/.changeset/flat-items-perform.md new file mode 100644 index 0000000000..4bc846ba7e --- /dev/null +++ b/.changeset/flat-items-perform.md @@ -0,0 +1,38 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels: + +- `scaffolder_task_count`: Tracks successful task runs. + + Labels: + + - `template`: The entity ref of the scaffolded template + - `user`: The entity ref of the user that invoked the template run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + +- `scaffolder_task_duration`: a histogram which tracks the duration of a task run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `result`: A boolean describing whether the task ran successfully + +- `scaffolder_step_count`: a count that tracks each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + +- `scaffolder_step_duration`: a histogram which tracks the duration of each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + +You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/prometheus-metrics.md diff --git a/.changeset/flat-kangaroos-kiss.md b/.changeset/flat-kangaroos-kiss.md new file mode 100644 index 0000000000..a79c8621b1 --- /dev/null +++ b/.changeset/flat-kangaroos-kiss.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +`GitlabDiscoveryEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/gitlab/discovery diff --git a/.changeset/forty-bags-trade.md b/.changeset/forty-bags-trade.md new file mode 100644 index 0000000000..4908134498 --- /dev/null +++ b/.changeset/forty-bags-trade.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Generated development HTTPS backend certificate is now checked for expiration date instead of file age. diff --git a/.changeset/forty-jokes-lie.md b/.changeset/forty-jokes-lie.md new file mode 100644 index 0000000000..bd44898004 --- /dev/null +++ b/.changeset/forty-jokes-lie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-openapi': patch +--- + +Added support to use the `UrlReaders` when `$ref` pointing to a URL. diff --git a/.changeset/fresh-cooks-sing.md b/.changeset/fresh-cooks-sing.md new file mode 100644 index 0000000000..31f5ff0f22 --- /dev/null +++ b/.changeset/fresh-cooks-sing.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': minor +--- + +This patch brings Gitea as a valid integration: target, via the ScmIntegration interface. It adds gitea to the relevant static properties (get integration by name, get integration by type) for plugins to be able to reference the same Gitea server. diff --git a/.changeset/fresh-weeks-share.md b/.changeset/fresh-weeks-share.md new file mode 100644 index 0000000000..aced6e910c --- /dev/null +++ b/.changeset/fresh-weeks-share.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +This patch adds GiteaURLReader to the available classes. It currently only reads single files via gitea's public repos api diff --git a/.changeset/fuzzy-dolls-give.md b/.changeset/fuzzy-dolls-give.md deleted file mode 100644 index 4de4d2e128..0000000000 --- a/.changeset/fuzzy-dolls-give.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-tasks': patch ---- - -Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. diff --git a/.changeset/fuzzy-dolls-shake.md b/.changeset/fuzzy-dolls-shake.md deleted file mode 100644 index 35ebcf6103..0000000000 --- a/.changeset/fuzzy-dolls-shake.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -'@backstage/plugin-catalog-import': minor ---- - -**Breaking** -Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials. - -Add the following to your `CatalogBuilder` to have the repo URL ingestion working again. - -```ts -// catalog.ts -import { GitHubLocationAnalyzer } from '@backstage/plugin-catalog-backend-module-github'; -... - builder.addLocationAnalyzers( - new GitHubLocationAnalyzer({ - discovery: env.discovery, - config: env.config, - }), - ); -... -``` diff --git a/.changeset/giant-pianos-repeat.md b/.changeset/giant-pianos-repeat.md deleted file mode 100644 index d83ce285e3..0000000000 --- a/.changeset/giant-pianos-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-bitbucket-cloud': patch ---- - -Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/gorgeous-balloons-sit.md b/.changeset/gorgeous-balloons-sit.md new file mode 100644 index 0000000000..a83b12131b --- /dev/null +++ b/.changeset/gorgeous-balloons-sit.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-cost-insights': patch +'@backstage/plugin-dynatrace': patch +'@backstage/plugin-stack-overflow-backend': patch +'@backstage/plugin-techdocs': patch +--- + +Tweak README diff --git a/.changeset/gorgeous-onions-thank.md b/.changeset/gorgeous-onions-thank.md new file mode 100644 index 0000000000..8b41bfa5a0 --- /dev/null +++ b/.changeset/gorgeous-onions-thank.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-aws': patch +--- + +`AwsS3EntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/aws-s3/discovery diff --git a/.changeset/gorgeous-queens-pull.md b/.changeset/gorgeous-queens-pull.md new file mode 100644 index 0000000000..bb257e85f0 --- /dev/null +++ b/.changeset/gorgeous-queens-pull.md @@ -0,0 +1,14 @@ +--- +'@backstage/integration': minor +--- + +Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. + +Deprecates: + +- `getGitHubFileFetchUrl` replaced by `getGithubFileFetchUrl` +- `GitHubIntegrationConfig` replaced by `GithubIntegrationConfig` +- `GitHubIntegration` replaced by `GithubIntegration` +- `readGitHubIntegrationConfig` replaced by `readGithubIntegrationConfig` +- `readGitHubIntegrationConfigs` replaced by `readGithubIntegrationConfigs` +- `replaceGitHubUrlType` replaced by `replaceGithubUrlType` diff --git a/.changeset/great-colts-invite.md b/.changeset/great-colts-invite.md new file mode 100644 index 0000000000..1ca71580dc --- /dev/null +++ b/.changeset/great-colts-invite.md @@ -0,0 +1,16 @@ +--- +'@backstage/cli': minor +--- + +Added a new ESLint rule that restricts imports of Link from @material-ui + +The rule can be can be overridden in the following way: + +```diff +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { ++ restrictedImports: [ ++ { name: '@material-ui/core', importNames: [] }, ++ { name: '@material-ui/core/Link', importNames: [] }, ++ ], +}); +``` diff --git a/.changeset/great-numbers-reply.md b/.changeset/great-numbers-reply.md deleted file mode 100644 index b72b05debf..0000000000 --- a/.changeset/great-numbers-reply.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Fix BitBucket server integration diff --git a/.changeset/grumpy-pans-knock.md b/.changeset/grumpy-pans-knock.md deleted file mode 100644 index 74e81e7bec..0000000000 --- a/.changeset/grumpy-pans-knock.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@techdocs/cli': patch -'@backstage/plugin-techdocs-node': patch ---- - -fixing techdocs-cli Docker client creation - -Docker client does not need to be created when --no-docker -option is provided. - -If you had DOCKER_CERT_PATH environment variable defined -the Docker client was looking for certificates -and breaking techdocs-cli generate command even with --no-docker -option. diff --git a/.changeset/grumpy-pigs-reflect.md b/.changeset/grumpy-pigs-reflect.md new file mode 100644 index 0000000000..797aefcba0 --- /dev/null +++ b/.changeset/grumpy-pigs-reflect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Get rid of `this-is-undefined-in-esm` warning diff --git a/.changeset/happy-avocados-tan.md b/.changeset/happy-avocados-tan.md new file mode 100644 index 0000000000..9f087930d5 --- /dev/null +++ b/.changeset/happy-avocados-tan.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Removed forced capitalization for Entity types in the catalog sidebar. diff --git a/.changeset/heavy-elephants-nail.md b/.changeset/heavy-elephants-nail.md new file mode 100644 index 0000000000..473e3eda9e --- /dev/null +++ b/.changeset/heavy-elephants-nail.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': minor +--- + +Deprecated the `homepage` config as the component that used it - `HomepageTimer` - has been removed and replaced by the `HeaderWorldClock` in the home plugin diff --git a/.changeset/hot-geese-vanish.md b/.changeset/hot-geese-vanish.md deleted file mode 100644 index b392c875c7..0000000000 --- a/.changeset/hot-geese-vanish.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': minor ---- - -Google OAuth refresh tokens will now be revoked on logout by calling Google's API diff --git a/.changeset/itchy-paws-protect.md b/.changeset/itchy-paws-protect.md new file mode 100644 index 0000000000..a0c272e5e4 --- /dev/null +++ b/.changeset/itchy-paws-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-azure': patch +--- + +Add `azureDevOpsEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/itchy-schools-run.md b/.changeset/itchy-schools-run.md deleted file mode 100644 index 6e46cabb15..0000000000 --- a/.changeset/itchy-schools-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -Allow interfaces to be used for inferred option types. diff --git a/.changeset/kind-emus-juggle.md b/.changeset/kind-emus-juggle.md new file mode 100644 index 0000000000..2a99deef81 --- /dev/null +++ b/.changeset/kind-emus-juggle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-aws': patch +--- + +Add `awsS3EntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/kind-penguins-report.md b/.changeset/kind-penguins-report.md deleted file mode 100644 index 4023ea5976..0000000000 --- a/.changeset/kind-penguins-report.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-auth-backend': minor ---- - -CookieConfigurer can optionally return the `SameSite` cookie attribute. -CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute. -defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests. - -OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`. -OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set. diff --git a/.changeset/large-dingos-juggle.md b/.changeset/large-dingos-juggle.md deleted file mode 100644 index 8566a0f305..0000000000 --- a/.changeset/large-dingos-juggle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-defaults': patch ---- - -Added root logger service to the set of default services. diff --git a/.changeset/large-jars-fry.md b/.changeset/large-jars-fry.md deleted file mode 100644 index 0da3f4302b..0000000000 --- a/.changeset/large-jars-fry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-bazaar': patch ---- - -Link to the user catalog entity of a member diff --git a/.changeset/lazy-beds-pull.md b/.changeset/lazy-beds-pull.md deleted file mode 100644 index 0d8be46576..0000000000 --- a/.changeset/lazy-beds-pull.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated fallback versions of dependencies in all templates. diff --git a/.changeset/lazy-eyes-switch.md b/.changeset/lazy-eyes-switch.md deleted file mode 100644 index f712f852ae..0000000000 --- a/.changeset/lazy-eyes-switch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings-backend': patch ---- - -Use `Response.status` instead of `.send(number)` diff --git a/.changeset/lazy-fireants-check.md b/.changeset/lazy-fireants-check.md deleted file mode 100644 index 234501b07a..0000000000 --- a/.changeset/lazy-fireants-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes': patch ---- - -Add useCustomResources react hook for fetching Kubernetes Custom Resources diff --git a/.changeset/lazy-planes-repair.md b/.changeset/lazy-planes-repair.md new file mode 100644 index 0000000000..00ecfb095e --- /dev/null +++ b/.changeset/lazy-planes-repair.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Deprecated the `HumanDuration` type, which should now instead be imported from `@backstage/types`. diff --git a/.changeset/little-bikes-eat.md b/.changeset/little-bikes-eat.md new file mode 100644 index 0000000000..0992198146 --- /dev/null +++ b/.changeset/little-bikes-eat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': patch +--- + +Fixed bug in Tech Radar where, on hover, the tech list quadrant would rerender and scroll top diff --git a/.changeset/little-hats-yell.md b/.changeset/little-hats-yell.md deleted file mode 100644 index 6063459d8d..0000000000 --- a/.changeset/little-hats-yell.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Update the `github:publish` action to allow passing whether pull -requests must be up to date with the default branch before merging. diff --git a/.changeset/little-roses-rule.md b/.changeset/little-roses-rule.md deleted file mode 100644 index 4acec131e6..0000000000 --- a/.changeset/little-roses-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Added a new `repo test` command. diff --git a/.changeset/loud-dots-sit.md b/.changeset/loud-dots-sit.md deleted file mode 100644 index fa6175358e..0000000000 --- a/.changeset/loud-dots-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings': patch ---- - -Update installation instructions diff --git a/.changeset/lovely-peaches-fold.md b/.changeset/lovely-peaches-fold.md deleted file mode 100644 index dcff35f4d7..0000000000 --- a/.changeset/lovely-peaches-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-tech-insights': patch ---- - -making available the search for the last FACTS executed diff --git a/.changeset/lucky-cats-peel.md b/.changeset/lucky-cats-peel.md new file mode 100644 index 0000000000..dfe1047b7c --- /dev/null +++ b/.changeset/lucky-cats-peel.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Small update to fix compatibility with newer versions of the `keyv` library diff --git a/.changeset/lucky-cows-boil.md b/.changeset/lucky-cows-boil.md deleted file mode 100644 index a598b3da95..0000000000 --- a/.changeset/lucky-cows-boil.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/plugin-bitbucket-cloud-common': minor ---- - -Update Bitbucket Cloud models to latest OAS version. - -The latest specification contained some BREAKING CHANGES -due to removed fields. - -All of these fields are not used at other plugins, though. -Therefore, this change has no impact on other modules here. diff --git a/.changeset/lucky-spoons-hide.md b/.changeset/lucky-spoons-hide.md new file mode 100644 index 0000000000..c481941f6a --- /dev/null +++ b/.changeset/lucky-spoons-hide.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated `@swc/core` to version 1.3.9 which fixes a `.tsx` parser bug diff --git a/.changeset/mean-files-fly.md b/.changeset/mean-files-fly.md new file mode 100644 index 0000000000..a7a79bcf74 --- /dev/null +++ b/.changeset/mean-files-fly.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-msgraph': patch +--- + +Add `microsoftGraphOrgEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/mean-spiders-design.md b/.changeset/mean-spiders-design.md deleted file mode 100644 index 3704c9fca9..0000000000 --- a/.changeset/mean-spiders-design.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend': minor ---- - -Be less restrictive with unknown keys on query endpoint diff --git a/.changeset/metal-dogs-swim.md b/.changeset/metal-dogs-swim.md new file mode 100644 index 0000000000..d97c7077f2 --- /dev/null +++ b/.changeset/metal-dogs-swim.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gerrit': patch +--- + +Add `gerritEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/mighty-lions-march.md b/.changeset/mighty-lions-march.md deleted file mode 100644 index 2f0b63d238..0000000000 --- a/.changeset/mighty-lions-march.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-vault-backend': patch ---- - -VaultBuilder.tsx renamed to VaultBuilder in order for module to be correctly loaded. diff --git a/.changeset/moody-carrots-shout.md b/.changeset/moody-carrots-shout.md deleted file mode 100644 index 486590e0ba..0000000000 --- a/.changeset/moody-carrots-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': patch ---- - -Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. diff --git a/.changeset/nasty-crabs-share.md b/.changeset/nasty-crabs-share.md new file mode 100644 index 0000000000..d09eb09044 --- /dev/null +++ b/.changeset/nasty-crabs-share.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-github-pull-requests-board': patch +--- + +Add a new "Team" Filter Options to the Github Pull Requests Dashboard. + +When toggling this option on, the dashboard will displays all of the PRs opened +by the members of that team on any repositories of the organization. diff --git a/.changeset/odd-pandas-suffer.md b/.changeset/odd-pandas-suffer.md deleted file mode 100644 index ce562a9a89..0000000000 --- a/.changeset/odd-pandas-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-github-issues': patch ---- - -Updated the `luxon` dependency to 3.x diff --git a/.changeset/odd-singers-taste.md b/.changeset/odd-singers-taste.md deleted file mode 100644 index 524de42255..0000000000 --- a/.changeset/odd-singers-taste.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -Add support for link to TechDocs and other links defined in template entity specification metadata on TemplateCard diff --git a/.changeset/old-cobras-suffer.md b/.changeset/old-cobras-suffer.md deleted file mode 100644 index 2891c29133..0000000000 --- a/.changeset/old-cobras-suffer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/test-utils': patch -'@backstage/plugin-cicd-statistics': patch ---- - -Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo diff --git a/.changeset/old-melons-bathe.md b/.changeset/old-melons-bathe.md deleted file mode 100644 index ac21377171..0000000000 --- a/.changeset/old-melons-bathe.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. diff --git a/.changeset/old-needles-brake.md b/.changeset/old-needles-brake.md deleted file mode 100644 index eea2bf6539..0000000000 --- a/.changeset/old-needles-brake.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-github-issues': patch -'@backstage/plugin-github-pull-requests-board': patch ---- - -Properly filter on relations instead of the spec, when finding by owner diff --git a/.changeset/orange-trees-peel.md b/.changeset/orange-trees-peel.md new file mode 100644 index 0000000000..344dd88cb9 --- /dev/null +++ b/.changeset/orange-trees-peel.md @@ -0,0 +1,14 @@ +--- +'@backstage/catalog-model': patch +'@backstage/core-components': patch +'@backstage/integration': patch +'@backstage/plugin-auth-node': patch +'@backstage/plugin-gcalendar': patch +'@backstage/plugin-periskop': patch +'@backstage/plugin-permission-common': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-xcmetrics': patch +--- + +Internal refactor of imports to avoid circular dependencies diff --git a/.changeset/perfect-moose-drum.md b/.changeset/perfect-moose-drum.md deleted file mode 100644 index 833e5eeb35..0000000000 --- a/.changeset/perfect-moose-drum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated lockfile parsing to have better support for Yarn 3. diff --git a/.changeset/plenty-kids-fetch.md b/.changeset/plenty-kids-fetch.md deleted file mode 100644 index 6a31b4db20..0000000000 --- a/.changeset/plenty-kids-fetch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-client': patch ---- - -Renamed argument in `validateEntity` from `location` to `locationRef` diff --git a/.changeset/plenty-laws-end.md b/.changeset/plenty-laws-end.md deleted file mode 100644 index e026ca3fca..0000000000 --- a/.changeset/plenty-laws-end.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Fixed an issue where `getClient()` for a `pluginId` would return different clients and not share them diff --git a/.changeset/poor-clouds-ring.md b/.changeset/poor-clouds-ring.md deleted file mode 100644 index 62ba3928a7..0000000000 --- a/.changeset/poor-clouds-ring.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Fixed a bug where entities provided without a location key would always replace existing entities, rather than updating them. diff --git a/.changeset/popular-bulldogs-lie.md b/.changeset/popular-bulldogs-lie.md new file mode 100644 index 0000000000..3bc83338c1 --- /dev/null +++ b/.changeset/popular-bulldogs-lie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Properly derive Github credentials when making requests in `GithubLocationAnalyzer` to support Github App authentication diff --git a/.changeset/popular-mails-wave.md b/.changeset/popular-mails-wave.md new file mode 100644 index 0000000000..23517f0608 --- /dev/null +++ b/.changeset/popular-mails-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-ilert': minor +--- + +Added support for multiple responders in alert list, added new tab with list to support iLert resource 'service', added new tab with list to support iLert resource 'status page' diff --git a/.changeset/pre.json b/.changeset/pre.json index c5f8b30596..a2fd9eb458 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -2,257 +2,246 @@ "mode": "pre", "tag": "next", "initialVersions": { - "example-app": "0.2.75", - "@backstage/app-defaults": "1.0.6", - "example-backend": "0.2.75", - "@backstage/backend-app-api": "0.2.1", - "@backstage/backend-common": "0.15.1", - "@backstage/backend-defaults": "0.1.1", - "example-backend-next": "0.0.3", - "@backstage/backend-plugin-api": "0.1.2", - "@backstage/backend-tasks": "0.3.5", - "@backstage/backend-test-utils": "0.1.28", - "@backstage/catalog-client": "1.1.0", - "@backstage/catalog-model": "1.1.1", - "@backstage/cli": "0.19.0", + "example-app": "0.2.76", + "@backstage/app-defaults": "1.0.7", + "example-backend": "0.2.76", + "@backstage/backend-app-api": "0.2.2", + "@backstage/backend-common": "0.15.2", + "@backstage/backend-defaults": "0.1.2", + "example-backend-next": "0.0.4", + "@backstage/backend-plugin-api": "0.1.3", + "@backstage/backend-tasks": "0.3.6", + "@backstage/backend-test-utils": "0.1.29", + "@backstage/catalog-client": "1.1.1", + "@backstage/catalog-model": "1.1.2", + "@backstage/cli": "0.20.0", "@backstage/cli-common": "0.1.10", - "@backstage/codemods": "0.1.39", - "@backstage/config": "1.0.2", - "@backstage/config-loader": "1.1.4", - "@backstage/core-app-api": "1.1.0", - "@backstage/core-components": "0.11.1", - "@backstage/core-plugin-api": "1.0.6", - "@backstage/create-app": "0.4.31", - "@backstage/dev-utils": "1.0.6", + "@backstage/codemods": "0.1.40", + "@backstage/config": "1.0.3", + "@backstage/config-loader": "1.1.5", + "@backstage/core-app-api": "1.1.1", + "@backstage/core-components": "0.11.2", + "@backstage/core-plugin-api": "1.0.7", + "@backstage/create-app": "0.4.32", + "@backstage/dev-utils": "1.0.7", "e2e-test": "0.2.0", - "@backstage/errors": "1.1.1", - "@backstage/integration": "1.3.1", - "@backstage/integration-react": "1.1.4", + "@backstage/errors": "1.1.2", + "@backstage/integration": "1.3.2", + "@backstage/integration-react": "1.1.5", "@backstage/release-manifests": "0.0.6", - "@techdocs/cli": "1.2.1", - "techdocs-cli-embedded-app": "0.2.74", - "@backstage/test-utils": "1.2.0", + "@techdocs/cli": "1.2.2", + "techdocs-cli-embedded-app": "0.2.75", + "@backstage/test-utils": "1.2.1", "@backstage/theme": "0.2.16", "@backstage/types": "1.0.0", "@backstage/version-bridge": "1.0.1", - "@backstage/plugin-adr": "0.2.1", - "@backstage/plugin-adr-backend": "0.2.1", - "@backstage/plugin-adr-common": "0.2.1", - "@backstage/plugin-airbrake": "0.3.9", - "@backstage/plugin-airbrake-backend": "0.2.9", - "@backstage/plugin-allure": "0.1.25", - "@backstage/plugin-analytics-module-ga": "0.1.20", - "@backstage/plugin-apache-airflow": "0.2.2", - "@backstage/plugin-api-docs": "0.8.9", + "@backstage/plugin-adr": "0.2.2", + "@backstage/plugin-adr-backend": "0.2.2", + "@backstage/plugin-adr-common": "0.2.2", + "@backstage/plugin-airbrake": "0.3.10", + "@backstage/plugin-airbrake-backend": "0.2.10", + "@backstage/plugin-allure": "0.1.26", + "@backstage/plugin-analytics-module-ga": "0.1.21", + "@backstage/plugin-apache-airflow": "0.2.3", + "@backstage/plugin-api-docs": "0.8.10", "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.0", - "@backstage/plugin-apollo-explorer": "0.1.2", - "@backstage/plugin-app-backend": "0.3.36", - "@backstage/plugin-auth-backend": "0.16.0", - "@backstage/plugin-auth-node": "0.2.5", - "@backstage/plugin-azure-devops": "0.2.0", - "@backstage/plugin-azure-devops-backend": "0.3.15", + "@backstage/plugin-apollo-explorer": "0.1.3", + "@backstage/plugin-app-backend": "0.3.37", + "@backstage/plugin-auth-backend": "0.17.0", + "@backstage/plugin-auth-node": "0.2.6", + "@backstage/plugin-azure-devops": "0.2.1", + "@backstage/plugin-azure-devops-backend": "0.3.16", "@backstage/plugin-azure-devops-common": "0.3.0", - "@backstage/plugin-badges": "0.2.33", - "@backstage/plugin-badges-backend": "0.1.30", - "@backstage/plugin-bazaar": "0.1.24", - "@backstage/plugin-bazaar-backend": "0.1.20", - "@backstage/plugin-bitbucket-cloud-common": "0.1.3", - "@backstage/plugin-bitrise": "0.1.36", - "@backstage/plugin-catalog": "1.5.1", - "@backstage/plugin-catalog-backend": "1.4.0", - "@backstage/plugin-catalog-backend-module-aws": "0.1.9", - "@backstage/plugin-catalog-backend-module-azure": "0.1.7", - "@backstage/plugin-catalog-backend-module-bitbucket": "0.2.3", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.1.3", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.1", - "@backstage/plugin-catalog-backend-module-gerrit": "0.1.4", - "@backstage/plugin-catalog-backend-module-github": "0.1.7", - "@backstage/plugin-catalog-backend-module-gitlab": "0.1.7", - "@backstage/plugin-catalog-backend-module-ldap": "0.5.3", - "@backstage/plugin-catalog-backend-module-msgraph": "0.4.2", - "@backstage/plugin-catalog-backend-module-openapi": "0.1.2", - "@backstage/plugin-catalog-common": "1.0.6", - "@internal/plugin-catalog-customized": "0.0.2", - "@backstage/plugin-catalog-graph": "0.2.21", - "@backstage/plugin-catalog-graphql": "0.3.13", - "@backstage/plugin-catalog-import": "0.8.12", - "@backstage/plugin-catalog-node": "1.1.0", - "@backstage/plugin-catalog-react": "1.1.4", - "@backstage/plugin-cicd-statistics": "0.1.11", - "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.5", - "@backstage/plugin-circleci": "0.3.9", - "@backstage/plugin-cloudbuild": "0.3.9", - "@backstage/plugin-code-climate": "0.1.9", - "@backstage/plugin-code-coverage": "0.2.2", - "@backstage/plugin-code-coverage-backend": "0.2.2", - "@backstage/plugin-codescene": "0.1.4", - "@backstage/plugin-config-schema": "0.1.32", - "@backstage/plugin-cost-insights": "0.11.31", + "@backstage/plugin-badges": "0.2.34", + "@backstage/plugin-badges-backend": "0.1.31", + "@backstage/plugin-bazaar": "0.1.25", + "@backstage/plugin-bazaar-backend": "0.2.0", + "@backstage/plugin-bitbucket-cloud-common": "0.2.0", + "@backstage/plugin-bitrise": "0.1.37", + "@backstage/plugin-catalog": "1.6.0", + "@backstage/plugin-catalog-backend": "1.5.0", + "@backstage/plugin-catalog-backend-module-aws": "0.1.10", + "@backstage/plugin-catalog-backend-module-azure": "0.1.8", + "@backstage/plugin-catalog-backend-module-bitbucket": "0.2.4", + "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.1.4", + "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.2", + "@backstage/plugin-catalog-backend-module-gerrit": "0.1.5", + "@backstage/plugin-catalog-backend-module-github": "0.1.8", + "@backstage/plugin-catalog-backend-module-gitlab": "0.1.8", + "@backstage/plugin-catalog-backend-module-ldap": "0.5.4", + "@backstage/plugin-catalog-backend-module-msgraph": "0.4.3", + "@backstage/plugin-catalog-backend-module-openapi": "0.1.3", + "@backstage/plugin-catalog-common": "1.0.7", + "@internal/plugin-catalog-customized": "0.0.3", + "@backstage/plugin-catalog-graph": "0.2.22", + "@backstage/plugin-catalog-graphql": "0.3.14", + "@backstage/plugin-catalog-import": "0.9.0", + "@backstage/plugin-catalog-node": "1.2.0", + "@backstage/plugin-catalog-react": "1.2.0", + "@backstage/plugin-cicd-statistics": "0.1.12", + "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.6", + "@backstage/plugin-circleci": "0.3.10", + "@backstage/plugin-cloudbuild": "0.3.10", + "@backstage/plugin-code-climate": "0.1.10", + "@backstage/plugin-code-coverage": "0.2.3", + "@backstage/plugin-code-coverage-backend": "0.2.3", + "@backstage/plugin-codescene": "0.1.5", + "@backstage/plugin-config-schema": "0.1.33", + "@backstage/plugin-cost-insights": "0.11.32", "@backstage/plugin-cost-insights-common": "0.1.1", - "@backstage/plugin-dynatrace": "0.2.0", - "@internal/plugin-todo-list": "1.0.5", - "@internal/plugin-todo-list-backend": "1.0.5", - "@internal/plugin-todo-list-common": "1.0.4", - "@backstage/plugin-explore": "0.3.40", - "@backstage/plugin-explore-react": "0.0.21", - "@backstage/plugin-firehydrant": "0.1.26", - "@backstage/plugin-fossa": "0.2.41", - "@backstage/plugin-gcalendar": "0.3.5", - "@backstage/plugin-gcp-projects": "0.3.28", - "@backstage/plugin-git-release-manager": "0.3.22", - "@backstage/plugin-github-actions": "0.5.9", - "@backstage/plugin-github-deployments": "0.1.40", - "@backstage/plugin-github-issues": "0.1.1", - "@backstage/plugin-github-pull-requests-board": "0.1.3", - "@backstage/plugin-gitops-profiles": "0.3.27", - "@backstage/plugin-gocd": "0.1.15", - "@backstage/plugin-graphiql": "0.2.41", - "@backstage/plugin-graphql-backend": "0.1.26", - "@backstage/plugin-home": "0.4.25", - "@backstage/plugin-ilert": "0.1.35", - "@backstage/plugin-jenkins": "0.7.8", - "@backstage/plugin-jenkins-backend": "0.1.26", - "@backstage/plugin-jenkins-common": "0.1.8", - "@backstage/plugin-kafka": "0.3.9", - "@backstage/plugin-kafka-backend": "0.2.29", - "@backstage/plugin-kubernetes": "0.7.2", - "@backstage/plugin-kubernetes-backend": "0.7.2", - "@backstage/plugin-kubernetes-common": "0.4.2", - "@backstage/plugin-lighthouse": "0.3.9", - "@backstage/plugin-newrelic": "0.3.27", - "@backstage/plugin-newrelic-dashboard": "0.2.2", - "@backstage/plugin-org": "0.5.9", - "@backstage/plugin-pagerduty": "0.5.2", - "@backstage/plugin-periskop": "0.1.7", - "@backstage/plugin-periskop-backend": "0.1.7", - "@backstage/plugin-permission-backend": "0.5.11", - "@backstage/plugin-permission-common": "0.6.4", - "@backstage/plugin-permission-node": "0.6.5", - "@backstage/plugin-permission-react": "0.4.5", - "@backstage/plugin-playlist": "0.1.0", - "@backstage/plugin-playlist-backend": "0.1.0", - "@backstage/plugin-playlist-common": "0.1.0", - "@backstage/plugin-proxy-backend": "0.2.30", - "@backstage/plugin-rollbar": "0.4.9", - "@backstage/plugin-rollbar-backend": "0.1.33", - "@backstage/plugin-scaffolder": "1.6.0", - "@backstage/plugin-scaffolder-backend": "1.6.0", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.11", - "@backstage/plugin-scaffolder-backend-module-rails": "0.4.4", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.9", - "@backstage/plugin-scaffolder-common": "1.2.0", - "@backstage/plugin-search": "1.0.2", - "@backstage/plugin-search-backend": "1.0.2", - "@backstage/plugin-search-backend-module-elasticsearch": "1.0.2", - "@backstage/plugin-search-backend-module-pg": "0.4.0", - "@backstage/plugin-search-backend-node": "1.0.2", - "@backstage/plugin-search-common": "1.0.1", - "@backstage/plugin-search-react": "1.1.0", - "@backstage/plugin-sentry": "0.4.2", - "@backstage/plugin-shortcuts": "0.3.1", - "@backstage/plugin-sonarqube": "0.4.1", - "@backstage/plugin-sonarqube-backend": "0.1.1", - "@backstage/plugin-splunk-on-call": "0.3.33", - "@backstage/plugin-stack-overflow": "0.1.5", - "@backstage/plugin-stack-overflow-backend": "0.1.5", - "@backstage/plugin-tech-insights": "0.3.0", - "@backstage/plugin-tech-insights-backend": "0.5.2", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.20", - "@backstage/plugin-tech-insights-common": "0.2.6", - "@backstage/plugin-tech-insights-node": "0.3.4", - "@backstage/plugin-tech-radar": "0.5.16", - "@backstage/plugin-techdocs": "1.3.2", - "@backstage/plugin-techdocs-addons-test-utils": "1.0.4", - "@backstage/plugin-techdocs-backend": "1.3.0", - "@backstage/plugin-techdocs-module-addons-contrib": "1.0.4", - "@backstage/plugin-techdocs-node": "1.4.0", - "@backstage/plugin-techdocs-react": "1.0.4", - "@backstage/plugin-todo": "0.2.11", - "@backstage/plugin-todo-backend": "0.1.33", - "@backstage/plugin-user-settings": "0.4.8", - "@backstage/plugin-user-settings-backend": "0.1.0", - "@backstage/plugin-vault": "0.1.3", - "@backstage/plugin-vault-backend": "0.2.2", - "@backstage/plugin-xcmetrics": "0.2.29" + "@backstage/plugin-dynatrace": "1.0.0", + "@internal/plugin-todo-list": "1.0.6", + "@internal/plugin-todo-list-backend": "1.0.6", + "@internal/plugin-todo-list-common": "1.0.5", + "@backstage/plugin-explore": "0.3.41", + "@backstage/plugin-explore-react": "0.0.22", + "@backstage/plugin-firehydrant": "0.1.27", + "@backstage/plugin-fossa": "0.2.42", + "@backstage/plugin-gcalendar": "0.3.6", + "@backstage/plugin-gcp-projects": "0.3.29", + "@backstage/plugin-git-release-manager": "0.3.23", + "@backstage/plugin-github-actions": "0.5.10", + "@backstage/plugin-github-deployments": "0.1.41", + "@backstage/plugin-github-issues": "0.1.2", + "@backstage/plugin-github-pull-requests-board": "0.1.4", + "@backstage/plugin-gitops-profiles": "0.3.28", + "@backstage/plugin-gocd": "0.1.16", + "@backstage/plugin-graphiql": "0.2.42", + "@backstage/plugin-graphql-backend": "0.1.27", + "@backstage/plugin-home": "0.4.26", + "@backstage/plugin-ilert": "0.1.36", + "@backstage/plugin-jenkins": "0.7.9", + "@backstage/plugin-jenkins-backend": "0.1.27", + "@backstage/plugin-jenkins-common": "0.1.9", + "@backstage/plugin-kafka": "0.3.10", + "@backstage/plugin-kafka-backend": "0.2.30", + "@backstage/plugin-kubernetes": "0.7.3", + "@backstage/plugin-kubernetes-backend": "0.7.3", + "@backstage/plugin-kubernetes-common": "0.4.3", + "@backstage/plugin-lighthouse": "0.3.10", + "@backstage/plugin-newrelic": "0.3.28", + "@backstage/plugin-newrelic-dashboard": "0.2.3", + "@backstage/plugin-org": "0.5.10", + "@backstage/plugin-pagerduty": "0.5.3", + "@backstage/plugin-periskop": "0.1.8", + "@backstage/plugin-periskop-backend": "0.1.8", + "@backstage/plugin-permission-backend": "0.5.12", + "@backstage/plugin-permission-common": "0.7.0", + "@backstage/plugin-permission-node": "0.7.0", + "@backstage/plugin-permission-react": "0.4.6", + "@backstage/plugin-playlist": "0.1.1", + "@backstage/plugin-playlist-backend": "0.2.0", + "@backstage/plugin-playlist-common": "0.1.1", + "@backstage/plugin-proxy-backend": "0.2.31", + "@backstage/plugin-rollbar": "0.4.10", + "@backstage/plugin-rollbar-backend": "0.1.34", + "@backstage/plugin-scaffolder": "1.7.0", + "@backstage/plugin-scaffolder-backend": "1.7.0", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.12", + "@backstage/plugin-scaffolder-backend-module-rails": "0.4.5", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.10", + "@backstage/plugin-scaffolder-common": "1.2.1", + "@backstage/plugin-search": "1.0.3", + "@backstage/plugin-search-backend": "1.1.0", + "@backstage/plugin-search-backend-module-elasticsearch": "1.0.3", + "@backstage/plugin-search-backend-module-pg": "0.4.1", + "@backstage/plugin-search-backend-node": "1.0.3", + "@backstage/plugin-search-common": "1.1.0", + "@backstage/plugin-search-react": "1.2.0", + "@backstage/plugin-sentry": "0.4.3", + "@backstage/plugin-shortcuts": "0.3.2", + "@backstage/plugin-sonarqube": "0.4.2", + "@backstage/plugin-sonarqube-backend": "0.1.2", + "@backstage/plugin-splunk-on-call": "0.3.34", + "@backstage/plugin-stack-overflow": "0.1.6", + "@backstage/plugin-stack-overflow-backend": "0.1.6", + "@backstage/plugin-tech-insights": "0.3.1", + "@backstage/plugin-tech-insights-backend": "0.5.3", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.21", + "@backstage/plugin-tech-insights-common": "0.2.7", + "@backstage/plugin-tech-insights-node": "0.3.5", + "@backstage/plugin-tech-radar": "0.5.17", + "@backstage/plugin-techdocs": "1.3.3", + "@backstage/plugin-techdocs-addons-test-utils": "1.0.5", + "@backstage/plugin-techdocs-backend": "1.4.0", + "@backstage/plugin-techdocs-module-addons-contrib": "1.0.5", + "@backstage/plugin-techdocs-node": "1.4.1", + "@backstage/plugin-techdocs-react": "1.0.5", + "@backstage/plugin-todo": "0.2.12", + "@backstage/plugin-todo-backend": "0.1.34", + "@backstage/plugin-user-settings": "0.5.0", + "@backstage/plugin-user-settings-backend": "0.1.1", + "@backstage/plugin-vault": "0.1.4", + "@backstage/plugin-vault-backend": "0.2.3", + "@backstage/plugin-xcmetrics": "0.2.30", + "@backstage/plugin-azure-sites": "0.0.0", + "@backstage/plugin-azure-sites-backend": "0.0.0", + "@backstage/plugin-azure-sites-common": "0.0.0" }, "changesets": [ - "blue-lions-greet", - "brave-peaches-brush", - "breezy-pots-worry", - "bright-rules-shout", - "calm-moose-fetch", - "calm-pianos-burn", - "clean-camels-sneeze", - "clever-ties-burn", - "cuddly-bikes-tease", - "curly-rats-itch", - "curvy-kiwis-fold", - "curvy-lemons-change", - "dry-shirts-attack", - "dull-rocks-warn", - "eleven-apples-accept", - "fair-tools-melt", - "fifty-berries-learn", - "grumpy-pans-knock", - "hot-geese-vanish", - "itchy-schools-run", - "kind-penguins-report", - "large-dingos-juggle", - "lazy-beds-pull", - "lazy-fireants-check", - "little-roses-rule", - "loud-dots-sit", - "lovely-peaches-fold", - "lucky-cows-boil", - "mean-spiders-design", - "mighty-lions-march", - "odd-pandas-suffer", - "odd-singers-taste", - "old-cobras-suffer", - "old-needles-brake", - "perfect-moose-drum", - "plenty-kids-fetch", - "plenty-laws-end", - "poor-clouds-ring", - "pretty-buttons-develop", - "quiet-dancers-jog", - "quiet-hats-kick", - "quiet-ligers-draw", - "rare-hotels-cough", - "red-dots-sleep", - "red-pants-rush", - "renovate-5da63a4", - "renovate-a02d90b", - "rich-carrots-reflect", - "rude-bulldogs-sleep", - "search-bobcats-love", - "search-cars-hide", - "search-clouds-begin", - "search-cycles-sniff", - "search-dull-planes-prove", - "search-rats-grin", - "selfish-turkeys-exist", - "shaggy-books-smell", - "silent-bees-repeat", - "silly-rules-join", - "sixty-items-nail", - "slow-mirrors-eat", - "smooth-tables-pull", - "soft-falcons-love", - "stupid-dragons-complain", - "sweet-insects-camp", - "swift-phones-cheat", - "tall-baboons-deliver", - "tender-drinks-drive", - "tender-parrots-peel", - "thick-kings-destroy", - "tiny-mails-bathe", - "tough-hairs-sparkle", - "twelve-melons-notice", - "warm-days-watch", - "wild-weeks-live", - "wise-ligers-scream", - "yellow-lemons-march", - "young-bugs-remember" + "analyze-software-creation", + "analyze-software-exploration", + "big-islands-add", + "brave-eels-allow", + "brown-days-pretend", + "calm-bottles-happen", + "chatty-planets-flash", + "clean-feet-remain", + "clean-planets-rhyme", + "dirty-birds-burn", + "dull-oranges-tap", + "eight-pears-attack", + "eleven-pets-sneeze", + "few-books-remember", + "flat-items-perform", + "flat-kangaroos-kiss", + "forty-bags-trade", + "forty-jokes-lie", + "fresh-cooks-sing", + "fresh-weeks-share", + "gorgeous-balloons-sit", + "gorgeous-onions-thank", + "gorgeous-queens-pull", + "great-colts-invite", + "grumpy-pigs-reflect", + "happy-avocados-tan", + "heavy-elephants-nail", + "itchy-paws-protect", + "kind-emus-juggle", + "lazy-planes-repair", + "little-bikes-eat", + "lucky-cats-peel", + "lucky-spoons-hide", + "mean-files-fly", + "metal-dogs-swim", + "nasty-crabs-share", + "orange-trees-peel", + "popular-bulldogs-lie", + "popular-mails-wave", + "real-swans-repair", + "renovate-6fb5f1b", + "selfish-kiwis-matter", + "shaggy-birds-happen", + "shaggy-colts-watch", + "sharp-goats-itch", + "shiny-beers-relax", + "short-balloons-work", + "sixty-islands-develop", + "sixty-pigs-shave", + "sixty-singers-push", + "spicy-parents-lick", + "spotty-dryers-explain", + "stupid-pens-occur", + "sweet-readers-compare", + "tame-ads-appear", + "tasty-colts-hug", + "tasty-scissors-tickle", + "ten-pens-draw", + "three-houses-agree", + "three-poems-think", + "two-oranges-joke", + "two-yaks-wave", + "unlucky-buttons-poke", + "wet-cameras-call" ] } diff --git a/.changeset/pretty-buttons-develop.md b/.changeset/pretty-buttons-develop.md deleted file mode 100644 index 7c686df250..0000000000 --- a/.changeset/pretty-buttons-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Auth0 provider now supports optional `connection` and `connectionScope` parameters to configure social identity providers. diff --git a/.changeset/purple-masks-travel.md b/.changeset/purple-masks-travel.md deleted file mode 100644 index 65d0a5fe9b..0000000000 --- a/.changeset/purple-masks-travel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': patch ---- - -Add missing config schema for the `GitHubEntityProvider`. diff --git a/.changeset/quiet-dancers-jog.md b/.changeset/quiet-dancers-jog.md deleted file mode 100644 index 2cca69103a..0000000000 --- a/.changeset/quiet-dancers-jog.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-gcalendar': patch ---- - -Fixed loader showing when user not signed in diff --git a/.changeset/quiet-hats-kick.md b/.changeset/quiet-hats-kick.md deleted file mode 100644 index a0bbab4cb1..0000000000 --- a/.changeset/quiet-hats-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-jenkins-backend': patch ---- - -Fixed a bug where `extraRequestHeaders` configuration was ignored. diff --git a/.changeset/quiet-ligers-draw.md b/.changeset/quiet-ligers-draw.md deleted file mode 100644 index 30f5845a54..0000000000 --- a/.changeset/quiet-ligers-draw.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-app-backend': patch ---- - -Added alpha plugin implementation for the new backend system. Available at `@backstage/plugin-app-backend/alpha`. diff --git a/.changeset/rare-cougars-cross.md b/.changeset/rare-cougars-cross.md deleted file mode 100644 index 3bfb31ae3d..0000000000 --- a/.changeset/rare-cougars-cross.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/cli': patch -'@backstage/plugin-airbrake-backend': patch -'@backstage/plugin-badges-backend': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-graphql-backend': patch -'@backstage/plugin-periskop-backend': patch -'@backstage/plugin-permission-backend': patch -'@backstage/plugin-rollbar-backend': patch -'@backstage/plugin-search-backend': patch -'@backstage/plugin-tech-insights-backend': patch -'@backstage/plugin-user-settings-backend': patch ---- - -Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` diff --git a/.changeset/rare-hotels-cough.md b/.changeset/rare-hotels-cough.md deleted file mode 100644 index 1a2395cf56..0000000000 --- a/.changeset/rare-hotels-cough.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-plugin-api': patch ---- - -Added documentation for `createBackendModule`, with guidelines for choosing a module ID. diff --git a/.changeset/real-swans-repair.md b/.changeset/real-swans-repair.md new file mode 100644 index 0000000000..67eafc9999 --- /dev/null +++ b/.changeset/real-swans-repair.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-permission-common': patch +--- + +Properly handle rules that have no parameters in `PermissionClient` diff --git a/.changeset/red-dots-sleep.md b/.changeset/red-dots-sleep.md deleted file mode 100644 index 0accac9734..0000000000 --- a/.changeset/red-dots-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Switching off duplicated timestamp in case of logging via task logger in a custom action diff --git a/.changeset/red-pants-rush.md b/.changeset/red-pants-rush.md deleted file mode 100644 index d2d7a52237..0000000000 --- a/.changeset/red-pants-rush.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Add `allowAutoMerge` option for `publish:github` action diff --git a/.changeset/renovate-5da63a4.md b/.changeset/renovate-5da63a4.md deleted file mode 100644 index 6d27c9e99f..0000000000 --- a/.changeset/renovate-5da63a4.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Updated dependency `@asyncapi/react-component` to `1.0.0-next.43`. diff --git a/.changeset/renovate-6fb5f1b.md b/.changeset/renovate-6fb5f1b.md new file mode 100644 index 0000000000..f98cc64ea4 --- /dev/null +++ b/.changeset/renovate-6fb5f1b.md @@ -0,0 +1,8 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `@svgr/plugin-jsx` to `6.5.x`. +Updated dependency `@svgr/plugin-svgo` to `6.5.x`. +Updated dependency `@svgr/rollup` to `6.5.x`. +Updated dependency `@svgr/webpack` to `6.5.x`. diff --git a/.changeset/renovate-a02d90b.md b/.changeset/renovate-a02d90b.md deleted file mode 100644 index 7bb69d9988..0000000000 --- a/.changeset/renovate-a02d90b.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Updated dependency `@asyncapi/react-component` to `1.0.0-next.42`. diff --git a/.changeset/rich-carrots-reflect.md b/.changeset/rich-carrots-reflect.md deleted file mode 100644 index 13a67bcd56..0000000000 --- a/.changeset/rich-carrots-reflect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Switched the Jest transform for YAML files to use a custom one available at `@backstage/cli/config/jestYamlTransform.js`. diff --git a/.changeset/rude-bulldogs-sleep.md b/.changeset/rude-bulldogs-sleep.md deleted file mode 100644 index 8a2a0590b3..0000000000 --- a/.changeset/rude-bulldogs-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Allow Placeholder value to be any value, not only string. diff --git a/.changeset/rude-tomatoes-behave.md b/.changeset/rude-tomatoes-behave.md deleted file mode 100644 index 880b4dffd5..0000000000 --- a/.changeset/rude-tomatoes-behave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-bazaar-backend': patch ---- - -Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. diff --git a/.changeset/search-bobcats-love.md b/.changeset/search-bobcats-love.md deleted file mode 100644 index f562961c1b..0000000000 --- a/.changeset/search-bobcats-love.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'@backstage/plugin-search-react': minor ---- - -The search query state now has an optional `pageLimit` property that determines how many results will be requested per page, it defaults to 25. - -Examples: -_Basic_ - -```jsx - - {results => { - // Item rendering logic is omitted - }} - -``` - -_With context_ - -```jsx - - - {results => { - // Item rendering logic is omitted - }} - - -``` diff --git a/.changeset/search-cars-hide.md b/.changeset/search-cars-hide.md deleted file mode 100644 index b89662e90a..0000000000 --- a/.changeset/search-cars-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-common': minor ---- - -There is a new property called `pageLimit` on the `SearchQuery` interface that specifies how many results should be returned per page. diff --git a/.changeset/search-clouds-begin.md b/.changeset/search-clouds-begin.md deleted file mode 100644 index b3c6fa4b69..0000000000 --- a/.changeset/search-clouds-begin.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -'@backstage/plugin-search-react': minor ---- - -The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. -Possibility to provide a custom no results component if needed through the `noResultsComponent` property. - -Examples: - -_Rendering a custom no results component_ - -```jsx -} -/> -``` - -_Disable rendering when there are no results_ - -```jsx - -``` diff --git a/.changeset/search-cycles-sniff.md b/.changeset/search-cycles-sniff.md deleted file mode 100644 index 350e499a18..0000000000 --- a/.changeset/search-cycles-sniff.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-search-backend-node': patch -'@backstage/plugin-search-backend-module-pg': patch -'@backstage/plugin-search-backend-module-elasticsearch': patch ---- - -The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. diff --git a/.changeset/search-dull-planes-prove.md b/.changeset/search-dull-planes-prove.md deleted file mode 100644 index f185207ede..0000000000 --- a/.changeset/search-dull-planes-prove.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -'@backstage/plugin-search-react': minor ---- - -The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. -Possibility to provide a custom no results component if needed through the `noResultsComponent` property. - -Examples: - -_Rendering a custom no results component_ - -```jsx -} - title="Documentation" - noResultsComponent={} -/> -``` - -_Disable rendering when there are no results_ - -```jsx -} - title="Documentation" - disableRenderingWithNoResults -/> -``` diff --git a/.changeset/search-rats-grin.md b/.changeset/search-rats-grin.md deleted file mode 100644 index 7b561d9c19..0000000000 --- a/.changeset/search-rats-grin.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/plugin-search-backend': minor ---- - -The query received by search engines now contains a property called `pageLimit`, it specifies how many results to return per page when sending a query request to the search backend. - -Example: -_Returns up to 30 results per page_ - -``` -GET /query?pageLimit=30 -``` - -The search backend validates the page limit and this value must not exceed 100, but it doesn't set a default value for the page limit parameter, it leaves it up to each search engine to set this, so Lunr, Postgres and Elastic Search set 25 results per page as a default value. diff --git a/.changeset/selfish-kiwis-matter.md b/.changeset/selfish-kiwis-matter.md new file mode 100644 index 0000000000..e258af0e97 --- /dev/null +++ b/.changeset/selfish-kiwis-matter.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Updated `@swc/core` to `v1.3.9` which fixes a `.tsx` parser bug. You may want to run `yarn backstage-cli versions:bump` to get on latest version including the CLI itself. diff --git a/.changeset/selfish-turkeys-exist.md b/.changeset/selfish-turkeys-exist.md deleted file mode 100644 index 3530b3a864..0000000000 --- a/.changeset/selfish-turkeys-exist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -EntityPickers now support flags to control when to include default namespace -in result diff --git a/.changeset/shaggy-birds-happen.md b/.changeset/shaggy-birds-happen.md new file mode 100644 index 0000000000..7f7a691c85 --- /dev/null +++ b/.changeset/shaggy-birds-happen.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-github-issues': minor +--- + +**BREAKING**: Changed the casing of all exported types to have a lowercase "h" in "github". E.g. "GitHubIssuesPage" was renamed to "GithubIssuesPage". Please rename your imports where necessary. diff --git a/.changeset/shaggy-books-smell.md b/.changeset/shaggy-books-smell.md deleted file mode 100644 index 3f8aaa389c..0000000000 --- a/.changeset/shaggy-books-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-tech-insights-node': patch ---- - -Updated package role to be `node-library`. diff --git a/.changeset/shaggy-colts-watch.md b/.changeset/shaggy-colts-watch.md new file mode 100644 index 0000000000..67003bd2d3 --- /dev/null +++ b/.changeset/shaggy-colts-watch.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Disable base path workaround in `Link` component when React Router v6 stable is used. diff --git a/.changeset/sharp-goats-itch.md b/.changeset/sharp-goats-itch.md new file mode 100644 index 0000000000..601d6a7e9d --- /dev/null +++ b/.changeset/sharp-goats-itch.md @@ -0,0 +1,5 @@ +--- +'@backstage/types': patch +--- + +Added the `HumanDuration` type, moved here from `@backstage/backend-tasks`. This type matches the `Duration.fromObject` form of `luxon`. diff --git a/.changeset/shiny-beers-relax.md b/.changeset/shiny-beers-relax.md new file mode 100644 index 0000000000..be8e9b6fb7 --- /dev/null +++ b/.changeset/shiny-beers-relax.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Updated the create-app command to no longer require Git to be installed and configured. A git repository will only be initialized if possible and if not already in an git repository. diff --git a/.changeset/short-balloons-work.md b/.changeset/short-balloons-work.md new file mode 100644 index 0000000000..5d0f66851d --- /dev/null +++ b/.changeset/short-balloons-work.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-bitrise': patch +'@backstage/plugin-code-coverage': patch +'@backstage/plugin-cost-insights': minor +'@backstage/plugin-git-release-manager': patch +'@backstage/plugin-xcmetrics': patch +--- + +Updated recharts to v2.0.0 and fixed typing issues diff --git a/.changeset/silent-bees-repeat.md b/.changeset/silent-bees-repeat.md deleted file mode 100644 index 4592d9a703..0000000000 --- a/.changeset/silent-bees-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -kubernetes service locator now take request context parameters diff --git a/.changeset/silly-rules-join.md b/.changeset/silly-rules-join.md deleted file mode 100644 index 88d3898317..0000000000 --- a/.changeset/silly-rules-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Initializes a git repository when creating an app using @packages/create-app diff --git a/.changeset/sixty-islands-develop.md b/.changeset/sixty-islands-develop.md new file mode 100644 index 0000000000..9eb36cc1a9 --- /dev/null +++ b/.changeset/sixty-islands-develop.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Added props to override default Scaffolder page title, subtitle and pageTitleOverride. +Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component). diff --git a/.changeset/sixty-items-nail.md b/.changeset/sixty-items-nail.md deleted file mode 100644 index bf8f9b8161..0000000000 --- a/.changeset/sixty-items-nail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes': patch ---- - -Fix infinite call bug in `useCustomResources` hook diff --git a/.changeset/sixty-pigs-shave.md b/.changeset/sixty-pigs-shave.md new file mode 100644 index 0000000000..e1f4443ad4 --- /dev/null +++ b/.changeset/sixty-pigs-shave.md @@ -0,0 +1,13 @@ +--- +'@backstage/backend-common': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-git-release-manager': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-github-issues': patch +'@backstage/plugin-github-pull-requests-board': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-techdocs-module-addons-contrib': patch +--- + +Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. diff --git a/.changeset/sixty-singers-push.md b/.changeset/sixty-singers-push.md new file mode 100644 index 0000000000..b5b50ae077 --- /dev/null +++ b/.changeset/sixty-singers-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-bazaar': minor +--- + +The limit prop of BazaarOverviewCard has been removed entirely, and instead replaced with a new optional boolean prop `fullWidth`. The BazaarOverviewCard now always use full height without fixed width. Also fixed problem with link to Bazaar. diff --git a/.changeset/slimy-suns-learn.md b/.changeset/slimy-suns-learn.md deleted file mode 100644 index 4a924a7a18..0000000000 --- a/.changeset/slimy-suns-learn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fixed setting default branch for Bitbucket Server diff --git a/.changeset/slow-mirrors-eat.md b/.changeset/slow-mirrors-eat.md deleted file mode 100644 index 6996a977d0..0000000000 --- a/.changeset/slow-mirrors-eat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Fix GitLab provider setup so that it supports GitLab installations with a path in the URL. diff --git a/.changeset/smooth-tables-pull.md b/.changeset/smooth-tables-pull.md deleted file mode 100644 index 2833ff5da7..0000000000 --- a/.changeset/smooth-tables-pull.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Switch to upfront resolution of `swc-loader` in Webpack config. diff --git a/.changeset/soft-falcons-love.md b/.changeset/soft-falcons-love.md deleted file mode 100644 index 56ed193501..0000000000 --- a/.changeset/soft-falcons-love.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/plugin-user-settings': minor ---- - -**BREAKING**: The `apiRef` passed to `ProviderSettingsItem` now needs to -implement `ProfileInfoApi & SessionApi`, rather than just the latter. This is -unlikely to have an effect on most users though, since the builtin auth -providers generally implement both. - -Fixed settings page showing providers as logged out when the user is using more -than one provider, and displayed some additional login information. diff --git a/.changeset/spicy-parents-lick.md b/.changeset/spicy-parents-lick.md new file mode 100644 index 0000000000..692745ed12 --- /dev/null +++ b/.changeset/spicy-parents-lick.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Include query parameters when navigating to relative links in documents diff --git a/.changeset/spotty-dryers-explain.md b/.changeset/spotty-dryers-explain.md new file mode 100644 index 0000000000..41bef3fbf7 --- /dev/null +++ b/.changeset/spotty-dryers-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-xcmetrics': patch +--- + +Removed an unused and hidden build details route. diff --git a/.changeset/stupid-dragons-complain.md b/.changeset/stupid-dragons-complain.md deleted file mode 100644 index 44be8342bf..0000000000 --- a/.changeset/stupid-dragons-complain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -Increased test database max connection pool size to reduce the risk of resource exhaustion. diff --git a/.changeset/stupid-pens-occur.md b/.changeset/stupid-pens-occur.md new file mode 100644 index 0000000000..d8af9a447a --- /dev/null +++ b/.changeset/stupid-pens-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': minor +--- + +**BREAKING**: Removed the `HomepageTimer` as it has been replaced by the `HeaderWorldClock` in the Home plugin and was deprecated over a year ago. diff --git a/.changeset/sweet-insects-camp.md b/.changeset/sweet-insects-camp.md deleted file mode 100644 index 4f91a196e8..0000000000 --- a/.changeset/sweet-insects-camp.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/cli': minor ---- - -**BREAKING**: Bumped `jest`, `jest-runtime`, and `jest-environment-jsdom` to v29. This is up from v27, so check out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and [v29](https://jestjs.io/docs/upgrading-to-jest29) (later [here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides. - -Particular changes that where encountered in the main Backstage repo are: - -- The updated snapshot format. -- `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. -- Error objects collected by `withLogCollector` from `@backstage/test-utils` are now objects with a `detail` property rather than a string. diff --git a/.changeset/sweet-readers-compare.md b/.changeset/sweet-readers-compare.md new file mode 100644 index 0000000000..dd61b0afdc --- /dev/null +++ b/.changeset/sweet-readers-compare.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-circleci': patch +--- + +Update screenshots in documentation to match latest CircleCI plugin diff --git a/.changeset/swift-phones-cheat.md b/.changeset/swift-phones-cheat.md deleted file mode 100644 index 6d1653caa3..0000000000 --- a/.changeset/swift-phones-cheat.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Removed `tsx` and `jsx` as supported extensions in backend packages. For most -repos, this will not have any effect. But if you inadvertently had added some -`tsx`/`jsx` files to your backend package, you may now start to see `code: 'MODULE_NOT_FOUND'` errors when launching the backend locally. The reason for -this is that the offending files get ignored during transpilation. Hence, the -importing file can no longer find anything to import. - -The fix is to rename any `.tsx` files in your backend packages to `.ts` instead, -or `.jsx` to `.js`. diff --git a/.changeset/tall-baboons-deliver.md b/.changeset/tall-baboons-deliver.md deleted file mode 100644 index a69e52e31a..0000000000 --- a/.changeset/tall-baboons-deliver.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-user-settings-backend': patch ---- - -Fix wrong import statement in `README.md`. diff --git a/.changeset/tame-ads-appear.md b/.changeset/tame-ads-appear.md new file mode 100644 index 0000000000..887cc73252 --- /dev/null +++ b/.changeset/tame-ads-appear.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-tech-insights-backend': patch +'@backstage/plugin-tech-insights-node': patch +--- + +Add a default delay to the fact retrievers to prevent cold-start errors diff --git a/.changeset/tasty-colts-hug.md b/.changeset/tasty-colts-hug.md new file mode 100644 index 0000000000..e56d48a441 --- /dev/null +++ b/.changeset/tasty-colts-hug.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-azure-sites': minor +'@backstage/plugin-azure-sites-backend': minor +'@backstage/plugin-azure-sites-common': minor +--- + +Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. diff --git a/.changeset/tasty-scissors-tickle.md b/.changeset/tasty-scissors-tickle.md new file mode 100644 index 0000000000..745172df36 --- /dev/null +++ b/.changeset/tasty-scissors-tickle.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +--- + +`BitbucketServerEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/bitbucketServer/discovery diff --git a/.changeset/techdocs-olive-worms-yell.md b/.changeset/techdocs-olive-worms-yell.md deleted file mode 100644 index 66b205fc19..0000000000 --- a/.changeset/techdocs-olive-worms-yell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': minor ---- - -Add optional `catalogClient` argument to `createRoute` parameters diff --git a/.changeset/ten-pens-draw.md b/.changeset/ten-pens-draw.md new file mode 100644 index 0000000000..be35a6f3b0 --- /dev/null +++ b/.changeset/ten-pens-draw.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-gerrit': patch +--- + +`GerritEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/gerrit/discovery diff --git a/.changeset/tender-drinks-drive.md b/.changeset/tender-drinks-drive.md deleted file mode 100644 index 0ea69832af..0000000000 --- a/.changeset/tender-drinks-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Add functionality to shutdown scaffolder tasks if they are stale diff --git a/.changeset/tender-parrots-peel.md b/.changeset/tender-parrots-peel.md deleted file mode 100644 index 566467e143..0000000000 --- a/.changeset/tender-parrots-peel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Added `*.session.sql` Visual Studio Code database functionality files to `.gitignore` in the default template. This is optional but potentially helpful if your developers use Visual Studio Code; you can add a line with that exact value to your own root `.gitignore` if you want the same. diff --git a/.changeset/thick-kings-destroy.md b/.changeset/thick-kings-destroy.md deleted file mode 100644 index 77c6be0d3c..0000000000 --- a/.changeset/thick-kings-destroy.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Make sure to stitch entities correctly after deletion, to ensure that their relations are updated. diff --git a/.changeset/three-houses-agree.md b/.changeset/three-houses-agree.md new file mode 100644 index 0000000000..041cd471d3 --- /dev/null +++ b/.changeset/three-houses-agree.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-tech-insights-backend': patch +'@backstage/plugin-tech-insights-node': patch +--- + +Use `HumanDuration` from `@backstage/types` diff --git a/.changeset/three-poems-think.md b/.changeset/three-poems-think.md new file mode 100644 index 0000000000..aad3cae117 --- /dev/null +++ b/.changeset/three-poems-think.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-catalog-backend-module-github': minor +--- + +Added the ability for the GitHub discovery provider to validate that catalog files exist before emitting them. + +Users can now set the `validateLocationsExist` property to `true` in their GitHub discovery configuration to opt in to this feature. +This feature only works with `catalogPath`s that do not contain wildcards. + +When `validateLocationsExist` is set to `true`, the GitHub discovery provider will retrieve the object from the +repository at the provided `catalogPath`. +If this file exists and is non-empty, then it will be emitted as a location for further processing. +If this file does not exist or is empty, then it will not be emitted. +Not emitting locations that do not exist allows for far fewer calls to the GitHub API to validate locations that do not exist. diff --git a/.changeset/tiny-mails-bathe.md b/.changeset/tiny-mails-bathe.md deleted file mode 100644 index 868c2a8d36..0000000000 --- a/.changeset/tiny-mails-bathe.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Updated the root `test` scripts to use `backstage-cli repo test`. - -To apply this change to an existing app, make the following change to the root `package.json`: - -```diff -- "test": "backstage-cli test", -- "test:all": "lerna run test -- --coverage", -+ "test": "backstage-cli repo test", -+ "test:all": "backstage-cli repo test --coverage", -``` diff --git a/.changeset/tough-hairs-sparkle.md b/.changeset/tough-hairs-sparkle.md deleted file mode 100644 index 404657227b..0000000000 --- a/.changeset/tough-hairs-sparkle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Add support for `allowedOrganizations` and `allowedOwners` to the `AzureRepoPicker`. diff --git a/.changeset/tough-seahorses-learn.md b/.changeset/tough-seahorses-learn.md deleted file mode 100644 index 518b6b6967..0000000000 --- a/.changeset/tough-seahorses-learn.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -'@backstage/plugin-bazaar-backend': minor ---- - -**BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. - -These changes are **required** to `packages/backend/src/plugins/bazaar.ts` - -The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. - -```diff -import { PluginEnvironment } from '../types'; -import { createRouter } from '@backstage/plugin-bazaar-backend'; -import { Router } from 'express'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, -+ identity: env.identity, - }); -} -``` diff --git a/.changeset/twelve-melons-notice.md b/.changeset/twelve-melons-notice.md deleted file mode 100644 index 4a91642426..0000000000 --- a/.changeset/twelve-melons-notice.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-tech-insights-backend': patch ---- - -Modify router endpoint to handle singular and collections of request parameters similarly. diff --git a/.changeset/two-oranges-joke.md b/.changeset/two-oranges-joke.md new file mode 100644 index 0000000000..7f116e8ab0 --- /dev/null +++ b/.changeset/two-oranges-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +--- + +Add `bitbucketServerEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/two-yaks-wave.md b/.changeset/two-yaks-wave.md new file mode 100644 index 0000000000..97c2908928 --- /dev/null +++ b/.changeset/two-yaks-wave.md @@ -0,0 +1,20 @@ +--- +'@backstage/core-components': patch +'@backstage/create-app': patch +'@backstage/plugin-azure-sites': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-circleci': patch +'@backstage/plugin-cloudbuild': patch +'@backstage/plugin-dynatrace': patch +'@backstage/plugin-gcalendar': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-github-issues': patch +'@backstage/plugin-gitops-profiles': patch +'@backstage/plugin-jenkins': patch +'@backstage/plugin-kafka': patch +'@backstage/plugin-pagerduty': patch +'@backstage/plugin-rollbar': patch +'@backstage/plugin-scaffolder': patch +--- + +Prefer using `Link` from `@backstage/core-components` rather than material-UI. diff --git a/.changeset/unlucky-buttons-poke.md b/.changeset/unlucky-buttons-poke.md new file mode 100644 index 0000000000..b0ccd2039a --- /dev/null +++ b/.changeset/unlucky-buttons-poke.md @@ -0,0 +1,15 @@ +--- +'@backstage/integration': patch +'@backstage/plugin-catalog-backend-module-aws': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-stack-overflow-backend': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Remove explicit default visibility at `config.d.ts` files. + +```ts +/** + * @visibility backend + */ +``` diff --git a/.changeset/unlucky-seas-sip.md b/.changeset/unlucky-seas-sip.md deleted file mode 100644 index 947e248244..0000000000 --- a/.changeset/unlucky-seas-sip.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. diff --git a/.changeset/warm-days-watch.md b/.changeset/warm-days-watch.md deleted file mode 100644 index b0a211c7a8..0000000000 --- a/.changeset/warm-days-watch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Added support for copy entity URL in entity page context menu diff --git a/.changeset/wet-cameras-call.md b/.changeset/wet-cameras-call.md new file mode 100644 index 0000000000..3d58719269 --- /dev/null +++ b/.changeset/wet-cameras-call.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-common': patch +--- + +The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + +- **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. +- Add the `PodStatusFetchResponse` to the `FetchResponse` union type. +- Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. diff --git a/.changeset/wild-weeks-live.md b/.changeset/wild-weeks-live.md deleted file mode 100644 index e66b3193d0..0000000000 --- a/.changeset/wild-weeks-live.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. diff --git a/.changeset/wise-ligers-scream.md b/.changeset/wise-ligers-scream.md deleted file mode 100644 index d71c69fd42..0000000000 --- a/.changeset/wise-ligers-scream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cost-insights': patch ---- - -Fixed bug in `CostOverviewBreakdownChart` component where some datasets caused the cost overview breakdown chart to tear. diff --git a/.changeset/yellow-lemons-march.md b/.changeset/yellow-lemons-march.md deleted file mode 100644 index 24d9e760ad..0000000000 --- a/.changeset/yellow-lemons-march.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-app-api': patch ---- - -Properly export `rootLoggerFactory`. diff --git a/.changeset/young-bugs-remember.md b/.changeset/young-bugs-remember.md deleted file mode 100644 index 8f78495ed7..0000000000 --- a/.changeset/young-bugs-remember.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-github': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Updated the `moduleId` of the experimental module export. diff --git a/.changeset/young-olives-cheer.md b/.changeset/young-olives-cheer.md deleted file mode 100644 index 536a8d6370..0000000000 --- a/.changeset/young-olives-cheer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-github-issues': patch ---- - -Add filtering and ordering to the graphql query diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cb1dd5d322..2dd8f4b55b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -35,6 +35,7 @@ yarn.lock @backstage/reviewers @backst /plugins/code-coverage @backstage/reviewers @alde @nissayeva /plugins/code-coverage-backend @backstage/reviewers @alde @nissayeva /plugins/cost-insights @backstage/reviewers @backstage/silver-lining +/plugins/cost-insights-* @backstage/reviewers @backstage/silver-lining /plugins/explore @backstage/reviewers @backstage/sda-se-reviewers /plugins/explore-react @backstage/reviewers @backstage/sda-se-reviewers /plugins/fossa @backstage/reviewers @backstage/sda-se-reviewers @@ -44,13 +45,15 @@ yarn.lock @backstage/reviewers @backst /plugins/ilert @backstage/reviewers @yacut /plugins/jenkins @backstage/reviewers @timja /plugins/jenkins-backend @backstage/reviewers @timja -/plugins/kafka @backstage/reviewers @nirga -/plugins/kafka-backend @backstage/reviewers @nirga +/plugins/kafka @backstage/reviewers @nirga @andrewthauer +/plugins/kafka-backend @backstage/reviewers @nirga @andrewthauer /plugins/kubernetes @backstage/reviewers @backstage/warpspeed /plugins/kubernetes-* @backstage/reviewers @backstage/warpspeed /plugins/newrelic-dashboard @backstage/reviewers @mufaddal7 /plugins/playlist @backstage/reviewers @kuangp /plugins/playlist-* @backstage/reviewers @kuangp +/plugins/rollbar @backstage/reviewers @andrewthauer +/plugins/rollbar-backend @backstage/reviewers @andrewthauer /plugins/scaffolder-backend-module-rails @backstage/reviewers @angeliski /plugins/scaffolder-backend-module-yeoman @backstage/reviewers @pawelmitka /plugins/search @backstage/reviewers @backstage/techdocs-core diff --git a/.github/renovate.json5 b/.github/renovate.json5 index a8bc3448a5..5c4745c70c 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -15,5 +15,10 @@ groupName: 'API Extractor / Rush Stack monorepo packages', rangeStrategy: 'replace', }, + { + matchSourceUrlPrefixes: ['https://github.com/gregberge/svgr'], + groupName: 'SVGR monorepo packages', + rangeStrategy: 'replace', + }, ], } diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index ae94912f38..d21d0f9f25 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -1,7 +1,9 @@ abc accessors +ACLs addon addons +ADRs Airbrake Airbrakes airbrake @@ -16,6 +18,7 @@ autoscaling Autoscaling autoselect Avro +backend's backported backporting Bigtable @@ -28,6 +31,7 @@ bool boolean builtins callout +CDNs Chai changeset changesets @@ -68,6 +72,7 @@ dataflow dayjs debounce Debounce +debuggability declaratively deduplicated deps @@ -85,6 +90,8 @@ dockerfiles Dockerize dockerode Docusaurus +DOMPurify +don'ts dynatrace Dynatrace ecco @@ -93,6 +100,8 @@ Env elasticsearch esbuild eslint +ESModule +ESModules etag Expedia facto @@ -112,6 +121,8 @@ github Gitiles gitlab GitLab +gitea +Gitea Gource Grafana graphql @@ -129,16 +140,20 @@ hotspots http https Iain +iLert img incentivised Indal +indexable inlined inlinehilite +integrator's interop JaCoCo JavaScript jenkins Jira +JWTs jq js json @@ -147,6 +162,7 @@ jsx Kaewkasi Keyv Knex +KPIs kubectl kubernetes kubernetes @@ -162,6 +178,7 @@ lunr Luxon magiclink mailto +maintainer's maintainership makefile md @@ -216,6 +233,7 @@ orgs pagerduty pageview parallelization +parseable Patrik Peloton performant @@ -300,6 +318,10 @@ stringify stringified subcomponent subcomponents +subfolder +subfolders +subheader +subheaders subkey subroutes subtree @@ -308,6 +330,7 @@ Superfences superset supertype storable +SVGs talkdesk Talkdesk tasklist @@ -319,6 +342,7 @@ templater Templater templaters Templaters +TFRecord theia thumbsup todo @@ -335,6 +359,7 @@ transpiled transpiler transpilers truthy +TSDoc typeahead ui unbreak @@ -352,17 +377,22 @@ utils validator validators varchar +VPCs +VSCode Wayfair Weaveworks Webpack +widget's winston www WWW +XCMetrics XML xyz yaml Zalando Zhou +zod zoomable zsh Alef diff --git a/.github/workflows/automate_stale.yml b/.github/workflows/automate_stale.yml index 35b0a8c0a6..51bf41d61a 100644 --- a/.github/workflows/automate_stale.yml +++ b/.github/workflows/automate_stale.yml @@ -22,7 +22,8 @@ jobs: stale-pr-message: > This PR has been automatically marked as stale because it has not had recent activity from the author. It will be closed if no further activity occurs. - If you are the author and the PR has been closed, feel free to re-open the PR and continue the contribution! + If the PR was closed and you want it re-opened, let us know + and we'll re-open the PR so that you can continue the contribution! days-before-pr-stale: 7 days-before-pr-close: 5 exempt-pr-labels: after-vacations,will-fix diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index fb99f41c55..af6f222377 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -55,7 +55,7 @@ jobs: run: ls microsite/build/backstage && ls microsite/build/backstage/storybook - name: Deploy both microsite and storybook to gh-pages - uses: JamesIves/github-pages-deploy-action@v4.4.0 + uses: JamesIves/github-pages-deploy-action@v4.4.1 with: branch: gh-pages folder: microsite/build/backstage diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 46a6227f2c..dd35ba26f3 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -1,5 +1,6 @@ name: Sync Snyk GitHub issues on: + workflow_dispatch: schedule: - cron: '0 */4 * * *' @@ -35,9 +36,11 @@ jobs: --org=backstage-dgh --strict-out-of-sync=false --json-file-output=snyk.json + --debug json: true env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + NODE_OPTIONS: --max-old-space-size=7168 - name: Update Github issues run: yarn ts-node scripts/snyk-github-issue-sync.ts env: diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 5aa12fe6b5..22421925c7 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -4,6 +4,10 @@ on: branches: - master +concurrency: + group: sync-version-packages + cancel-in-progress: true + jobs: create-release-pr: name: Create Changeset PR diff --git a/ADOPTERS.md b/ADOPTERS.md index ca56a00890..cc5db5dea7 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -6,7 +6,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | Organization | Contact | Description of Use | | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | -| [bol.com](https://www.bol.com) | [@sagacity](https://github.com/sagacity) | Initial work being done to unify platform tooling. | +| [bol.com](https://www.bol.com) | [@acierto](https://github.com/acierto), [@clanghout](https://github.com/clanghout) | Initial work being done to unify platform tooling. | | [DFDS](https://www.dfds.com) | [@carlsendk](https://github.com/carlsendk) | V2 self-service platform. | | [Roadie](https://roadie.io) | [@dtuite](https://github.com/dtuite) | Hosted, managed Backstage with easy set-up | | [Roku](https://www.roku.com) | [@timurista](https://github.com/timurista) | Initial work on Cloud engineering service platform. | @@ -22,10 +22,10 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | | [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | | [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | -| [Expedia Group](https://www.expediagroup.com) | [@guillermomanzo](https://github.com/guillermomanzo), [Sheena Sharma](mailto:shesharma@expediagroup.com), [@ajbw](https://github.com/ajbw) | EG Common Developer Toolkit | +| [Expedia Group](https://www.expediagroup.com) | [@gman0922](https://github.com/gman0922), [Sheena Sharma](mailto:shesharma@expediagroup.com), [Alekhya Karuturi](mailto:akaruturi@expediagroup.com) | EG Developer Front Door | | [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | | [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | -| [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | +| [Lunar](https://lunar.app) | [Bjørn Hald Sørensen](https://github.com/crevil) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | | [Trendyol](https://trendyol.com) | [Gamze Senturk](https://github.com/gmzsenturk), [Mert Can Bilgic](https://github.com/mertcb) | The Developer Portal has been called `Pandora`. Provides an overview of Trendyol tech ecosystem. TechDocs, Catalog, Custom Plugins and Theme. | | [Peloton](https://www.onepeloton.com/) | [Matt Waldron](https://github.com/daftgopher) | Creating our first developer portal and tech-docs. Exploring Service Catalog, Tech Insights and Cost Insights as well. | | [Brex](https://www.brex.com/) | [Vamsi Chitters](https://github.com/vamsikc) | A centralized UI to understand how a service fits in the whole Brex architecture and manage a team’s engineering dependencies. | @@ -71,7 +71,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem | | [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services | | [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. | -| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services through templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. | +| [PicPay](https://www.picpay.com) | [Elton Welsch](https://github.com/eltonwelsch), [Emanuella Okada](https://github.com/ManuOkadaPicPay), [PicPay](https://github.com/picpay) | Developer portal for building services through templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. | | [Epic Games](https://www.epicgames.com) | [Brian Jung](https://github.com/brian-at-epic), [Jeff Goldian](https://github.com/jeffgoldian-Epic) | Developer Portal: Service Catalog, Documentation, Software Templates and more making our internal teams' lives easier! | | [Globo](https://globo.com) | [Carlos Gusmão](https://github.com/caeugusmao), [Guilherme Vierno](https://github.com/vierno), [Denis Aoki](https://github.com/dnsaoki2), [Maycon Dionisio](https://github.com/MayconDionisio), | Reduce the friction of accessing the information engineers need about Globo's digital services through a coherent and centralized experience. | | [QBE](https://www.qbe.com/) | [Daniel Steel](https://github.com/danielsteelqbe), [Pete Jespers](https://github.com/petejespersqbe) | Developer portal allowing our global teams to explore and create applications, documentation and cloud infrastructure easily and quickly 🚀 | @@ -214,4 +214,5 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [Vipps](https://vipps.no) | [Martin Ehrnst](https://github.com/ehrnst) | Vipps use backstage for our service catalog, documentation, and developer portal. Using templates we are able to simplify the developer experience when deploying new services to our platform. | | [Ferrovial](https://ferrovial.com) | [Jose Luis Rosado](mailto:jlrosado@ferrovial.com) | Backstage is helping us to improve and acelerate dev experience helping teams to quickly find technical documentation, infrastructure templates, pipelines, software components and quickstarters that have been developed by our squads in a inner source friendly environment. | | [Inter&Co](https://bancointer.com.br) | [Arnaud Lanna](https://github.com/arnaudlanna), [Adriano Silva](https://github.com/adrianovss), [Bruno Grossi](https://github.com/begrossi) | We're using Backstage as our internal Developer Portal to catalog and collect repositories and microservices pieces of information like ownership, deployment time, and documentation. | -| [StatusNeo](https://statusneo.com/) | [Karan Nangru](mailto:nangru@statusneo.com), [@NishkarshRaj](https://github.com/NishkarshRaj), and [Gaurav Sarien](mailto:gaurav.sarien@statusneo.com) | Harnessing the power of central catalog inventory and self-serving software templates | +| [StatusNeo](https://statusneo.com/) | [Karan Nangru](mailto:nangru@statusneo.com), [@NishkarshRaj](https://github.com/NishkarshRaj), and [Gaurav Sarien](mailto:gaurav.sarien@statusneo.com) | Harnessing the power of central catalog inventory and self-serving software templates | +| [Alaska Airlines](https://alaskaair.com) | [@swerdick](https://github.com/swerdick) | Backstage is the developer portal for our 'software delivery platform'. Consolidating developer tools to one place, and providing automation to make it easy for developers to create and deploy applications to Kubernetes | diff --git a/README.md b/README.md index 5dba1fdf52..9a3c4cdb5a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![headline](docs/assets/headline.png) +[![headline](docs/assets/headline.png)](https://backstage.io/) # [Backstage](https://backstage.io) @@ -53,7 +53,7 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how - [Adopters](ADOPTERS.md) - Companies already using Backstage - [Blog](https://backstage.io/blog/) - Announcements and updates - [Newsletter](https://mailchi.mp/spotify/backstage-community) - Subscribe to our email newsletter -- [Backstage Community Sessions](https://github.com/backstage/community) - Join monthly meetup and explore Backstage community +- [Backstage Community Sessions](https://github.com/backstage/community) - Join monthly meetups and explore Backstage community - Give us a star ⭐️ - If you are using Backstage or think it is an interesting project, we would love a star ❤️ ## License diff --git a/REVIEWING.md b/REVIEWING.md new file mode 100644 index 0000000000..469ac249f7 --- /dev/null +++ b/REVIEWING.md @@ -0,0 +1,317 @@ +# Introduction + +This file provides pointers for reviewing pull requests. While the main audience are reviewers, this can also be useful if you are contributing to this repository. + +## Code Style + +See our code style documented at [STYLE.md](./STYLE.md). + +In particular when it comes to naming, make sure that naming follows established conventions within the project and/or package. + +When adding new dependencies to packages it is always preferred to use version ranges that are already in use by other packages in the repository. This helps minimize lockfile changes and reduce package duplication, both in our repository as well as other Backstage installations. + +## Secure Coding Practices + +Be sure to familiarize yourself with our [secure coding practices](./SECURITY.md#coding-practices). + +## Release & Versioning Policy + +When reviewing pull requests it's important to consider our [versioning policy and release cycle](https://backstage.io/docs/overview/versioning-policy). Generally the most important bit is our [package versioning policy](https://backstage.io/docs/overview/versioning-policy#package-versioning-policy), which describes when and how we can ship breaking changes. We'll dive into how to identify breaking changes in a different section. + +One other thing to keep in mind, especially when merging pull requests, is where in the release cycle we're currently at. In particular you want to avoid merging any large or risky changes towards the end of each release cycle. If there is a change that is ready to be merged, but you want to hold off until the next main line release, then you can label it with the `merge-after-release` label. + +## Changesets + +We use changesets to track changes in all published packages. Changesets both define what should go into the changelog of each package, but also what kind of version bump should be done for the next release. +An introduction to changesets can be found in our [contribution guidelines](./CONTRIBUTING.md#creating-changesets). + +When reviewing a changeset, the most important things to look for are the bump levels, i.e. `major` / `minor` / `patch`, as well as whether the content is accurate and if it's written in a way that makes sense when reading it in the changelog for each package. + +### Reviewing Changeset Bump Levels + +The following table provides a reference for what type of version bump is needed for each package. This is applied separately to each individual package, it does not matter what the scope of a change is in any other broader context. + +| Scope | Current Package Version | Bump Level | +| --------------- | ----------------------- | ---------- | +| Breaking Change | `1.0` and above | `major` | +| New Feature | `1.0` and above | `minor` | +| Fix | `1.0` and above | `patch` | +| Breaking Change | `0.x` | `minor` | +| New Feature | `0.x` | `patch` | +| Fix | `0.x` | `patch` | + +The only situation where a package that is currently at `0.x` can have a `major` bump is if all owners and stakeholders of the package agree that the package is ready to be released as `1.0`. + +### Reviewing Changeset Content + +Each changeset should be written in a way that describes the impact of the change for the users of each package. The contents of the changesets will end up in the changelog of each package, for example [@backstage/core-plugin-api](./packages/core-plugin-api/CHANGELOG.md). The changelogs are intended to provide both a summary of the new features as well as guidance in the case of breaking changes or deprecations. + +Some things that changeset should NOT contain are: + +- Internal architecture details - these are generally not interesting to users, focus on the impact towards users of the package instead. +- Information related to a different package. +- A large amount of content, consider for example a separate migration guide instead, either in the package README or [./docs/](./docs/), and then link to that instead. +- Documentation - changesets can describe new features, but it should not be relied on for documenting them. Documentation should either be placed in [TSDoc](https://tsdoc.org) comments, package README, or [./docs/](./docs/). + +### When is a changeset needed? + +In general our changeset feedback bot will take care of informing whether a changeset is needed or not, but there are some edge cases. Whether a changeset is needed depends mostly on what files have been changed, but sometimes also on the kind of change that has been made. + +Changes that do NOT need a new changeset: + +- Changes to any test, storybook, or other local development files, for example, `MyComponent.test.tsx`, `MyComponent.stories.tsx`, `**mocks**/MyMock.ts`, `.eslintrc.js`, `setupTests.ts`, or `api-report.md`. Explained differently, it is only files that affect the published package that need changesets, such as source files and additional resources like `package.json`, `README.md`, `config.d.ts`, etc. +- When tweaking a change that has not yet been released, you can rely on and potentially modify the existing changeset instead. +- Changes that do not belong to a published packages, either because it's not a package at all, such as `docs/`, or because the package is private, such as `packages/app`. +- Changes that do not end up having an effect on the published package, such as whitespace fixes or code formatting changes. Although it's also fine to have a short changeset for these kind of changes too. + +### Changeset Example + +Consider the following scenario for a changeset: + +A new `EntityList` component has been added to `plugins/catalog-react`. + +Below are examples of a good and three bad changesets for that change. + +**GOOD** + +```md +--- +'@backstage/plugin-catalog-react': minor +--- + +Added a new `EntityList` component that can be used to display detailed information about a list of entities. +``` + +The `@backstage/plugin-catalog-react` package has reached version `1.x`, which means that feature additions that aren't breaking should be a `minor` change. We don't bother with too much documentation, keeping it short and sweet. The main purpose is to inform users that this new component exists and to give them an idea of how they can use it. + +**BAD** + +```md +--- +'@backstage/plugin-catalog-react': minor +'@backstage/plugin-catalog': minor +--- + +Added `EntityList` component. + +Fixed a bug in the catalog index page. +``` + +This changeset is too short, it's best to give users an idea of how they can benefit from the new addition. + +It also includes changes affecting both the Catalog and Catalog React library. It should be split into two separate changesets for each of the two packages, otherwise we'll end up with redundant and unrelated information in both changelogs. + +**BAD** + +```md +--- +'@backstage/plugin-catalog-react': major +--- + +Added a new `EntityList` component that can be used to display detailed information about a list of entities. The component looks like this: + +![EntityList screenshot](./docs/assets/headline.png) + +It accepts the following properties: + +- entities - The entities that should be listed. +- title - An optional formatting function for the list titles. +- dialog - An optional component that overrides the default details dialog. +``` + +This changeset is getting too detailed. It's not always bad to get this much into the weeds, but keep in mind that changesets are not easy to browse when searching for information about specific APIs. It's better to document things like this separately and keep the changeset more lean. Also avoid linking to assets in changesets, keep them text-only. + +The change is also marked as a breaking `major` change. This should be changed to `minor` since adding new APIs is never a breaking change. + +**BAD** + +```md +--- +'@backstage/plugin-catalog-react': patch +--- + +Added a new `EntityList` component that can be used to display detailed information about a list of entities. The `ListView` component was also refactored in order to make it possible to reuse it between the new `EntityList` and `KindList` components. +``` + +Assuming that the `ListView` component is not public API, this changeset goes into details that are not interesting to the user of the package. Internal changes do not need to be highlighted in changesets. If an internal refactor is the only change then it's alright to say something short like "Internal refactor to improve code reuse", but otherwise those details should be left out. + +The `@backstage/plugin-catalog-react` package has also reached `1.x`, which means that new features should be introduced through a `minor` bump. We'd only use `patch` bumps for minor changes or fixes that do not affect the public API. + +## Breaking Changes + +Identifying breaking changes can be quite tricky. You need to look at the changes from both the point of view of consumers and producers of APIs, as well as behavioral changes. In this section we explore a couple of methods for identifying whether a change is breaking or not. + +### Behavioral Changes + +These are changes where the behavior of the code changes, but the public API is unchanged or doesn't have any breaking changes. They can be anything from tiny tweaks, like adding a bit of padding to a visual element, to a complete redesign and refactor of an entire plugin. + +It's hard to set up exact rules for when a behavioral change is breaking or not. In some cases it's obvious, for example if you remove important functionality of a system, while in other cases it can be very hard to tell. In the end what's important is whether a significant number of users of the package will be negatively impacted by the change. One question that you can ask yourself here is "is it likely that there are users that don't want the new behavior, or will need to change their code to adapt to the new behavior?" If the answer is yes, then it's likely a breaking change. You do also want to keep [xkcd.com/1172](https://xkcd.com/1172/) in mind though. + +Note that even a bug fix can be considered a breaking change in some situations. One thing to lean on in that case is what the _documented_ behavior is. If the current behavior does not match the documented behavior, then a change to match the documentation is generally not a breaking change. That is unless it is likely that there are a significant number of users that will be impacted by the change. + +For tricky behavioral changes you may simply need to let end users provide feedback. This can be done either by hiding the new behavior behind an experimental feature switch, or by releasing the change early on in the release cycle, preferably in the first or second `next`-line release. Be ready to respond to feedback and potentially revert the change if needed. + +### Public API Changes + +Typescript is a huge help when it comes to identifying breaking changes, as well as the API Reports that we generate for all packages. Most of the time it is enough to only look at the API Reports to determine whether a change is breaking or not. If you determine that a change is breaking at the TypeScript level, then it is a breaking change. + +In this section we will be talking about changed "types", but by that we mean any kind of exported symbol from packages, such as TypeScript types aliases or interfaces, functions, classes, constants, etc. + +#### API Reports + +We generate API Reports using the [API Extractor](https://api-extractor.com/) tool. These reports are generated for most packages in the Backstage repository, and are stored in the `api-report.md` file of each package. For CLI package we use custom tooling, and instead store the result in `cli-report.md`. Whenever the public API of a package changes, the API Report needs to be updated to reflect the new state of the API. Our CI checks will fail if the API reports are not up to date in a pull request. + +Each API report contains a list of all the exported types of each package. As long as the API report does not have any warnings it will contain the full publicly facing API of the package, meaning you do not need to consider any other changes to the package from the point of view of TypeScript API stability. + +Exported types can be marked with either `@public`, `@alpha` or `@beta` release tags. It is only the `@public` exports that we consider to be part of the stable API. The `@alpha` and `@beta` exports are considered unstable and can be changed at any time without needing a breaking package versions bump. However, this **ONLY** applies if the package has been configured to use experimental type builds, which looks like this in `package.json`: + +```json + "build": "backstage-cli package build --experimental-type-build" +``` + +If a package does not have this configuration, then all exported types are considered stable, even if they are marked as `@alpha` or `@beta`. + +#### Type Contract Direction + +An important distinction to make when looking at changes to an API Report is the direction of the contract of a changed type, that is, whether it's used as input or output from the user's point of view. In the next two sections we'll dive into the different directions of a type contract, and how it affects whether a change is breaking or not. + +#### Input Types + +An input type is one where a value needs to be provided by users of the package. The most common form of input type are function, constructor, and method parameters. + +The following is an example where `MyComponentProps` is an input type: + +```ts +type MyComponentProps = { + title: string; + size?: 'small' | 'medium' | 'large'; +}; + +function MyComponent(props: MyComponentProps): JSX.Element; +``` + +And from the package user's point of view it would look something like this: + +```tsx + +``` + +When modifying an input type, any change that increases constraints are breaking. For example, if we made the `size` prop required, that would be a breaking change. Likewise, if we changed the type of `size` to `'small' | 'large'`, that would also be breaking. + +On the other hand, it's fine to relax constraints without it being a breaking change. For example, if we made the `title` prop optional, that would not be breaking. Likewise, if we changed the type of `size` to `'small' | 'medium' | 'large' | 'huge'`, that would not be breaking either. It is also possible to add new properties without it being a breaking change, as long as they are optional. + +There's an edge-case where completely removing a property is also considered a breaking change. That's because of TypeScript being strict and refusing unknown properties, rather than a runtime breaking change. It is typically an easy thing for consumers to fix though. + +Another way to think about the rules for evolving input types is that the old type must be assignable to the new type. In this case for example `_props: NewComponentProps = {} as OldComponentProps`. It's not a silver bullet though, because of edge-cases like the one mentioned above. + +#### Output Types + +An output type is one that the user receives from the packages. One of the most obvious examples here are the top-level exports from the package itself, but it also includes for example function return types. + +The following is an example where both `useBox` and `Box` are output types: + +```ts +type Box = { + title: string; + shape?: 'square' | 'rounded'; +}; + +function useBox(): Box; +``` + +And from the consumer's point of view it would look something like this: + +```ts +const { title, shape } = useBox(); +``` + +When modifying an output type, any change that reduces constraints are breaking. For example, if we made the `title` property optional, that would be a breaking change, or if we changed the type of `shape` to `'square' | 'rounded' | 'octagon'`. + +Adding new properties is not a breaking change, regardless of whether they are optional or not. Removing properties is on the other hand always breaking. + +It is generally fine to increase constraints without it being a breaking change. For example, if we made the `shape` property required, that would not be breaking. + +There are some edge-cases though, for example if `shape` was changed to just `'square'`, that would be a breaking change because consumers might be checking for `box.shape === 'rounded'`, which would then be breaking. It's typically a quite easy thing for consumers to fix though. More generally, type unions and discriminated unions are quite troublesome in output types, as both adding and removing types from them are considered breaking changes. + +Another way to think about the rules for evolving output types is that the new type must be assignable to the old type. In this case for example `_box: OldBox = {} as NewBox`. It's not a silver bullet though, because of edge-cases like the one mentioned above. + +#### I/O Types + +Some types are considered both input and output types. For example, consider the following example: + +```ts +type Point = { + x: number; + y: number; +}; + +function trimCoords(point: Point): Point; +``` + +In this case `Point` is both an input and output type. This means that the only changes we can make to the type that aren't breaking are the intersection of allowed changes between input and output types. In practice this only allows for the addition of new optional properties. Because of this constraint it is generally best to avoid using I/O types, and keep the input separated from the output. + +There are some cases where I/O types favor either input or output when it comes to API stability. For example, all types used by Utility APIs are I/O types, but the stability of the output is a lot more important than the stability of the input. That is because it's a lot easier for the single producer of the input interface to adapt to changes compared to all consumers of the API that use it as an output type. + +#### Identifying the Contract Direction + +The only way to identify the contract direction of a type is to look at the context in which it's being used. In particular this can be tricky when looking at individual type aliases and interfaces, as you need to look at the rest of the package exports to see how the type is being used. + +One important rule is that the context considered for any type is limited to only the package in which the type is declared. Just because a type is imported in a different package and used as an input type does not make it an input type. + +The following rules can be used to identify the direction of a type alias or interface: + +- If the type is used in an input context, for example function parameter, then it's an input type. +- If the type is used in an output context, for example function return type, then it's an output type. +- If the type is referenced by another type, then it inherits the direction of that type, except if referenced through a function callback, in which case the direction is reversed. +- If the type is used or inherits both input and output contexts, then it's an I/O type. +- If the type is not referenced anywhere else, then it's an I/O type. + +Below is an example of the public API of a package, with type directions assigned to each export: + +```ts +// I/O, used by getPoint as return type and referenced by BoxProps, an input type +interface Point { + x: number; + y: number; +} + +// Output, since it's an exported function +function getPoint(): Point; + +// Input, used by Box as parameter type +interface BoxProps { + point?: Point +} + +// Output, since it's an exported function +function Box(props: BoxProps): JSX.Element; + +// Output, used by createWidget as return type +interface Widget { + ... +} + +// Output, as it's referenced by WidgetOptions, which is an input +// type, but the render callback causes a direction reversal +interface WidgetProps { + ... +} + +// Input, just like WidgetProps this is due to the direction reversal +// caused by the render callback +type RenderedWidget = JSX.Element | null; + +// Input, used by createWidget parameter type +interface WidgetOptions { + render(props: WidgetProps): RenderedWidget; +} + +// Output, since it's an exported function +function createWidget(options: WidgetOptions): Widget; + +// I/O, since it's not referenced anywhere else +type LabelStyle = 'normal' | 'thin'; + +// Output, since it's an exported constant +const LABEL_SIZE: number; +``` diff --git a/STYLE.md b/STYLE.md index b28c5da4fa..87e0644759 100644 --- a/STYLE.md +++ b/STYLE.md @@ -11,8 +11,8 @@ Our TypeScript style is inspired by the [style guidelines](https://github.com/Mi 1. Use PascalCase for type names. 1. Do not use `I` as a prefix for interface names. 1. Use PascalCase for `enum` values. -1. Use camelCase for function names. -1. Use camelCase for property names and local variables. +1. Use `camelCase` for function names. +1. Use `camelCase` for property names and local variables. 1. Do not use `_` as a prefix for private properties. 1. Use whole words in names when possible. 1. Give type parameters names prefixed with `T`, for example `Request`. @@ -134,6 +134,30 @@ This section describes guidelines for designing public APIs. It can also be appl } ``` +1. When there is a significant number of arguments to a function or method, prefer to use a single options object as the argument, rather than many positional arguments. + + ```ts + // Bad + function createWidget(id: string, name: string, width: number) {} + + // Good + function createWidget(options: CreateWidgetOptions) {} + ``` + +1. Avoid arrays as return types; prefer response objects. + + ```ts + interface UserApi { + // Bad + // Can only return Users without signaling additional information such as pagination. + listUsers(): Promise; + + // Good + // Easy to evolve with additional fields. + listUsers(): Promise; + } + ``` + # Documentation Guidelines We use [API Extractor](https://api-extractor.com/pages/overview/demo_docs/) to generate our documentation, which in turn uses [TSDoc](https://github.com/microsoft/tsdoc) to parse our doc comments. diff --git a/app-config.yaml b/app-config.yaml index 57349d213a..69a1207365 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -216,7 +216,6 @@ catalog: - Component - API - Resource - - Template - System - Domain - Location @@ -271,6 +270,8 @@ catalog: # Backstage example templates - type: file target: ../../plugins/scaffolder-backend/sample-templates/all-templates.yaml + rules: + - allow: [Template] # Backstage end-to-end tests of TechDocs - type: file target: ../../cypress/e2e-fixture.catalog.info.yaml @@ -420,16 +421,6 @@ costInsights: kind: 'PINTS_OF_ICE_CREAM' unit: 'ice cream pint' rate: 5.5 -homepage: - clocks: - - label: UTC - timezone: UTC - - label: NYC - timezone: 'America/New_York' - - label: STO - timezone: 'Europe/Stockholm' - - label: TYO - timezone: 'Asia/Tokyo' pagerduty: eventsBaseUrl: 'https://events.pagerduty.com/v2' jenkins: diff --git a/contrib/docker/cookiecutter-with-jinja2-extensions/README.md b/contrib/docker/cookiecutter-with-jinja2-extensions/README.md index 0b754fa4ce..d51b46cb96 100644 --- a/contrib/docker/cookiecutter-with-jinja2-extensions/README.md +++ b/contrib/docker/cookiecutter-with-jinja2-extensions/README.md @@ -12,7 +12,7 @@ Using Cookiecutter extensions is a two-step process: This step depends on how the scaffolder is setup to use Cookiecutter: - Using a local Cookiecutter, or -- Using a Cookiecutter Docker image, e.g. [spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/37e35b91/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile). +- Using a Cookiecutter Docker image, e.g. [spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile). Say we want to install [`jinja2_custom_filters_extension`](https://pypi.org/project/jinja2-custom-filters-extension/) to use the `upper_case_first_letter` filter in a Cookiecutter template. diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md index c8f4083a0c..6c5fabd709 100644 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -75,7 +75,7 @@ export const createAuthMiddleware = async ( // Authorization header may be forwarded by plugin requests req.headers.authorization = `Bearer ${token}`; } - if (token && token !== req.cookies.token) { + if (token && token !== req.cookies?.token) { setTokenCookie(res, { token, secure, diff --git a/contrib/docs/tutorials/prometheus-metrics.md b/contrib/docs/tutorials/prometheus-metrics.md index 5b7dc61df3..56989e709c 100644 --- a/contrib/docs/tutorials/prometheus-metrics.md +++ b/contrib/docs/tutorials/prometheus-metrics.md @@ -106,3 +106,7 @@ There are some custom metrics that have been added to Backstage will be output f - `catalog_processing_duration_seconds`: Time spent executing the full processing flow - `catalog_processors_duration_seconds`: Time spent executing catalog processors - `catalog_processing_queue_delay_seconds`: The amount of delay between being scheduled for processing, and the start of actually being processed +- `scaffolder_task_count`: Tracks successful task runs. +- `scaffolder_task_duration`: a histogram which tracks the duration of a task run +- `scaffolder_step_count`: a count that tracks each step run +- `scaffolder_step_duration`: a histogram which tracks the duration of each step run diff --git a/contrib/scripts/orphan-clean-up/README.md b/contrib/scripts/orphan-clean-up/README.md index a6a25a4e7d..6fdf51bbbf 100644 --- a/contrib/scripts/orphan-clean-up/README.md +++ b/contrib/scripts/orphan-clean-up/README.md @@ -2,20 +2,47 @@ ## Overview -The Orphan Clean Up script is a basic PowerShell script to delete orphaned entities in the catalog. This script also assumes that you do not have authentication setup for your Backstage API endpoints. +The Orphan Clean Up scripts are a basic scripts to delete orphaned entities in the catalog. + +This script also assumes that you do not have authentication setup for your Backstage API endpoints. _Warning:_ There is a risk of entities being orphaned (and being deleted by this script) in case of the location having problems and returning a 404 status code. This might lead to accidental deletion of entities until the processing loop has recreated the entity. -## Requirements +## PowerShell + +A PowerShell implementation of the orphan cleanup script. + +### Requirements This script is PowerShell based so therefore needs to be ran in a PowerShell session. If you are not able to use PowerShell the script should give you a clear idea as to how to create it with another scripting language like Bash. -## Usage +### Usage Here's how to use the script: -1. Download the script +1. Download the `OrphanCleanUp.ps1` script 2. Now start a PowerShell session 3. Next navigate to the location you downloaded the script 4. Then run this command replacing `https:\\backstage.my-company.com` with the URL of your Backstage instance: `.\OrphanCleanUp.ps1 https:\\backstage.my-company.com` 5. The script will output the number of orphaned entities it finds and then the name for each one it deletes + +## Bash + +A bash implementation of the orphan cleanup script. + +### Requirements + +This script is shell based and requires the following programs to be installed on your system: + +- curl +- jq + +### Usage + +Here's how to use the script: + +1. Download the `orphan_cleanup.sh` script +2. Now start a bash session +3. Next navigate to the location you downloaded the script +4. Then run this command replacing `https://backstage.my-company.com` with the URL of your Backstage instance: `./orphan_cleanup.sh https://backstage.my-company.com` +5. The script will output the number of orphaned entities it finds and then the name for each one it deletes diff --git a/contrib/scripts/orphan-clean-up/orphan_cleanup.sh b/contrib/scripts/orphan-clean-up/orphan_cleanup.sh new file mode 100755 index 0000000000..6ffe4737fb --- /dev/null +++ b/contrib/scripts/orphan-clean-up/orphan_cleanup.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euo pipefail + +# Cleanes up orphaned entities for the provided Backstage URL, defaults to the local backend +BACKSTAGE_URL=${1:-'http://localhost:7007'} +echo $BACKSTAGE_URL + +ORPHAN_API_URL="$BACKSTAGE_URL/api/catalog/entities?filter=metadata.annotations.backstage.io/orphan=true" +ORPHAN_DELETE_API_URL="$BACKSTAGE_URL/api/catalog/entities/by-uid" + +ORPHANS=$(curl -s $ORPHAN_API_URL) + +echo "" +echo "Found $(echo $ORPHANS | jq length ) orphaned entities" +echo "" + +jq -c '.[]' <<< $ORPHANS | while read ORPHAN; do + echo $ORPHAN | jq "." + echo "Deleting orphan entity: $(echo $ORPHAN | jq -r .metadata.name) of kind: $(echo $ORPHAN | jq -r .kind)" + curl -X DELETE "$ORPHAN_DELETE_API_URL/$(echo $ORPHAN | jq -r .metadata.uid)" +done diff --git a/cypress/yarn.lock b/cypress/yarn.lock index d55c96a984..3b1358156e 100644 --- a/cypress/yarn.lock +++ b/cypress/yarn.lock @@ -414,8 +414,8 @@ __metadata: linkType: hard "cypress@npm:^10.0.0": - version: 10.8.0 - resolution: "cypress@npm:10.8.0" + version: 10.10.0 + resolution: "cypress@npm:10.10.0" dependencies: "@cypress/request": ^2.88.10 "@cypress/xvfb": ^1.2.4 @@ -461,7 +461,7 @@ __metadata: yauzl: ^2.10.0 bin: cypress: bin/cypress - checksum: c052690049980e7721e6fca563b724fde839d87d83c1478dfe26ce7d230992717c2c4028e7157bfb39ec274473e51929f49e5aab6a23c2b25cde2a439b1c3cf9 + checksum: 668a32534a527dba79754abbf98af176b80c539a12ec00058932ba2a19c794c7888323e59e738c30f726ad740c5451c31d02548a0cb7c1b1c8ad01c55a984ca2 languageName: node linkType: hard diff --git a/docs/api/backend.md b/docs/api/backend.md deleted file mode 100644 index 0990343a01..0000000000 --- a/docs/api/backend.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -id: backend -title: Backend -description: About Backend ---- - -## TODO diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md index 9ebcfc0945..017c5b699d 100644 --- a/docs/api/utility-apis.md +++ b/docs/api/utility-apis.md @@ -13,31 +13,31 @@ both with other plugins and the app itself. Backstage provides two primary methods for plugins to communicate across their boundaries in client-side code. The first one being the -[createPlugin](../reference/core-plugin-api.createplugin.md) API along with the +[`createPlugin`](../reference/core-plugin-api.createplugin.md) API along with the extensions that it can provide, and the second one being Utility APIs. While the -[createPlugin](../reference/core-plugin-api.createplugin.md) API is focused on +[`createPlugin`](../reference/core-plugin-api.createplugin.md) API is focused on the initialization plugins and the app, the Utility APIs provide ways for plugins to communicate during their entire life cycle. ## Consuming APIs -Each Utility API is tied to an [ApiRef](../reference/core-plugin-api.apiref.md) +Each Utility API is tied to an [`ApiRef`](../reference/core-plugin-api.apiref.md) instance, which is a global singleton object without any additional state or functionality, its only purpose is to reference Utility APIs. -[ApiRef](../reference/core-plugin-api.apiref.md)s are created using -[createApiRef](../reference/core-plugin-api.createapiref.md), which is exported -by [@backstage/core-plugin-api](../reference/core-plugin-api.md). There are also +[`ApiRef`](../reference/core-plugin-api.apiref.md)s are created using +[`createApiRef`](../reference/core-plugin-api.createapiref.md), which is exported +by [`@backstage/core-plugin-api`](../reference/core-plugin-api.md). There are also many predefined Utility APIs in -[@backstage/core-plugin-api](../reference/core-plugin-api.md), and they're all +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), and they're all exported with a name of the pattern `*ApiRef`, for example -[errorApiRef](../reference/core-plugin-api.errorapiref.md). +[`errorApiRef`](../reference/core-plugin-api.errorapiref.md). To access one of the Utility APIs inside a React component, use the -[useApi](../reference/core-plugin-api.useapi.md) hook exported by -[@backstage/core-plugin-api](../reference/core-plugin-api.md), or the -[withApis](../reference/core-plugin-api.withapis.md) HOC if you prefer class +[`useApi`](../reference/core-plugin-api.useapi.md) hook exported by +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), or the +[`withApis`](../reference/core-plugin-api.withapis.md) HOC if you prefer class components. For example, the -[ErrorApi](../reference/core-plugin-api.errorapi.md) can be accessed like this: +[`ErrorApi`](../reference/core-plugin-api.errorapi.md) can be accessed like this: ```tsx import React from 'react'; @@ -56,14 +56,14 @@ export const MyComponent = () => { ``` Note that there is no explicit type given for -[ErrorApi](../reference/core-plugin-api.errorapi.md). This is because the -[errorApiRef](../reference/core-plugin-api.errorapiref.md) has the type -embedded, and [useApi](../reference/core-plugin-api.useapi.md) is able to infer +[`ErrorApi`](../reference/core-plugin-api.errorapi.md). This is because the +[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) has the type +embedded, and [`useApi`](../reference/core-plugin-api.useapi.md) is able to infer the type. Also note that consuming Utility APIs is not limited to plugins, it can be done from any component inside Backstage, including the ones in -[@backstage/core-plugin-api](../reference/core-plugin-api.md). The only +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). The only requirement is that they are beneath the `AppProvider` in the react tree. ## Supplying APIs @@ -71,15 +71,15 @@ requirement is that they are beneath the `AppProvider` in the react tree. ### API Factories APIs are registered in the form of -[ApiFactories](../reference/core-plugin-api.apifactory.md), which encapsulate +[`ApiFactory`](../reference/core-plugin-api.apifactory.md) instances, which encapsulate the process of instantiating an API. It is a collection of three things: the -[ApiRef](../reference/core-plugin-api.apiref.md) of the API to instantiate, a +[`ApiRef`](../reference/core-plugin-api.apiref.md) of the API to instantiate, a list of all required dependencies, and a factory function that returns a new API instance. For example, this is the default -[ApiFactory](../reference/core-plugin-api.apifactory.md) for the -[ErrorApi](../reference/core-plugin-api.errorapi.md): +[`ApiFactory`](../reference/core-plugin-api.apifactory.md) for the +[`ErrorApi`](../reference/core-plugin-api.errorapi.md): ```ts createApiFactory({ @@ -93,25 +93,25 @@ createApiFactory({ }); ``` -In this example the [errorApiRef](../reference/core-plugin-api.errorapiref.md) +In this example the [`errorApiRef`](../reference/core-plugin-api.errorapiref.md) is our API, which encapsulates the -[ErrorApi](../reference/core-plugin-api.errorapi.md) type. The -[alertApiRef](../reference/core-plugin-api.alertapiref.md) is our single +[`ErrorApi`](../reference/core-plugin-api.errorapi.md) type. The +[`alertApiRef`](../reference/core-plugin-api.alertapiref.md) is our single dependency, which we give the name `alertApi`, and is then passed on to the factory function, which returns an implementation of the -[ErrorApi](../reference/core-plugin-api.errorapi.md). +[`ErrorApi`](../reference/core-plugin-api.errorapi.md). -The [createApiFactory](../reference/core-plugin-api.createapifactory.md) +The [`createApiFactory`](../reference/core-plugin-api.createapifactory.md) function is a thin wrapper that enables TypeScript type inference. You may notice that there are no type annotations in the above example, and that is because we're able to infer all types from the -[ApiRef](../reference/core-plugin-api.apiref.md)s. TypeScript will make sure +[`ApiRef`](../reference/core-plugin-api.apiref.md)s. TypeScript will make sure that the return value of the `factory` function matches the type embedded in -`api`'s [ApiRef](../reference/core-plugin-api.apiref.md), in this case the -[ErrorApi](../reference/core-plugin-api.errorapi.md). It will also match the +`api`'s [`ApiRef`](../reference/core-plugin-api.apiref.md), in this case the +[`ErrorApi`](../reference/core-plugin-api.errorapi.md). It will also match the types between the `deps` and the parameters of the `factory` function, again using the type embedded within the -[ApiRef](../reference/core-plugin-api.apiref.md)s. +[`ApiRef`](../reference/core-plugin-api.apiref.md)s. ## Registering API Factories @@ -123,13 +123,13 @@ app, and the app itself. Starting with the Backstage core library, it provides implementations for all of the core APIs. The core APIs are the ones exported by -[@backstage/core-plugin-api](../reference/core-plugin-api.md), such as the -[errorApiRef](../reference/core-plugin-api.errorapiref.md) and -[configApiRef](../reference/core-plugin-api.configapiref.md). +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md), such as the +[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) and +[`configApiRef`](../reference/core-plugin-api.configapiref.md). The core APIs are loaded for any app created with -[createApp](../reference/app-defaults.createapp.md) from -[@backstage/core-plugin-api](../reference/app-defaults.md), which means that +[`createApp`](../reference/app-defaults.createapp.md) from +[`@backstage/core-plugin-api`](../reference/app-defaults.md), which means that there is no step that needs to be taken to include these APIs in an app. ### Plugin APIs @@ -137,13 +137,13 @@ there is no step that needs to be taken to include these APIs in an app. In addition to the core APIs, plugins can define and export their own APIs. While doing so they should usually also provide default implementations of their own APIs, for example, the `catalog` plugin exports `catalogApiRef`, and also -supplies a default [ApiFactory](../reference/core-plugin-api.apifactory.md) of +supplies a default [`ApiFactory`](../reference/core-plugin-api.apifactory.md) of that API using the `CatalogClient`. There is one restriction to plugin-provided API Factories: plugins may not supply factories for core APIs, trying to do so will cause the app to refuse to start. Plugins supply their APIs through the `apis` option of -[createPlugin](../reference/core-plugin-api.createplugin.md), for example: +[`createPlugin`](../reference/core-plugin-api.createplugin.md), for example: ```ts export const techdocsPlugin = createPlugin({ @@ -168,7 +168,7 @@ Lastly, the app itself is the final point where APIs can be added, and what has the final say in what APIs will be loaded at runtime. The app may override the factories for any of the core or plugin APIs, with the exception of the config, app theme, and identity APIs. These are static APIs that are tied into the -[createApp](../reference/app-defaults.createapp.md) implementation, and +[`createApp`](../reference/app-defaults.createapp.md) implementation, and therefore not possible to override. Overriding APIs is useful for apps that want to switch out behavior to tailor it @@ -231,19 +231,19 @@ const app = createApp({ ``` Note that the above line will cause an error if `IgnoreErrorApi` does not fully -implement the [ErrorApi](../reference/core-plugin-api.errorapi.md), as it is +implement the [`ErrorApi`](../reference/core-plugin-api.errorapi.md), as it is checked by the type embedded in the -[errorApiRef](../reference/core-plugin-api.errorapiref.md) at compile time. +[`errorApiRef`](../reference/core-plugin-api.errorapiref.md) at compile time. ## Defining custom Utility APIs Plugins are free to define their own Utility APIs. Simply define the TypeScript interface for the API, and create an -[ApiRef](../reference/core-plugin-api.apiref.md) using -[createApiRef](../reference/core-plugin-api.createapiref.md) exported from -[@backstage/core-plugin-api](../reference/core-plugin-api.md). Also be sure to +[`ApiRef`](../reference/core-plugin-api.apiref.md) using +[`createApiRef`](../reference/core-plugin-api.createapiref.md) exported from +[`@backstage/core-plugin-api`](../reference/core-plugin-api.md). Also be sure to provide at least one implementation of the API, and to declare a default factory -for the API in [createPlugin](../reference/core-plugin-api.createplugin.md). +for the API in [`createPlugin`](../reference/core-plugin-api.createplugin.md). Custom Utility APIs can be either public or private, which is up to the plugin to choose. Private APIs do not expose an external API surface, and it's @@ -255,16 +255,16 @@ backwards compatibility of public APIs, as you may otherwise break apps that are using your plugin. To make an API public, simply export the -[ApiRef](../reference/core-plugin-api.apiref.md) of the API, and any associated +[`ApiRef`](../reference/core-plugin-api.apiref.md) of the API, and any associated types. To make an API private, just avoid exporting the -[ApiRef](../reference/core-plugin-api.apiref.md), but still be sure to supply a -default factory to [createPlugin](../reference/core-plugin-api.createplugin.md). +[`ApiRef`](../reference/core-plugin-api.apiref.md), but still be sure to supply a +default factory to [`createPlugin`](../reference/core-plugin-api.createplugin.md). Private APIs are useful for plugins that want to depend on other APIs outside of React components, but not have to expose an entire API surface to maintain. When using private APIs, it is fine to use the `typeof` of an implementing class as the type parameter passed to -[createApiRef](../reference/core-plugin-api.createapiref.md), while public APIs +[`createApiRef`](../reference/core-plugin-api.createapiref.md), while public APIs should always define a separate TypeScript interface type. Plugins may depend on APIs from other plugins, both in React components and as @@ -273,13 +273,13 @@ dependencies between plugins. ## Architecture -The [ApiRef](../reference/core-plugin-api.apiref.md) instances mentioned above +The [`ApiRef`](../reference/core-plugin-api.apiref.md) instances mentioned above provide a point of indirection between consumers and producers of Utility APIs. It allows for plugins and components to depend on APIs in a type-safe way, without having a direct reference to a concrete implementation of the APIs. The Apps are also given a lot of flexibility in what implementations to provide. As long as they adhere to the contract established by an -[ApiRef](../reference/core-plugin-api.apiref.md), they are free to choose any +[`ApiRef`](../reference/core-plugin-api.apiref.md), they are free to choose any implementation they want. The figure below shows the relationship between @@ -304,16 +304,16 @@ The indirection provided by Utility APIs also makes it straightforward to test components that depend on APIs, and to provide a standard common development environment for plugins. A proper test wrapper with mocked API implementations is not yet ready, but it will be provided as a part of -[@backstage/test-utils](../reference/test-utils.md). It will provide mocked +[`@backstage/test-utils`](../reference/test-utils.md). It will provide mocked variants of APIs, with additional methods for asserting a component's interaction with the API. The common development environment for plugins is included in -[@backstage/dev-utils](../reference/dev-utils.md), where the exported -[createDevApp](../reference/dev-utils.createdevapp.md) function creates an +[`@backstage/dev-utils`](../reference/dev-utils.md), where the exported +[`createDevApp`](../reference/dev-utils.createdevapp.md) function creates an application with implementations for all core APIs already present. Contrary to the method for wiring up Utility API implementations in an app created with -[createApp](../reference/app-defaults.createapp.md), -[createDevApp](../reference/dev-utils.createdevapp.md) uses automatic dependency +[`createApp`](../reference/app-defaults.createapp.md), +[`createDevApp`](../reference/dev-utils.createdevapp.md) uses automatic dependency injection. This is to make it possible to replace any API implementation, and having that be reflected in dependents of that API. diff --git a/docs/architecture-decisions/adr003-avoid-default-exports.md b/docs/architecture-decisions/adr003-avoid-default-exports.md index 34e5cd5411..8ab79a451f 100644 --- a/docs/architecture-decisions/adr003-avoid-default-exports.md +++ b/docs/architecture-decisions/adr003-avoid-default-exports.md @@ -15,7 +15,7 @@ thing well". The module would be consumed (`const localName = require('the-module');`) without having to know the internal structure. -Now, ESModules are the primary authoring format. They have numerous benefits, +Now, `ESModules` are the primary authoring format. They have numerous benefits, such as compile-time verification of exports, and standards-defined semantics. They have a similar mechanism known as "default exports", which allows for a consumer to `import localName from 'the-module';`. This is implicitly the same diff --git a/docs/architecture-decisions/index.md b/docs/architecture-decisions/index.md index 15d1b0e9d5..615d737fe1 100644 --- a/docs/architecture-decisions/index.md +++ b/docs/architecture-decisions/index.md @@ -34,5 +34,5 @@ Records should be stored under the `architecture-decisions` directory. ## Superseding an ADR -If an ADR supersedes an older ADR then the older ADR's status is changed to -superseded by ADR-XXXX and links to the new ADR. +If an ADR supersedes an older ADR then the status of the older ADR is changed to +"superseded by ADR-XXXX", and links to the new ADR. diff --git a/docs/assets/getting-started/b-scaffold-1.png b/docs/assets/getting-started/b-scaffold-1.png index 094b104b7c..d32010ae91 100644 Binary files a/docs/assets/getting-started/b-scaffold-1.png and b/docs/assets/getting-started/b-scaffold-1.png differ diff --git a/docs/assets/getting-started/b-scaffold-2.png b/docs/assets/getting-started/b-scaffold-2.png index db66b83a26..2e8374451b 100644 Binary files a/docs/assets/getting-started/b-scaffold-2.png and b/docs/assets/getting-started/b-scaffold-2.png differ diff --git a/docs/assets/getting-started/b-scaffold-3.png b/docs/assets/getting-started/b-scaffold-3.png deleted file mode 100644 index b98ec2d8e3..0000000000 Binary files a/docs/assets/getting-started/b-scaffold-3.png and /dev/null differ diff --git a/docs/assets/getting-started/portal.png b/docs/assets/getting-started/portal.png index f16ad44757..b3133f2278 100644 Binary files a/docs/assets/getting-started/portal.png and b/docs/assets/getting-started/portal.png differ diff --git a/docs/assets/getting-started/wizard.png b/docs/assets/getting-started/wizard.png index 679878e252..8b6dce6131 100644 Binary files a/docs/assets/getting-started/wizard.png and b/docs/assets/getting-started/wizard.png differ diff --git a/docs/auth/gitlab/provider.md b/docs/auth/gitlab/provider.md index 90939a1f03..15488bc4c1 100644 --- a/docs/auth/gitlab/provider.md +++ b/docs/auth/gitlab/provider.md @@ -18,7 +18,7 @@ Settings for local development: - Name: Backstage (or your custom app name) - Redirect URI: `http://localhost:7007/api/auth/gitlab/handler/frame` -- Scopes: read_user +- Scopes: `read_user` ## Configuration @@ -35,6 +35,8 @@ auth: clientSecret: ${AUTH_GITLAB_CLIENT_SECRET} ## uncomment if using self-hosted GitLab # audience: https://gitlab.company.com + ## uncomment if using a custom redirect URI + # callbackUrl: https://${BASE_URL}/api/auth/gitlab/handler/frame ``` The GitLab provider is a structure with three configuration keys: @@ -44,6 +46,9 @@ The GitLab provider is a structure with three configuration keys: - `clientSecret`: The Application secret - `audience` (optional): The base URL for the self-hosted GitLab instance, e.g. `https://gitlab.company.com` +- `callbackUrl` (optional): The URL matching the Redirect URI registered when creating your GitLab OAuth App, e.g. + `https://$backstage.acme.corp/api/auth/gitlab/handler/frame` + Note: Due to a peculiarity with GitLab OAuth, ensure there is no trailing `/` after 'frame' in the URL. ## Adding the provider to the Backstage frontend diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md index ef79127223..cbd24867b7 100644 --- a/docs/auth/microsoft/provider.md +++ b/docs/auth/microsoft/provider.md @@ -46,6 +46,12 @@ The Microsoft provider is a structure with three configuration keys: - `clientSecret`: Secret, found on App Registration > Certificates & secrets - `tenantId`: Directory (tenant) ID, found on App Registration > Overview +In order to finish signing a user in from Azure, the Backstage backend must +fetch their information from graph.microsoft.com (as seen in [this source +code](https://github.com/seanfisher/passport-microsoft/blob/0456aa9bce05579c18e77f51330176eb26373658/lib/strategy.js#L93-L95)), +so ensure that your Backstage backend has connectivity to this host. +Otherwise users may see an `Authentication failed, failed to fetch user profile` error when they attempt to log in. + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `microsoftAuthApiRef` reference and diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index f856a2a415..d915262eb7 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -243,8 +243,8 @@ need this as well) to support user sessions. ### The Sign In provider The last step is to add the provider to the `SignInPage` so users can sign in with your -new provider, please follow the [Sing In Configuration][3] docs, here's where you import -and use the API ref we defined earlier. +new provider, please follow the [Sign In Configuration][3] docs, here's where you import +and use the API reference we defined earlier. ## Note diff --git a/docs/conf/defining.md b/docs/conf/defining.md index 29964dbcbc..9e13442f7b 100644 --- a/docs/conf/defining.md +++ b/docs/conf/defining.md @@ -138,7 +138,7 @@ may need to pass in all files using one or multiple `--config ` options. > to change for different deployment environments should be static > configuration, while it should otherwise be avoided. -When defining configuration for your plugin, keep keys camelCased and stick to +When defining configuration for your plugin, keep keys on `camelCase` form and stick to existing casing conventions such as `baseUrl` rather than `baseURL`. It is also usually best to prefer objects over arrays, as it makes it possible diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index d35e7dd593..04a509d316 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -61,13 +61,18 @@ FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. USER node + +# This should create the app dir as `node`. +# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. +# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. WORKDIR /app # This switches many Node.js dependencies to production mode. @@ -79,7 +84,8 @@ ENV NODE_ENV production COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz -RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --production --network-timeout 300000 # Then copy the rest of the backend bundle, along with any other files we might want. COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ @@ -163,53 +169,68 @@ RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf { # Stage 2 - Install dependencies and build packages FROM node:16-bullseye-slim AS build -WORKDIR /app -COPY --from=packages /app . - # install sqlite3 dependencies -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ yarn config set python /usr/bin/python3 -RUN yarn install --frozen-lockfile --network-timeout 600000 && rm -rf "$(yarn cache dir)" +USER node +WORKDIR /app -COPY . . +COPY --from=packages --chown=node:node /app . + +# Stop cypress from downloading it's massive binary. +ENV CYPRESS_INSTALL_BINARY=0 +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --network-timeout 600000 + +COPY --chown=node:node . . RUN yarn tsc RUN yarn --cwd packages/backend build # If you have not yet migrated to package roles, use the following command instead: # RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies +RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \ + && tar xzf packages/backend/dist/skeleton.tar.gz -C packages/backend/dist/skeleton \ + && tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle + # Stage 3 - Build the actual backend image and install production dependencies FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. USER node + +# This should create the app dir as `node`. +# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. +# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. WORKDIR /app -# This switches many Node.js dependencies to production mode. -ENV NODE_ENV production - # Copy the install dependencies from the build stage and context -COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton.tar.gz ./ -RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz +COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton/ ./ -RUN yarn install --frozen-lockfile --production --network-timeout 600000 && rm -rf "$(yarn cache dir)" +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --production --network-timeout 600000 # Copy the built packages from the build stage -COPY --from=build --chown=node:node /app/packages/backend/dist/bundle.tar.gz . -RUN tar xzf bundle.tar.gz && rm bundle.tar.gz +COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./ # Copy any other files that we need at runtime COPY --chown=node:node app-config.yaml ./ +# This switches many Node.js dependencies to production mode. +ENV NODE_ENV production + CMD ["node", "packages/backend", "--config", "app-config.yaml"] ``` @@ -225,6 +246,7 @@ however ignore any existing build output or dependencies on the host. For our new `.dockerignore`, replace the contents of your existing one with this: ```text +dist-types node_modules packages/*/dist packages/*/node_modules diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 1dd684db5f..55843f3b24 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -62,7 +62,7 @@ This is an array used to determine where to retrieve cluster configuration from. Valid cluster locator methods are: - [`catalog`](#catalog) -- [`localKubectlProxy`](#localKubectlProxy) +- [`localKubectlProxy`](#localkubectlproxy) - [`config`](#config) - [`gke`](#gke) - [custom `KubernetesClustersSupplier`](#custom-kubernetesclusterssupplier) @@ -217,11 +217,11 @@ for some dashboards, such as GKE. ###### required parameters for GKE -| Name | Description | -| ----------- | ------------------------------------------------------------------------ | -| projectId | the ID of the GCP project containing your Kubernetes clusters | -| region | the region of GCP containing your Kubernetes clusters | -| clusterName | the name of your kubernetes cluster, within your `projectId` GCP project | +| Name | Description | +| ------------- | ------------------------------------------------------------------------ | +| `projectId` | the ID of the GCP project containing your Kubernetes clusters | +| `region` | the region of GCP containing your Kubernetes clusters | +| `clusterName` | the name of your kubernetes cluster, within your `projectId` GCP project | Note that the GKE cluster locator can automatically provide the values for the `dashboardApp` and `dashboardParameters` options if you set the diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 46f28dcb01..0a4f5ee400 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -110,7 +110,7 @@ metadata: backstage.io/edit-url: https://github.com/my-org/catalog/edit/master/my-service.jsonnet ``` -These annotations allow customising links from the catalog pages. The view URL +These annotations allow customizing links from the catalog pages. The view URL should point to the canonical metadata YAML that governs this entity. The edit URL should point to the source file for the metadata. In the example above, `my-org` generates its catalog data from Jsonnet files in a monorepo, so the @@ -159,11 +159,11 @@ metadata: github.com/project-slug: backstage/backstage ``` -The value of this annotation is the so-called slug that identifies a project on +The value of this annotation is the so-called slug that identifies a repository on [GitHub](https://github.com) (either the public one, or a private GitHub Enterprise installation) that is related to this entity. It is on the format -`/`, and is the same as can be seen in the URL location -bar of the browser when viewing that project. +`/`, and is the same as can be seen in the URL location +bar of the browser when viewing that repository. Specifying this annotation will enable GitHub related features in Backstage for that entity. diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md index afef2c3372..b4c4d2d4ce 100644 --- a/docs/features/software-templates/writing-custom-field-extensions.md +++ b/docs/features/software-templates/writing-custom-field-extensions.md @@ -81,8 +81,7 @@ import { scaffolderPlugin, createScaffolderFieldExtension, } from '@backstage/plugin-scaffolder'; -import { MyCustomExtension } from './MyCustomExtension'; -import { myCustomValidation } from './validation'; +import { MyCustomExtension, myCustomValidation } from './MyCustomExtension'; export const MyCustomFieldExtension = scaffolderPlugin.provide( createScaffolderFieldExtension({ diff --git a/docs/features/techdocs/how-to-guides.md b/docs/features/techdocs/how-to-guides.md index 4f2dc6c348..70ea27821b 100644 --- a/docs/features/techdocs/how-to-guides.md +++ b/docs/features/techdocs/how-to-guides.md @@ -111,7 +111,7 @@ in Backstage. While a default table experience, similar to the one provided by the Catalog plugin, is made available for ease-of-use, it's possible for you to provide a completely custom experience, tailored to the needs of your organization. For example, TechDocs comes with an alternative grid based layout -(``). +(``) and panel layout (`TechDocsCustomHome`). This is done in your `app` package. By default, you might see something like this in your `App.tsx`: @@ -126,18 +126,120 @@ const AppRoutes = () => { }; ``` +### Using TechDocsCustomHome + +You can easily customize the TechDocs home page using TechDocs panel layout +(``). + +Modify your `App.tsx` as follows: + +```tsx +import { TechDocsCustomHome } from '@backstage/plugin-techdocs'; +//... + +const techDocsTabsConfig = [ + { + label: "Recommended Documentation", + panels: [ + { + title: 'Golden Path', + description: 'Documentation about standards to follow', + panelType: 'DocsCardGrid', + filterPredicate: entity => entity?.metadata?.tags?.includes('recommended') ?? false, + } + ] + } +] + +const AppRoutes = () => { + + }> + ; +}; +``` + +### Building a Custom home page + But you can replace `` with any React component, which will be rendered in its place. Most likely, you would want to create and maintain such a component in a new directory at `packages/app/src/components/techdocs`, and import and use it in `App.tsx`: +For example, you can define the following Custom home page component: + +```tsx +import React from 'react'; + +import { Content } from '@backstage/core-components'; +import { + CatalogFilterLayout, + EntityOwnerPicker, + EntityTagPicker, + UserListPicker, + EntityListProvider, +} from '@backstage/plugin-catalog-react'; +import { + TechDocsPageWrapper, + TechDocsPicker, +} from '@backstage/plugin-techdocs'; +import { Entity } from '@backstage/catalog-model'; + +import { + EntityListDocsGrid, + DocsGroupConfig, +} from '@backstage/plugin-techdocs'; + +export type CustomTechDocsHomeProps = { + groups?: Array<{ + title: React.ReactNode; + filterPredicate: (entity: Entity) => boolean; + }>; +}; + +export const CustomTechDocsHome = ({ groups }: CustomTechDocsHomeProps) => { + return ( + + + + + + + + + + + + + + + + + + ); +}; +``` + +Then you can add the following to your `App.tsx`: + ```tsx import { CustomTechDocsHome } from './components/techdocs/CustomTechDocsHome'; // ... const AppRoutes = () => { }> - + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: 'My Docs', + filterPredicate: 'ownedByUser', + }, + ]} + /> ; }; @@ -437,7 +539,7 @@ FROM python:3.8-alpine RUN apk update && apk --no-cache add gcc musl-dev openjdk11-jdk curl graphviz ttf-dejavu fontconfig -RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==1.0.1 +RUN pip install --upgrade pip && pip install mkdocs-techdocs-core==1.1.7 RUN pip install mkdocs-kroki-plugin diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index 64109385a0..146f160d71 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -6,7 +6,7 @@ description: Documentation on Customizing look and feel of the App Backstage ships with a default theme with a light and dark mode variant. The themes are provided as a part of the -[@backstage/theme](https://www.npmjs.com/package/@backstage/theme) package, +[`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) package, which also includes utilities for customizing the default theme, or creating completely new themes. @@ -14,7 +14,7 @@ completely new themes. The easiest way to create a new theme is to use the `createTheme` function exported by the -[@backstage/theme](https://www.npmjs.com/package/@backstage/theme) package. You +[`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) package. You can use it to override some basic parameters of the default theme such as the color palette and font. @@ -33,16 +33,16 @@ const myTheme = createTheme({ If you want more control over the theme, and for example customize font sizes and margins, you can use the lower-level `createThemeOverrides` function -exported by [@backstage/theme](https://www.npmjs.com/package/@backstage/theme) +exported by [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme) in combination with -[createTheme](https://material-ui.com/customization/theming/#createmuitheme-options-args-theme) -from [@material-ui/core](https://www.npmjs.com/package/@material-ui/core). See +[`createTheme`](https://material-ui.com/customization/theming/#createmuitheme-options-args-theme) +from [`@material-ui/core`](https://www.npmjs.com/package/@material-ui/core). See the "Overriding Backstage and Material UI css rules" section below. You can also create a theme from scratch that matches the `BackstageTheme` type -exported by [@backstage/theme](https://www.npmjs.com/package/@backstage/theme). +exported by [`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme). See the -[material-ui docs on theming](https://material-ui.com/customization/theming/) +[Material-UI docs on theming](https://material-ui.com/customization/theming/) for more information about how that can be done. ## Using your Custom Theme @@ -79,7 +79,7 @@ const app = createApp({ Note that your list of custom themes overrides the default themes. If you still want to use the default themes, they are exported as `lightTheme` and `darkTheme` from -[@backstage/theme](https://www.npmjs.com/package/@backstage/theme). +[`@backstage/theme`](https://www.npmjs.com/package/@backstage/theme). ## Example of a custom theme diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index 9ce65294ad..fc5b02e6f4 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -284,27 +284,25 @@ otherwise something went terribly wrong. ## Create a new component using a software template -- Go to `create` and choose to create a website with the `React SSR Template` -- Type in a name, let's use `tutorial` -- Select the group `team-a` which will own this new website, and go to the next - step - +- Go to `create` and choose to create a website with the `Example Node.js Template` +- Type in a name, let's use `tutorial` and click `Next Step`

- Software template deployment input screen asking for a name, the group owning this, and a description + Software template deployment input screen asking for a name

-- For the location, we're going to use the default -- As owner, type your GitHub username -- For the repository name, type `tutorial`. Go to the next step - +- You should see the following screen:

Software template deployment input screen asking for the GitHub username, and name of the new repo to create

+- For host, it should default to github.com +- As owner, type your GitHub username +- For the repository name, type `tutorial`. Go to the next step + - Review the details of this new service, and press `Create` if you want to deploy it like this. - - You can follow along with the progress, and as soon as every step is - finished, you can take a look at your new service +- You can follow along with the progress, and as soon as every step is + finished, you can take a look at your new service Achievement unlocked. You've set up an installation of the core Backstage App, made it persistent, and configured it so you are now able to use software diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index ade73d3f68..477467bb88 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -51,14 +51,10 @@ create a subdirectory inside your current working directory. npx @backstage/create-app ``` -The wizard will ask you - -- The name of the app, which will also be the name of the directory -- The database type to use for the backend. For this guide, you'll be using the - SQLite option. +The wizard will ask you for the name of the app, which will also be the name of the directory

- Screenshot of the wizard asking for a name for the app, and a selection menu for the database. + Screenshot of the wizard asking for a name for the app.

### Run the Backstage app diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index 9e9e6ebede..748aeb2392 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -32,6 +32,11 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } ``` For simple setups, you can omit the provider ID at the config @@ -47,6 +52,11 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } ``` As this provider is not one of the default providers, you will first need to install @@ -69,10 +79,13 @@ const builder = await CatalogBuilder.create(env); builder.addEntityProvider( AwsS3EntityProvider.fromConfig(env.config, { logger: env.logger, + // optional: alternatively, use scheduler with schedule defined in app-config.yaml schedule: env.scheduler.createScheduledTaskRunner({ frequency: { minutes: 30 }, timeout: { minutes: 3 }, }), + // optional: alternatively, use schedule + scheduler: env.scheduler, }), ); ``` diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 6a420462c0..2156f60814 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -42,12 +42,17 @@ catalog: project: myproject repository: service-* # this will match all repos starting with service-* path: /catalog-info.yaml + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } anotherProviderId: # another identifier organization: myorg project: myproject repository: '*' # this will match all repos path: /src/*/catalog-info.yaml # this will search for files deep inside the /src folder - yetAotherProviderId: # guess, what? Another one :) + yetAnotherProviderId: # guess, what? Another one :) host: selfhostedazure.yourcompany.com organization: myorg project: myproject @@ -55,11 +60,20 @@ catalog: The parameters available are: -- `host:` Leave empty for Cloud hosted, otherwise set to your self-hosted instance host. -- `organization:` Your Organization slug (or Collection for on-premise users). Required. -- `project:` Your project slug. Required. -- `repository:` The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched. -- `path:` Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml. +- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host. +- **`organization:`** Your Organization slug (or Collection for on-premise users). Required. +- **`project:`** Your project slug. Required. +- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched. +- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml. +- **`schedule`** _(optional)_: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. _Note:_ the path parameter follows the same rules as the search on Azure DevOps web interface. For more details visit the @@ -84,10 +98,13 @@ const builder = await CatalogBuilder.create(env); +builder.addEntityProvider( + AzureDevOpsEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ // optional: alternatively, use scheduler with schedule defined in app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ -+ frequency: Duration.fromObject({ minutes: 30 }), -+ timeout: Duration.fromObject({ minutes: 3 }), ++ frequency: { minutes: 30 }, ++ timeout: { minutes: 3 }, + }), ++ // optional: alternatively, use schedule ++ scheduler: env.scheduler, + }), +); ``` diff --git a/docs/integrations/bitbucket/discovery.md b/docs/integrations/bitbucket/discovery.md deleted file mode 100644 index 15ede69a4a..0000000000 --- a/docs/integrations/bitbucket/discovery.md +++ /dev/null @@ -1,174 +0,0 @@ ---- -id: discovery -title: Bitbucket Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from repositories in Bitbucket ---- - -The Bitbucket integration has a special discovery processor for discovering -catalog entities located in Bitbucket. The processor will crawl your Bitbucket -account and register entities matching the configured path. This can be useful -as an alternative to static locations or manually adding things to the catalog. - -## Installation - -You will have to add the processor in the catalog initialization code of your -backend. The provider is not installed by default, therefore you have to add a -dependency to `@backstage/plugin-catalog-backend-module-bitbucket` to your backend -package. - -```bash -# From your Backstage root directory -yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-bitbucket -``` - -And then add the processor to your catalog builder: - -```diff -// In packages/backend/src/plugins/catalog.ts -+import { BitbucketDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-bitbucket'; - - export default async function createPlugin( - env: PluginEnvironment, - ): Promise { - const builder = await CatalogBuilder.create(env); -+ builder.addProcessor( -+ BitbucketDiscoveryProcessor.fromConfig(env.config, { logger: env.logger }) -+ ); -``` - -## Self-hosted Bitbucket Server - -To use the discovery processor with a self-hosted Bitbucket Server, you'll need -a Bitbucket integration [set up](../bitbucketServer/locations.md) with a `BITBUCKET_TOKEN` and a -`BITBUCKET_API_BASE_URL`. Then you can add a location target to the catalog -configuration: - -```yaml -catalog: - locations: - - type: bitbucket-discovery - target: https://bitbucket.mycompany.com/projects/my-project/repos/service-*/catalog-info.yaml -``` - -Note the `bitbucket-discovery` type, as this is not a regular `url` processor. - -The target is composed of four parts: - -- The base instance URL, `https://bitbucket.mycompany.com` in this case -- The project key to scan, which accepts \* wildcard tokens. This can simply be - `*` to scan repositories from all projects. This example only scans for - repositories in the `my-project` project. -- The repository blob to scan, which accepts \* wildcard tokens. This can simply - be `*` to scan all repositories in the project. This example only looks for - repositories prefixed with `service-`. -- The path within each repository to find the catalog YAML file. This will - usually be `/catalog-info.yaml` or a similar variation for catalog files - stored in the root directory of each repository. If omitted, the default value - `catalog-info.yaml` will be used. E.g. given that `my-project`and `service-a` - exists, `https://bitbucket.mycompany.com/projects/my-project/repos/service-*/` - will result in: - `https://bitbucket.mycompany.com/projects/my-project/repos/service-a/catalog-info.yaml`. - -## Bitbucket Cloud - -To use the discovery processor with Bitbucket Cloud, you'll need a Bitbucket -integration [set up](../bitbucketCloud/locations.md) with a `username` and an `appPassword`. Then -you can add a location target to the catalog configuration: - -```yaml -catalog: - locations: - - type: bitbucket-discovery - target: https://bitbucket.org/workspaces/my-workspace -``` - -Note the `bitbucket-discovery` type, as this is not a regular `url` processor. - -The target is composed of the following parts: - -- The base URL for Bitbucket, `https://bitbucket.org` -- The workspace name to scan (following the `workspaces/` path part), which must - match a workspace accessible with the username of your integration. -- (Optional) The project key to scan (following the `projects/` path part), - which accepts \* wildcard tokens. If omitted, repositories from all projects - in the workspace are included. -- (Optional) The repository blob to scan (following the `repos/` path part), - which accepts \* wildcard tokens. If omitted, all repositories in the - workspace are included. -- (Optional) The `catalogPath` query argument to specify the location within - each repository to find the catalog YAML file. This will usually be - `/catalog-info.yaml` or a similar variation for catalog files stored in the - root directory of each repository. If omitted, the default value - `catalog-info.yaml` will be used. -- (Optional) The `q` query argument to be passed through to Bitbucket for - filtering results via the API. This is the most flexible option and will - reduce the amount of API calls if you have a large workspace. - [See here for the specification](https://developer.atlassian.com/bitbucket/api/2/reference/meta/filtering) - for the query argument (will be passed as the `q` query parameter). -- (Optional) The `search=true` query argument to activate the mode utilizing code search. - - Is mutually exclusive to the `q` query argument. - - Allows providing values at `catalogPath` for finding catalog files as allowed by the `path` filter/modifier - [at Bitbucket Cloud's code search](https://confluence.atlassian.com/bitbucket/code-search-in-bitbucket-873876782.html#Search-Pathmodifier). - - `catalogPath=/catalog-info.yaml` - - `catalogPath=catalog-info.yaml` (anywhere in the repository) - - `catalogPath=/path/catalog-info.yaml` - - `catalogPath=path/catalog-info.yaml` - - `catalogPath=/path/*/catalog-info.yaml` - - `catalogPath=path/*/catalog-info.yaml` - - Supports multiple catalog files per repository depending on the `catalogPath` value. - - Registers `Location` entities for existing files only vs all matching repositories. - -Examples: - -- `https://bitbucket.org/workspaces/my-workspace/projects/my-project` will find - all repositories in the `my-project` project in the `my-workspace` workspace. -- `https://bitbucket.org/workspaces/my-workspace/repos/service-*` will find all - repositories starting with `service-` in the `my-workspace` workspace. -- `https://bitbucket.org/workspaces/my-workspace/projects/apis-*/repos/service-*` - will find all repositories starting with `service-`, in all projects starting - with `apis-` in the `my-workspace` workspace. -- `https://bitbucket.org/workspaces/my-workspace?q=project.key ~ "my-project"` - will find all repositories in a project containing `my-project` in its key. -- `https://bitbucket.org/workspaces/my-workspace?catalogPath=my/nested/path/catalog.yaml` - will find all repositories in the `my-workspace` workspace and use the catalog - file at `my/nested/path/catalog.yaml`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=/catalog.yaml` - will find all `catalog.yaml` files located in the root of repositories in the workspace `my-workspace`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=catalog.yaml` - will find all `catalog.yaml` files located anywhere within repositories in the workspace `my-workspace`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=/my/nested/path/catalog.yaml` - will find all `catalog.yaml` files located within the directory `/my/nested/path/` within - repositories in the workspace `my-workspace`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=my/nested/path/catalog.yaml` - will find all `catalog.yaml` files located within the directory `my/nested/path/` located anywhere within - repositories in the workspace `my-workspace`. -- `https://bitbucket.org/workspaces/my-workspace?search=true&catalogPath=/my/*/path/catalog.yaml` - will find all `catalog.yaml` files located within a directory `path/` located within any (recursive) directory - within the directory `my/` in the root of repositories in the workspace `my-workspace` - (`/my/nested/path/catalog.yaml`, `/my/very/nested/path/catalog.yaml`, ...). -- `https://bitbucket.org/workspaces/my-workspace/projects/apis-*/repos/service-*?search=true&catalogPath=catalog.yaml` - will find all `catalog.yaml` files located anywhere within repositories starting with `service-` - in projects starting with `api-` in the workspace `my-workspace`. - -## Custom repository processing - -The Bitbucket Discovery Processor will by default emit a location for each -matching repository for further processing by other processors. However, it is -possible to override this functionality and take full control of how each -matching repository is processed. - -`BitbucketDiscoveryProcessor.fromConfig` takes an optional parameter -`options.parser` where you can set your own parser to be used for each matched -repository. - -```typescript -const processor = BitbucketDiscoveryProcessor.fromConfig(env.config, { - parser: async function* customRepositoryParser({ client, repository }) { - // Custom logic for interpreting the matching repository. - // See defaultRepositoryParser for an example - }, - logger: env.logger, -}); -``` diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index c5a17bb2c9..9b9bc4ecde 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -102,9 +102,3 @@ catalog: - **`workspace`**: Name of your organization account/workspace. If you want to add multiple workspaces, you need to add one provider config each. - -## Alternative - -_Deprecated!_ Please raise issues for use cases not covered by the entity provider. - -[You can use the `BitbucketDiscoveryProcessor`.](../bitbucket/discovery.md#bitbucket-cloud) diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 57e52d1dae..db1cc43ea4 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -37,10 +37,13 @@ And then add the entity provider to your catalog builder: + builder.addEntityProvider( + BitbucketServerEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ // optional: alternatively, use scheduler with schedule defined in app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, + }), ++ // optional: alternatively, use schedule ++ scheduler: env.scheduler, + }), + ); @@ -66,19 +69,33 @@ catalog: filters: # optional projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } ``` -- **host**: +- **`host`**: The host of the Bitbucket Server instance, **note**: the host needs to registered as an integration as well, see [location](locations.md). - **`catalogPath`** _(optional)_: Default: `/catalog-info.yaml`. Path where to look for `catalog-info.yaml` files. When started with `/`, it is an absolute path from the repo root. -- **filters** _(optional)_: +- **`filters`** _(optional)_: - **`projectKey`** _(optional)_: Regular expression used to filter results based on the project key. - - **repoSlug** _(optional)_: + - **`repoSlug`** _(optional)_: Regular expression used to filter results based on the repo slug. +- **`schedule`** _(optional)_: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. ## Custom location processing @@ -93,22 +110,13 @@ repository. ```typescript const provider = BitbucketServerEntityProvider.fromConfig(env.config, { logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), + schedule: env.scheduler, parser: async function* customLocationParser(options: { - location: LocationSpec, - client: BitbucketServerClient, + location: LocationSpec; + client: BitbucketServerClient; }) { // Custom logic for interpreting the matching repository // See defaultBitbucketServerLocationParser for an example - } -); + }, +}); ``` - -## Alternative - -_Deprecated!_ Please raise issues for use cases not covered by the entity provider. - -[You can use the `BitbucketDiscoveryProcessor`.](../bitbucket/discovery.md#self-hosted-bitbucket-server) diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md index 04fa3dac2a..7ed05d2b3b 100644 --- a/docs/integrations/gerrit/discovery.md +++ b/docs/integrations/gerrit/discovery.md @@ -32,10 +32,13 @@ const builder = await CatalogBuilder.create(env); builder.addEntityProvider( GerritEntityProvider.fromConfig(env.config, { logger: env.logger, + // optional: alternatively, use scheduler with schedule defined in app-config.yaml schedule: env.scheduler.createScheduledTaskRunner({ frequency: { minutes: 30 }, timeout: { minutes: 3 }, }), + // optional: alternatively, use schedule + scheduler: env.scheduler, }), ); ``` @@ -54,6 +57,11 @@ catalog: host: gerrit-your-company.com branch: master # Optional query: 'state=ACTIVE&prefix=webapps' + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } backend: host: gerrit-your-company.com branch: master # Optional @@ -62,8 +70,8 @@ catalog: The provider configuration is composed of three parts: -- host, the host of the Gerrit integration to use. -- branch, the branch where we will look for catalog entities (defaults to "master"). -- query, this string is directly used as the argument to the "List Project" API. - Typically you will want to have some filter here to exclude projects that will +- **`host`**: the host of the Gerrit integration to use. +- **`branch`** _(optional)_: the branch where we will look for catalog entities (defaults to "master"). +- **`query`**: this string is directly used as the argument to the "List Project" API. + Typically, you will want to have some filter here to exclude projects that will never contain any catalog files. diff --git a/docs/integrations/gerrit/locations.md b/docs/integrations/gerrit/locations.md index 9bff2385e3..08dad94ecf 100644 --- a/docs/integrations/gerrit/locations.md +++ b/docs/integrations/gerrit/locations.md @@ -38,7 +38,7 @@ a structure with up to six elements: not set. The address used to clone a repo is the `cloneUrl` plus the repo name. - `gitilesBaseUrl` (optional): This is needed for creating a valid user-friendly URL that can be used for browsing the content of the provider. If not set a default - value will be created in the same way as the "baseUrl" option. There is no + value will be created in the same way as the `baseUrl` option. There is no requirement to have Gitiles for the Backstage Gerrit integration but without it some links in the Backstage UI will be broken. - `username` (optional): The Gerrit username to use in API requests. If diff --git a/docs/integrations/gitea/locations.md b/docs/integrations/gitea/locations.md new file mode 100644 index 0000000000..c58703da3a --- /dev/null +++ b/docs/integrations/gitea/locations.md @@ -0,0 +1,38 @@ +--- +id: locations +title: Gitea Locations +sidebar_label: Locations +description: Integrating source code stored in Gitea into the Backstage catalog +--- + +The Gitea integration supports loading catalog entities from a hosted repository. Entities can be added to +[static catalog configuration](../../features/software-catalog/configuration.md), +registered with the +[catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) +plugin. + +## Configuration + +To use this integration, add configuration to your root `app-config.yaml`: + +```yaml +integrations: + gitea: + - host: gitea.example.com + password: ${GITEA_TOKEN} + - host: gitea.example.com + username: ${GITEA_USERNAME} + password: ${GITEA_PASSWORD} +``` + +Directly under the `gitea` key is a list of provider configurations, where you +can list the Gitea instances you want to be able to fetch +data from. Each entry is a structure with up to four elements: + +- `host`: The host of the gitea instance that you want to match on. +- `baseUrl` (optional): Needed if the Gitea instance is not reachable at + the base of the `host` option (e.g. `https://git.company.com/gitea`). This is the address that you would open in a browser. +- `username` (optional): The gitea username to use in API requests. +- `password` (optional): The password or api token to authenticate with. + +You may supply only the `password` field, if authenticating via API access tokens (generated in Settings > Applications). diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 9b60b41682..4f032901a9 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -30,19 +30,22 @@ And then add the entity provider to your catalog builder: ```diff // In packages/backend/src/plugins/catalog.ts -+ import { GitHubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; ++ import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; export default async function createPlugin( env: PluginEnvironment, ): Promise { const builder = await CatalogBuilder.create(env); + builder.addEntityProvider( -+ GitHubEntityProvider.fromConfig(env.config, { ++ GithubEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ // optional: alternatively, use scheduler with schedule defined in app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, + }), ++ // optional: alternatively, use schedule ++ scheduler: env.scheduler, + }), + ); @@ -68,6 +71,11 @@ catalog: filters: branch: 'main' # string repository: '.*' # Regex + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } customProviderId: organization: 'new-org' # string catalogPath: '/custom/path/catalog-info.yaml' # string @@ -96,6 +104,13 @@ catalog: topic: include: ['backstage-include'] # optional array of strings exclude: ['experiments'] # optional array of strings + validateLocationsExist: + organization: 'backstage' # string + catalogPath: '/catalog-info.yaml' # string + filters: + branch: 'main' # string + repository: '.*' # Regex + validateLocationsExist: true # optional boolean enterpriseProviderId: host: ghe.example.net organization: 'backstage' # string @@ -110,27 +125,43 @@ This provider supports multiple organizations via unique provider IDs. - **`catalogPath`** _(optional)_: Default: `/catalog-info.yaml`. Path where to look for `catalog-info.yaml` files. - You can use wildcards - `*` or `**` - to search the path and/or the filename -- **filters** _(optional)_: - - **branch** _(optional)_: + You can use wildcards - `*` or `**` - to search the path and/or the filename. + Wildcards cannot be used if the `validateLocationsExist` option is set to `true`. +- **`filters`** _(optional)_: + - **`branch`** _(optional)_: String used to filter results based on the branch name. - - **repository** _(optional)_: + - **`repository`** _(optional)_: Regular expression used to filter results based on the repository name. - - **topic** _(optional)_: + - **`topic`** _(optional)_: Both of the filters below may be used at the same time but the exclusion filter has the highest priority. In the example above, a repository with the `backstage-include` topic would still be excluded if it were also carrying the `experiments` topic. - - **include** _(optional)_: + - **`include`** _(optional)_: An array of strings used to filter in results based on their associated GitHub topics. If configured, only repositories with one (or more) topic(s) present in the inclusion filter will be ingested - - **exclude** _(optional)_: + - **`exclude`** _(optional)_: An array of strings used to filter out results based on their associated GitHub topics. If configured, all repositories _except_ those with one (or more) topics(s) present in the exclusion filter will be ingested. -- **organization**: +- **`host`** _(optional)_: + The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). +- **`organization`**: Name of your organization account/workspace. If you want to add multiple organizations, you need to add one provider config each. -- **host** _(optional)_: - The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). +- **`validateLocationsExist`** _(optional)_: + Whether to validate locations that exist before emitting them. + This option avoids generating locations for catalog info files that do not exist in the source repository. + Defaults to `false`. + Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in + conjunction with wildcards in the `catalogPath`. +- **`schedule`** _(optional)_: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. ## GitHub API Rate Limits diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index df5aadcab4..dc0b911b3d 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -25,6 +25,11 @@ catalog: group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole project will be scanned entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` projectPattern: /[\s\S]*/ # Optional. Filters found projects based on provided patter. Defaults to `/[\s\S]*/`, what means to not filter anything + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } ``` As this provider is not one of the default providers, you will first need to install @@ -47,10 +52,13 @@ const builder = await CatalogBuilder.create(env); builder.addEntityProvider( ...GitlabDiscoveryEntityProvider.fromConfig(env.config, { logger: env.logger, + // optional: alternatively, use scheduler with schedule defined in app-config.yaml schedule: env.scheduler.createScheduledTaskRunner({ frequency: { minutes: 30 }, timeout: { minutes: 3 }, }), + // optional: alternatively, use schedule + scheduler: env.scheduler, }), ); ``` diff --git a/docs/integrations/index.md b/docs/integrations/index.md index 94f18daa09..b090d6d77e 100644 --- a/docs/integrations/index.md +++ b/docs/integrations/index.md @@ -16,17 +16,13 @@ integrations are used by many Backstage core features and other plugins. Each key under `integrations` is a separate configuration for a single external provider. Providers each have different configuration; here's an example of -configuration to use both GitHub and Bitbucket: +configuration to use GitHub: ```yaml integrations: github: - host: github.com token: ${GITHUB_TOKEN} - bitbucket: - - host: bitbucket.org - username: ${BITBUCKET_USERNAME} - appPassword: ${BITBUCKET_APP_PASSWORD} ``` See documentation for each type of integration for full details on diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index 1408d732d3..d82836b9ae 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -11,15 +11,21 @@ For some use cases, you may want to define custom [rules](./concepts.md#resource Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule in `packages/backend/src/plugins/permission.ts`, but you can put it anywhere that's accessible by your `backend` package. ```typescript -import type { Entity } from '@backstage/plugin-catalog-model'; +import type { Entity } from '@backstage/catalog-model'; import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; import { createConditionFactory } from '@backstage/plugin-permission-node'; +import { z } from 'zod'; export const isInSystemRule = createCatalogPermissionRule({ name: 'IS_IN_SYSTEM', description: 'Checks if an entity is part of the system provided', resourceType: 'catalog-entity', - apply: (resource: Entity, systemRef: string) => { + paramsSchema: z.object({ + systemRef: z + .string() + .describe('SystemRef to check the resource is part of'), + }), + apply: (resource: Entity, { systemRef }) => { if (!resource.relations) { return false; } @@ -28,9 +34,9 @@ export const isInSystemRule = createCatalogPermissionRule({ .filter(relation => relation.type === 'partOf') .some(relation => relation.targetRef === systemRef); }, - toQuery: (systemRef: string) => ({ + toQuery: ({ systemRef }) => ({ key: 'relations.partOf', - value: systemRef, + values: [systemRef], }), }); @@ -85,14 +91,14 @@ class TestPermissionPolicy implements PermissionPolicy { if (isResourcePermission(request.permission, 'catalog-entity')) { return createCatalogConditionalDecision( request.permission, -- catalogConditions.isEntityOwner( -- user?.identity.ownershipEntityRefs ?? [], -- ), +- catalogConditions.isEntityOwner({ +- claims: user?.identity.ownershipEntityRefs ?? [], +- }), + { + anyOf: [ -+ catalogConditions.isEntityOwner( -+ user?.identity.ownershipEntityRefs ?? [] -+ ), ++ catalogConditions.isEntityOwner({ ++ claims: user?.identity.ownershipEntityRefs ?? [] ++ }), + isInSystem('interviewing') + ] + } diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index 6e6bcd2d03..d2c467ff08 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -53,7 +53,6 @@ $ yarn workspace backend add @backstage/plugin-permission-backend 2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. ```typescript -import { IdentityClient } from '@backstage/plugin-auth-node'; import { createRouter } from '@backstage/plugin-permission-backend'; import { AuthorizeResult, @@ -119,7 +118,6 @@ permission: 2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity: ```diff - import { IdentityClient } from '@backstage/plugin-auth-node'; import { createRouter } from '@backstage/plugin-permission-backend'; import { AuthorizeResult, diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 124605cb66..241b175987 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -48,7 +48,9 @@ Edit `plugins/todo-list-backend/src/service/router.ts`: ... - import { InputError } from '@backstage/errors'; +- import { IdentityApi } from '@backstage/plugin-auth-node'; + import { InputError, NotAllowedError } from '@backstage/errors'; ++ import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node'; + import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common'; + import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; @@ -56,7 +58,7 @@ Edit `plugins/todo-list-backend/src/service/router.ts`: export interface RouterOptions { logger: Logger; - identity: IdentityClient; + identity: IdentityApi; + permissions: PermissionEvaluator; } @@ -69,11 +71,13 @@ Edit `plugins/todo-list-backend/src/service/router.ts`: ... router.post('/todos', async (req, res) => { - const token = IdentityClient.getBearerToken(req.header('authorization')); let author: string | undefined = undefined; - const user = token ? await identity.authenticate(token) : undefined; + const user = await identity.getIdentity({ request: req }); author = user?.identity.userEntityRef; ++ const token = getBearerTokenFromAuthorizationHeader( ++ req.header('authorization'), ++ ); + const decision = ( + await permissions.authorize([{ permission: todoListCreatePermission }], { + token, @@ -128,10 +132,8 @@ In order to test the logic above, the integrators of your backstage instance nee ```diff // packages/backend/src/plugins/permission.ts -- import { IdentityClient } from '@backstage/plugin-auth-node'; + import { + BackstageIdentityResponse, -+ IdentityClient + } from '@backstage/plugin-auth-node'; import { PermissionPolicy, @@ -170,3 +172,117 @@ Let's flip the result back to `ALLOW` before moving on. }; } ``` + +At this point everything is working but if you run `yarn tsc` you'll get some errors, let's fix those up. + +First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: + +```diff + import { getVoidLogger } from '@backstage/backend-common'; + import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; ++ import { PermissionEvaluator } from '@backstage/plugin-permission-common'; + import express from 'express'; + import request from 'supertest'; + + import { createRouter } from './router'; + ++ const mockedAuthorize: jest.MockedFunction = ++ jest.fn(); ++ const mockedPermissionQuery: jest.MockedFunction< ++ PermissionEvaluator['authorizeConditional'] ++ > = jest.fn(); + ++ const permissionEvaluator: PermissionEvaluator = { ++ authorize: mockedAuthorize, ++ authorizeConditional: mockedPermissionQuery, ++ }; + + describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + identity: {} as DefaultIdentityClient, ++ permissions: toPermissionEvaluator, + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + }); + }); + +``` + +Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`, first we need to add the `@backstage/plugin-permission-node` package to `plugins/todo-list-backend/package.json` and then we can make the following edits: + +```diff + import { + createServiceBuilder, + loadBackendConfig, + SingleHostDiscovery, ++ ServerTokenManager, + } from '@backstage/backend-common'; + import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; + import { ServerPermissionClient } from '@backstage/plugin-permission-node'; + import { Server } from 'http'; + import { Logger } from 'winston'; + import { createRouter } from './router'; + + export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; + } + + export async function startStandaloneServer( + options: ServerOptions, + ): Promise { + const logger = options.logger.child({ service: 'todo-list-backend' }); + logger.debug('Starting application server...'); + const config = await loadBackendConfig({ logger, argv: process.argv }); + const discovery = SingleHostDiscovery.fromConfig(config); ++ const tokenManager = ServerTokenManager.fromConfig(config, { ++ logger, ++ }); ++ const permissions = ServerPermissionClient.fromConfig(config, { ++ discovery, ++ tokenManager, ++ }); + const router = await createRouter({ + logger, + identity: DefaultIdentityClient.create({ + discovery, + issuer: await discovery.getExternalBaseUrl('auth'), + }), ++ permissions, + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/todo-list', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); + } + + module.hot?.accept(); +``` + +Now when you run `yarn tsc` you should have no more errors. diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index 5bace9ad7b..8913b6d438 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -84,6 +84,7 @@ Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append th ```typescript import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; +import { z } from 'zod'; import { Todo, TodoFilter } from './todos'; export const createTodoListPermissionRule = makeCreatePermissionRule< @@ -95,10 +96,13 @@ export const isOwner = createTodoListPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the todo belongs to the user', resourceType: TODO_LIST_RESOURCE_TYPE, - apply: (resource: Todo, userId: string) => { + paramsSchema: z.object({ + userId: z.string().describe('User ID to match on the resource') + }) + apply: (resource: Todo, { userId }) => { return resource.author === userId; }, - toQuery: (userId: string) => { + toQuery: ({ userId }) => { return { property: 'author', values: [userId], @@ -223,7 +227,9 @@ Let's go back to the permission policy's handle function and try to authorize ou + if (isPermission(request.permission, todoListUpdatePermission)) { + return createTodoListConditionalDecision( + request.permission, -+ todoListConditions.isOwner(user?.identity.userEntityRef), ++ todoListConditions.isOwner({ ++ userId: user?.identity.userEntityRef ?? '', ++ }), + ); + } + diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index c813ba594c..8ddcffdcd9 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -144,7 +144,9 @@ import { + ) { return createTodoListConditionalDecision( request.permission, - todoListConditions.isOwner(user?.identity.userEntityRef), + todoListConditions.isOwner({ + userId: user?.identity.userEntityRef + }), ); } ``` diff --git a/docs/permissions/plugin-authors/05-frontend-authorization.md b/docs/permissions/plugin-authors/05-frontend-authorization.md index dc4df7839c..d494ea60ba 100644 --- a/docs/permissions/plugin-authors/05-frontend-authorization.md +++ b/docs/permissions/plugin-authors/05-frontend-authorization.md @@ -118,7 +118,7 @@ Providing a disabled state can be a helpful signal to users, but there may be ca - - - -+ ++ }> + + + @@ -165,3 +165,25 @@ Providing a disabled state can be a helpful signal to users, but there may be ca ``` Now you should find that the component for adding a todo list item does not render at all. Success! + +You can also use `RequirePermission` to prevent access to routes as well. Here's how that would look in your `packages/app/src/App.tsx`: + +```diff ++ import { RequirePermission } from '@backstage/plugin-permission-react'; ++ import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; + +... + + }> + {searchPage} + + } /> ++ ++ ++ + +``` + +Now if you try to navigate to `https://localhost:3000/todo-list` you'll get and error page if you do not have permission. diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md index c29424241c..7e741ae48f 100644 --- a/docs/permissions/writing-a-policy.md +++ b/docs/permissions/writing-a-policy.md @@ -70,9 +70,9 @@ Let's change the policy to the following: - }; + return createCatalogConditionalDecision( + request.permission, -+ catalogConditions.isEntityOwner( -+ user?.identity.ownershipEntityRefs ?? [], -+ ), ++ catalogConditions.isEntityOwner({ ++ claims: user?.identity.ownershipEntityRefs ?? [], ++ }), + ); } @@ -119,9 +119,9 @@ class TestPermissionPolicy implements PermissionPolicy { + if (isResourcePermission(request.permission, 'catalog-entity')) { return createCatalogConditionalDecision( request.permission, - catalogConditions.isEntityOwner( - user?.identity.ownershipEntityRefs ?? [], - ), + catalogConditions.isEntityOwner({ + claims: user?.identity.ownershipEntityRefs ?? [], + }), ); } diff --git a/docs/plugins/add-to-marketplace.md b/docs/plugins/add-to-marketplace.md index 790512e6aa..e4281d9b3c 100644 --- a/docs/plugins/add-to-marketplace.md +++ b/docs/plugins/add-to-marketplace.md @@ -23,4 +23,5 @@ iconUrl: # Used as the src attribute for your logo. # You can provide an external url or add your logo under static/img and provide a path # relative to static/ e.g. img/my-logo.png npmPackageName: # Your npm package name E.g. '@backstage/plugin-' quotes are required +addedDate: # The date plugin added to marketplace E.g. '2022-10-01' quotes are required ``` diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md index ccb4e5c5a7..46e1d354df 100644 --- a/docs/plugins/analytics.md +++ b/docs/plugins/analytics.md @@ -52,12 +52,13 @@ learn how to contribute the integration yourself! The following table summarizes events that, depending on the plugins you have installed, may be captured. -| Action | Subject | Other Notes | -| ---------- | --------------------------------------------------- | ----------------------------------------------------------------- | -| `navigate` | The URL of the page that was navigated to | | -| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to | -| `search` | The search term entered in any search bar component | The `searchTypes` attribute holds `types` constraining the search | -| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided | +| Action | Subject | Other Notes | +| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------- | +| `navigate` | The URL of the page that was navigated to | | +| `click` | The text of the link that was clicked on | The `to` attribute represents the URL clicked to | +| `create` | The `name` of the software being created; if no `name` property is requested by the given Software Template, then the string `new {templateName}` is used instead. | The context holds an `entityRef`, set to the template's ref (e.g. `template:default/template-name`) | +| `search` | The search term entered in any search bar component | The context holds `searchTypes`, representing `types` constraining the search | +| `discover` | The title of the search result that was clicked on | The `value` is the result rank. A `to` attribute is also provided | If there is an event you'd like to see captured, please [open an issue][add-event] describing the event you want to see and the questions it @@ -301,11 +302,11 @@ it's important to keep each of these levels of detail disaggregated. automatically as part of the `extension` in which the `filter` event was captured). -- On the flip side, when adding `attributes` to an event, look at existing - events and see if the data you are capturing matches the intention, type, or - even the content of _their_ `attributes`. For instance, it may be common for - events that involve the Catalog to add details like entity `name`, `kind`, - and/or `namespace` as `attributes`. Using the same keys in your event will +- On the flip side, when adding `attributes` to or `context` around an event, + look at existing events and see if the data you are capturing matches the + intention, type, or even the content of _their_ `attributes` or `context`. + For instance, it's common for events that involve the Catalog to include an + `entityRef` contextual key. Using the same keys and values in your event will ensure that events instrumented across plugins can easily be aggregated. ### Unit Testing Event Capture diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index 2ec9d7a6e5..1c64fd3f68 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -196,14 +196,14 @@ const App = () => ( There are a couple of naming patterns to adhere to as you build plugins, which helps clarify the intent and usage of the exports. -| Description | Pattern | Examples | -| --------------------- | --------------- | ---------------------------------------------- | -| Top-level Pages | \*Page | CatalogIndexPage, SettingsPage, LighthousePage | -| Entity Tab Content | Entity\*Content | EntityJenkinsContent, EntityKubernetesContent | -| Entity Overview Card | Entity\*Card | EntitySentryCard, EntityPagerDutyCard | -| Entity Conditional | is\*Available | isPagerDutyAvailable, isJenkinsAvailable | -| Plugin Instance | \*Plugin | jenkinsPlugin, catalogPlugin | -| Utility API Reference | \*ApiRef | configApiRef, catalogApiRef | +| Description | Pattern | Examples | +| --------------------- | ----------------- | ---------------------------------------------------- | +| Top-level Pages | `\*Page` | `CatalogIndexPage`, `SettingsPage`, `LighthousePage` | +| Entity Tab Content | `Entity\*Content` | `EntityJenkinsContent`, `EntityKubernetesContent` | +| Entity Overview Card | `Entity\*Card` | `EntitySentryCard`, `EntityPagerDutyCard` | +| Entity Conditional | `is\*Available` | `isPagerDutyAvailable`, `isJenkinsAvailable` | +| Plugin Instance | `\*Plugin` | `jenkinsPlugin`, `catalogPlugin` | +| Utility API Reference | `\*ApiRef` | `configApiRef`, `catalogApiRef` | ### Routing System @@ -515,10 +515,10 @@ deprecated while making the new additions, to then be removed at a later point. Many export naming patterns have been changed to avoid import aliases and to clarify intent. Refer to the following table to formulate the new name: -| Description | Existing Pattern | New Pattern | Examples | -| -------------------- | -------------------------- | --------------- | ---------------------------------------------- | -| Top-level Pages | Router | \*Page | CatalogIndexPage, SettingsPage, LighthousePage | -| Entity Tab Content | Router | Entity\*Content | EntityJenkinsContent, EntityKubernetesContent | -| Entity Overview Card | \*Card | Entity\*Card | EntitySentryCard, EntityPagerDutyCard | -| Entity Conditional | isPluginApplicableToEntity | is\*Available | isPagerDutyAvailable, isJenkinsAvailable | -| Plugin Instance | plugin | \*Plugin | jenkinsPlugin, catalogPlugin | +| Description | Existing Pattern | New Pattern | Examples | +| -------------------- | ---------------------------- | ----------------- | ---------------------------------------------------- | +| Top-level Pages | `Router` | `\*Page` | `CatalogIndexPage`, `SettingsPage`, `LighthousePage` | +| Entity Tab Content | `Router` | `Entity\*Content` | `EntityJenkinsContent`, `EntityKubernetesContent` | +| Entity Overview Card | `\*Card` | `Entity\*Card` | `EntitySentryCard`, `EntityPagerDutyCard` | +| Entity Conditional | `isPluginApplicableToEntity` | `is\*Available` | `isPagerDutyAvailable`, `isJenkinsAvailable` | +| Plugin Instance | `plugin` | `\*Plugin` | `jenkinsPlugin`, `catalogPlugin` | diff --git a/docs/plugins/new-backend-system.md b/docs/plugins/new-backend-system.md new file mode 100644 index 0000000000..e5c318d5c2 --- /dev/null +++ b/docs/plugins/new-backend-system.md @@ -0,0 +1,305 @@ +--- +id: new-backend-system +title: New Backend System +description: Details of the upcoming backend system +--- + +> **DISCLAIMER: The new backend system is under active development and is not considered stable** + +## Status + +The new backend system is under active development, and only a small number of plugins and services have been migrated so far. It is possible to try it out, but it is not recommended to use this new system in production yet. + +You can find an example backend setup at https://github.com/backstage/backstage/tree/master/packages/backend-next. + +## Overview + +The new Backstage backend system is being built to help make it simpler to install backend plugins and keep projects up to date. It also changes the foundation to one that makes it a lot easier to evolve plugins and the system itself. You can read more about the reasoning in the [original RFC](https://github.com/backstage/backstage/issues/11611). + +One of the goals of the new system was to reduce the code needed for setting up a Backstage backend and installing plugins. This is an example of how you create, add features, and start up your backend in the new system: + +```ts +import { createBackend } from '@backstage/backend-defaults'; +import { catalogPlugin } from '@backstage/plugin-catalog-backend'; + +// Create your backend instance +const backend = createBackend(); + +// Install all desired features +backend.add(catalogPlugin()); + +// Start up the backend +await backend.start(); +``` + +One notable change that helped achieve this much slimmer backend setup is the introduction of dependency injection, with a system that is very similar to the one in the Backstage frontend. + +## Building Blocks + +This section introduces the high-level building blocks upon which this new system is built. These are all concepts that exist in our current system in one way or another, but the have all been lifted up to be first class concerns in the new system. + +### Backend + +This is the backend instance itself, which you can think of as the unit of deployment. It does not have any functionality in itself, but is simply responsible for wiring things together. + +It is up to you to decide how many different backends you want to deploy. You can have all features in a single one, or split things out into multiple smaller deployments. All depending on your need to scale and isolate individual features. + +### Plugins + +Plugins provide the actual features, just like in our existing system. They operate completely independently of each other. If plugins what to communicate with each other, they must do so over the wire. There can be no direct communication between plugins through code. Because of this constraints, each plugins can be considered to be its own microservice. + +### Services + +Services provide utilities to help make it simpler to implement plugins, so that each plugin doesn't need to implement everything from scratch. There are both many built-in services, like the ones for logging, database access, and reading configuration, but you can also import third-party services, or create your own. + +Services are also a customization point for individual backend installations. You can both override services with your own implementations, as well as make smaller customizations to existing services. + +### Extension Points + +Many plugins have ways in which you can extend them, for example entity providers for the Catalog, or custom actions for the Scaffolder. These extension patterns are now encoded into Extension Points. + +Extension Points look a little bit like services, since you depended on them just like you would a service. A key difference is that extension points are registered and provided by plugins themselves, based on what customizations each individual plugin wants to expose. + +Extension Points are also exported separately from the plugin instance itself, and a single plugin can also expose multiple different extension points at once. This makes it easier to evolve and deprecated individual Extension Points over time, rather than dealing with a single large API surface. + +### Modules + +Modules use the plugin Extension Points to add new features for plugins. They might for example add an individual Catalog Entity Provider, or one or more Scaffolder Actions. Modules are basically plugins for plugins. + +Each module may only extend a single plugin, and the module must be deployed together with that plugin in the same backend instance. Modules may however only communicate with their plugin through its registered extension points. + +Just like plugins, modules also have access to services and can depend on their own service implementations. They will however share services with the plugin that they extend, there are no module-specific service implementations. + +## Creating Plugins + +Plugins are created using the `createBackendPlugin` function. All plugins must have an ID and a register method. Plugins may also accept an options object, which can be either optional or required. The options are passed to the second parameter of the register method, and the options type is inferred and forwarded to the returned plugin factory function. + +```ts +import { + configServiceRef, + createBackendPlugin, +} from '@backstage/backend-plugin-api'; + +// export type ExamplePluginOptions = { exampleOption: boolean }; +export const examplePlugin = createBackendPlugin({ + // unique id for the plugin + id: 'example', + // It's possible to provide options to the plugin + // register(env, options: ExamplePluginOptions) { + register(env) { + env.registerInit({ + deps: { + logger: loggerServiceRef, + }, + // logger is provided by the backend based on the dependency on loggerServiceRef above. + async init({ logger }) { + logger.info('Hello from example plugin'); + }, + }); + }, +}); +``` + +The plugin can then be installed in the backend using the returned plugin factory function: + +```ts +backend.add(examplePlugin()); +``` + +If we wanted our plugin to accept options as well, we'd accept the options as the second parameter of the register method: + +```ts +export const examplePlugin = createBackendPlugin({ + id: 'example', + register(env, options?: { silent?: boolean }) { + env.registerInit({ + deps: { logger: loggerServiceRef }, + async init({ logger }) { + if (!options?.silent) { + logger.info('Hello from example plugin'); + } + }, + }); + }, +}); +``` + +Passing the option to the plugin during installation looks like this: + +```ts +backend.add(examplePlugin({ silent: true })); +``` + +## Creating Modules + +Some facts about modules + +- A Module is able to extend a plugin with additional functionality using the `ExtensionPoint`s registered by the plugin. +- A module can only extend one plugin but can interact with multiple `ExtensionPoint`s registered by that plugin. +- A module is always initialized before the plugin it extends. + +A module depends on the `ExtensionPoint`s exported by the target plugin's library package, for example `@backstage/plugin-catalog-node`, and does not directly declare a dependency on the plugin package itself. + +Here's an example on how to create a module that adds a new processor using the `catalogProcessingExtensionPoint`: + +```ts +import { createBackendModule } from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { MyCustomProcessor } from './processor'; + +export const exampleCustomProcessorCatalogModule = createBackendModule({ + moduleId: 'exampleCustomProcessor', + pluginId: 'catalog', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + }, + async init({ catalog }) { + catalog.addProcessor(new MyCustomProcessor()); + }, + }); + }, +}); +``` + +### Extension Points + +Modules depend on extension points just as a regular dependency by specifying it in the `deps` section. + +#### Defining an Extension Point + +```ts +import { createExtensionPoint } from '@backstage/backend-plugin-api'; + +export interface ScaffolderActionsExtensionPoint { + addAction(action: ScaffolderAction): void; +} + +export const scaffolderActionsExtensionPoint = + createExtensionPoint({ + id: 'scaffolder.actions', + }); +``` + +#### Registering an Extension Point + +Extension points are registered by a plugin and extended by modules. + +## Backend Services + +The default backend provides several _services_ out of the box which includes access to configuration, logging, databases and more. +Service dependencies are declared using their `ServiceRef`s in the `deps` section of the plugin or module, and the implementations are then forwarded to the `init` method of the plugin or module. + +### Service References + +A `ServiceRef` is a named reference to an interface which are later used to resolve the concrete service implementation. Conceptually this is very similar to `ApiRef`s in the frontend. +Services is what provides common utilities that previously resided in the `PluginEnvironment` such as Config, Logging and Database. + +On startup the backend will make sure that the services are initialized before being passed to the plugin/module that depend on them. +ServiceRefs contain a scope which is used to determine if the serviceFactory creating the service will create a new instance scoped per plugin/module or if it will be shared. `plugin` scoped services will be created once per plugin/module and `root` scoped services will be created once per backend instance. + +#### Defining a Service + +```ts +import { + createServiceFactory, + pluginMetadataServiceRef, + loggerServiceRef, +} from '@backstage/backend-plugin-api'; +import { ExampleImpl } from './ExampleImpl'; + +export interface ExampleApi { + doSomething(): Promise; +} + +export const exampleServiceRef = createServiceRef({ + id: 'example', + scope: 'plugin', // can be 'root' or 'plugin' + + // The defaultFactory is optional to implement but it will be used if no other factory is provided to the backend. + // This is allows for the backend to provide a default implementation of the service without having to wire it beforehand. + defaultFactory: async service => + createServiceFactory({ + service, + deps: { + logger: loggerServiceRef, + plugin: pluginMetadataServiceRef, + }, + // Logger is available directly in the factory as it's a root scoped service and will be created once per backend instance. + async factory({ logger }) { + // plugin is available as it's a plugin scoped service and will be created once per plugin. + return async ({ plugin }) => { + // This block will be executed once for every plugin that depends on this service + logger.info('Initializing example service plugin instance'); + return new ExampleImpl({ logger, plugin }); + }; + }, + }), +}); +``` + +### Overriding Services + +In this example we replace the default root logger service implementation with a custom one that streams logs to GCP. The `rootLoggerServiceRef` has a `'root'` scope, meaning there are no plugin-specific instances of this service. + +```ts +import { + createServiceFactory, + rootLoggerServiceRef, + LoggerService, +} from '@backstage/backend-plugin-api'; + +// This custom implementation would typically live separately from +// the backend setup code, either nearby such as in +// packages/backend/src/services/logger/GoogleCloudLogger.ts +// Or you can let it live in its own library package. +class GoogleCloudLogger implements LoggerService { + static factory = createServiceFactory({ + service: rootLoggerServiceRef, + deps: {}, + async factory() { + return new GoogleCloudLogger(); + }, + }); + // custom implementation here ... +} + +// packages/backend/src/index.ts +const backend = createBackend({ + services: [ + // supplies additional or replacement services to the backend + GoogleCloudLogger.factory(), + ], +}); +``` + +## Testing + +Utilities for testing backend plugins and modules are available in `@backstage/backend-test-utils`. + +```ts +import { startTestBackend } from '@backstage/backend-test-utils'; + +describe('Example', () => { + it('should do something', async () => { + await startTestBackend({ + // mock services can be provided to the backend + services: [someServiceFactory], + // plugins and modules for testing + features: [testModule()], + }); + + // assertions + }); +}); +``` + +## Package structure + +A detailed explanation of the package architecture can be found in the [Backstage Architecture Overview](../overview/architecture-overview.md#package-architecture). The most important packages to consider for this system are `backend`, `plugin--backend`, `plugin--node`, and `plugin--backend-module-`. + +- `plugin--backend` houses the implementation of the plugins themselves. +- `plugin--node` houses the extension points and any other utilities that modules or other plugins might need. +- `plugin--backend-module-` houses the modules that extend the plugins via the extension points. +- `backend` is the backend itself that wires everything together to something that you can deploy. diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md index 9e8b67710a..c71b27b448 100644 --- a/docs/plugins/structure-of-a-plugin.md +++ b/docs/plugins/structure-of-a-plugin.md @@ -83,7 +83,7 @@ export const ExamplePage = examplePlugin.provide( This is where the plugin is created and where it creates and exports extensions that can be imported and used the app. See reference docs for -[createPlugin](../reference/core-plugin-api.createplugin.md) or introduction to +[`createPlugin`](../reference/core-plugin-api.createplugin.md) or introduction to the new [Composability System](./composability.md). ## Components diff --git a/docs/plugins/url-reader.md b/docs/plugins/url-reader.md index 4c6938ade2..8788737f17 100644 --- a/docs/plugins/url-reader.md +++ b/docs/plugins/url-reader.md @@ -174,14 +174,14 @@ which can be used to request the provider's API. `read` then makes an authenticated request to the provider API and returns the file's content. -#### readUrl +#### `readUrl` `readUrl` is a new interface that allows complex response objects and is intended to replace the `read` method. This new method is currently optional to implement which allows for a soft migration to `readUrl` instead of `read` in the future. -#### readTree +#### `readTree` `readTree` method also expects user-friendly URLs similar to `read` but the URL should point to a tree (could be the root of a repository or even a @@ -241,8 +241,8 @@ without an `etag`, the response contains an ETag of the resource (should ideally forward the ETag returned by the provider). If the method is called with an `etag`, it first compares the ETag and returns a `NotModifiedError` in case the resource has not been modified. This approach is very similar to the actual -[ETag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and -[If-None-Match](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) +[`ETag`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag) and +[`If-None-Match`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-None-Match) HTTP headers. ### 6. Debugging diff --git a/docs/releases/v1.2.0.md b/docs/releases/v1.2.0.md index dba36bfa2c..b2d50a09a2 100644 --- a/docs/releases/v1.2.0.md +++ b/docs/releases/v1.2.0.md @@ -38,7 +38,7 @@ Server-to-server authentication tokens issued from a TokenManager (specifically, ### Kubernetes -Added support for `oidc` as authProvider for kubernetes authentication and added an optional `oidcTokenProvider` config value. This will allow users to authenticate to kubernetes clusters using ID tokens obtained from the configured auth provider in their Backstage instance. Contributed by @dbravovmw(https://github.com/dbravovmw). #11328(https://github.com/backstage/backstage/pull/11328) +Added support for `oidc` as an auth provider for kubernetes authentication and added an optional `oidcTokenProvider` config value. This will allow users to authenticate to kubernetes clusters using ID tokens obtained from the configured auth provider in their Backstage instance. Contributed by @dbravovmw(https://github.com/dbravovmw). #11328(https://github.com/backstage/backstage/pull/11328) ### Misc diff --git a/docs/releases/v1.7.0-changelog.md b/docs/releases/v1.7.0-changelog.md new file mode 100644 index 0000000000..3653484952 --- /dev/null +++ b/docs/releases/v1.7.0-changelog.md @@ -0,0 +1,2589 @@ +# Release v1.7.0 + +## @backstage/cli@0.20.0 + +### Minor Changes + +- f368ad7279: **BREAKING**: Bumped `jest`, `jest-runtime`, and `jest-environment-jsdom` to v29. This is up from v27, so check out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and [v29](https://jestjs.io/docs/upgrading-to-jest29) (later [here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides. + + Particular changes that where encountered in the main Backstage repo are: + + - The updated snapshot format. + - `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. + - Error objects collected by `withLogCollector` from `@backstage/test-utils` are now objects with a `detail` property rather than a string. + +### Patch Changes + +- 78d5eb299e: Tweak the Jest Caching loader to only operate when in `watch` mode + +- 9c595302cb: Normalize on winston version ^3.2.1 + +- 24b40140c4: Treat files in `__testUtils__` and `__mocks__` directories as test files for linting + purposes. + + Updates the parts of the eslint configuration generator that specify which files + should be treated as test code to include any files under two additional + directories: + + - `__mocks__`: this is the directory used by Jest[0] for mock code. + - `__testUtils__`: a suggested location for utility code executed only when + running tests. + + [0]: https://jestjs.io/docs/manual-mocks#mocking-user-modules + +- 3e309107ca: Updated fallback versions of dependencies in all templates. + +- 292a088807: Added a new `repo test` command. + +- ba63cae41c: Updated lockfile parsing to have better support for Yarn 3. + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` + +- 2dddb32fea: Switched the Jest transform for YAML files to use a custom one available at `@backstage/cli/config/jestYamlTransform.js`. + +- a541a3a78a: Switch to upfront resolution of `swc-loader` in Webpack config. + +- cfb3598410: Removed `tsx` and `jsx` as supported extensions in backend packages. For most + repos, this will not have any effect. But if you inadvertently had added some + `tsx`/`jsx` files to your backend package, you may now start to see `code: 'MODULE_NOT_FOUND'` errors when launching the backend locally. The reason for + this is that the offending files get ignored during transpilation. Hence, the + importing file can no longer find anything to import. + + The fix is to rename any `.tsx` files in your backend packages to `.ts` instead, + or `.jsx` to `.js`. + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/errors@1.1.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-backend@0.17.0 + +### Minor Changes + +- e2dc42e9f0: Google OAuth refresh tokens will now be revoked on logout by calling Google's API +- 5fa831ce55: CookieConfigurer can optionally return the `SameSite` cookie attribute. + CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute. + defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests. + + OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`. + OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set. + +### Patch Changes + +- b5c126010c: Auth0 provider now supports optional `connection` and `connectionScope` parameters to configure social identity providers. +- 8c6ec175bf: Fix GitLab provider setup so that it supports GitLab installations with a path in the URL. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-bazaar-backend@0.2.0 + +### Minor Changes + +- 8554533546: **BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. + + These changes are **required** to `packages/backend/src/plugins/bazaar.ts` + + The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. + + ```diff + import { PluginEnvironment } from '../types'; + import { createRouter } from '@backstage/plugin-bazaar-backend'; + import { Router } from 'express'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-bitbucket-cloud-common@0.2.0 + +### Minor Changes + +- ad74723fbf: Update Bitbucket Cloud models to latest OAS version. + + The latest specification contained some BREAKING CHANGES + due to removed fields. + + All of these fields are not used at other plugins, though. + Therefore, this change has no impact on other modules here. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog@1.6.0 + +### Minor Changes + +- d558f41d3a: Added new column `Label` to `CatalogTable.columns`, this new column allows you make use of labels from metadata. + For example: category and visibility are type of labels associated with API entity illustrated below. + + YAML code snippet for API entity + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: API + metadata: + name: sample-api + description: API for sample + links: + - url: http://localhost:8080/swagger-ui.html + title: Swagger UI + tags: + - http + labels: + category: legacy + visibility: protected + ``` + + Consumers can customise columns to include label column and show in api-docs list + + ```typescript + const columns = [ + CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), + CatalogTable.columns.createLabelColumn('category', { title: 'Category' }), + CatalogTable.columns.createLabelColumn('visibility', { + title: 'Visibility', + defaultValue: 'public', + }), + ]; + ``` + +### Patch Changes + +- 4efadb6968: Implemented the visual parts of `EntityKindPicker` so that it can be shown alongside the other filters on the left side of your catalog pages. +- 182000c663: Added `emptyContent` property to CatalogTable and DefaultCatalogPage to support customization of the Catalog Table. +- e89e1f614d: Added support for copy entity URL in entity page context menu +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend@1.5.0 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 8cb6e10105: Fixed a bug where entities provided without a location key would always replace existing entities, rather than updating them. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 63296ebcd4: Allow Placeholder value to be any value, not only string. +- 74022e0163: Make sure to stitch entities correctly after deletion, to ensure that their relations are updated. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-import@0.9.0 + +### Minor Changes + +- b2e6cb6acf: **Breaking** + Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials. + + Add the following to your `CatalogBuilder` to have the repo URL ingestion working again. + + ```ts + // catalog.ts + import { GithubLocationAnalyzer } from '@backstage/plugin-catalog-backend-module-github'; + ... + builder.addLocationAnalyzers( + new GithubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + }), + ); + ... + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/catalog-client@1.1.1 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog-node@1.2.0 + +### Minor Changes + +- 404366c853: Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` so make sure imports are updated. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-react@1.2.0 + +### Minor Changes + +- 4efadb6968: Implemented the visual parts of `EntityKindPicker` so that it can be shown alongside the other filters on the left side of your catalog pages. + +### Patch Changes + +- 7939e743f5: Added two new `EntityRefLinks` props, the first being `getTitle` that allows for customization of the title used for each link. The second one is `fetchEntities`, which triggers a fetching of all entities so that the full entity definition is available in the `getTitle` callback. +- e9e532ebd8: Fixed issue where the query kind parameter is not honored +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-permission-react@0.4.6 + +## @backstage/plugin-permission-common@0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-permission-node@0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- 9335ad115e: Exported types for the .metadata endpoint of the permission router +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-playlist-backend@0.2.0 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-playlist-common@0.1.1 + +## @backstage/plugin-scaffolder@1.7.0 + +### Minor Changes + +- f13d5f3f06: Add support for link to TechDocs and other links defined in template entity specification metadata on TemplateCard +- 05f22193c5: EntityPickers now support flags to control when to include default namespace + in result + +### Patch Changes + +- e4f0a96424: Making the description of the GitLab repoUrl owner field more clearer by focusing it refers to the GitLab namespace. +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 8220f2fd83: Support custom layouts in dry run editor +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- 8960d83013: Add support for `allowedOrganizations` and `allowedOwners` to the `AzureRepoPicker`. +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-react@0.4.6 + +## @backstage/plugin-scaffolder-backend@1.7.0 + +### Minor Changes + +- 253453fa14: Added a new property called `additionalTemplateGlobals` which allows you to add global functions to the scaffolder nunjucks templates. +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- 304305dd20: Add `allowAutoMerge` option for `publish:github` action +- 694bfe2d61: Add functionality to shutdown scaffolder tasks if they are stale +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 489621f613: Switching off duplicated timestamp in case of logging via task logger in a custom action +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-backend@1.1.0 + +### Minor Changes + +- 16c853a6ed: Be less restrictive with unknown keys on query endpoint +- a799972bb1: The query received by search engines now contains a property called `pageLimit`, it specifies how many results to return per page when sending a query request to the search backend. + + Example: + _Returns up to 30 results per page_ + + GET /query?pageLimit=30 + + The search backend validates the page limit and this value must not exceed 100, but it doesn't set a default value for the page limit parameter, it leaves it up to each search engine to set this, so Lunr, Postgres and Elastic Search set 25 results per page as a default value. + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-common@1.1.0 + +### Minor Changes + +- a799972bb1: There is a new property called `pageLimit` on the `SearchQuery` interface that specifies how many results should be returned per page. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-react@1.2.0 + +### Minor Changes + +- 4ed1fa2480: The search query state now has an optional `pageLimit` property that determines how many results will be requested per page, it defaults to 25. + + Examples: + _Basic_ + + ```jsx + + {results => { + // Item rendering logic is omitted + }} + + ``` + + _With context_ + + ```jsx + + + {results => { + // Item rendering logic is omitted + }} + + + ``` + +- bed5a1dc6e: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + + ``` + +- 3de4bd4f19: A `` component was created for limiting the number of results shown per search page. Use this new component to give users options to select how many search results they want to display per page. The default options are 10, 25, 50, 100. + + See examples below: + + _Basic_ + + ```jsx + import React, { useState } from 'react'; + import { Grid } from '@material-ui/core'; + import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; + import { + SearchBarBase, + SearchPaginationBase, + SearchResultList, + } from '@backstage/plugin-search-react'; + + const SearchPage = () => { + const [term, setTerm] = useState(''); + const [pageLimit, setPageLimit] = useState(25); + const [pageCursor, setPageCursor] = useState(); + + return ( + +
} /> + + + + + + + + + + + + + + + ); + }; + ``` + + _With context_ + + ```jsx + import React from 'react'; + import { Grid } from '@material-ui/core'; + import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; + import { + SearchBar, + SearchResult, + SearchPagination, + SearchResultListLayout, + SearchContextProvider, + DefaultResultListItem, + } from '@backstage/plugin-search-react'; + + const SearchPage = () => ( + + +
} /> + + + + + + + + + + + {({ results }) => ( + ( + + )} + /> + )} + + + + + + + ); + ``` + +- 6faaa05626: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + title="Documentation" + noResultsComponent={} + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + } + title="Documentation" + disableRenderingWithNoResults + /> + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-techdocs-backend@1.4.0 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-techdocs-node@1.4.1 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-user-settings@0.5.0 + +### Minor Changes + +- 5543e86660: **BREAKING**: The `apiRef` passed to `ProviderSettingsItem` now needs to + implement `ProfileInfoApi & SessionApi`, rather than just the latter. This is + unlikely to have an effect on most users though, since the builtin auth + providers generally implement both. + + Fixed settings page showing providers as logged out when the user is using more + than one provider, and displayed some additional login information. + +### Patch Changes + +- 06d61d1266: Handle errors that may occur when the user logs out +- 44c9a95dcf: Prevent `.set()` to execute a request to the StorageClient if the user is `guest` +- 174f02a00a: Update installation instructions +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/app-defaults@1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + +## @backstage/backend-app-api@0.2.2 + +### Patch Changes + +- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. +- 45857bffae: Properly export `rootLoggerFactory`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/errors@1.1.2 + +## @backstage/backend-common@0.15.2 + +### Patch Changes + +- e8d7976413: Added back support for when no branch is provided for the Bitbucket Server `UrlReader` +- c44cf412de: Fix BitBucket server integration +- c31f7cdfbc: Fixed an issue where `getClient()` for a `pluginId` would return different clients and not share them +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/backend-defaults@0.1.2 + +### Patch Changes + +- 96d288a02d: Added root logger service to the set of default services. +- Updated dependencies + - @backstage/backend-app-api@0.2.2 + - @backstage/backend-plugin-api@0.1.3 + +## @backstage/backend-plugin-api@0.1.3 + +### Patch Changes + +- 28377dc89f: Allow interfaces to be used for inferred option types. +- a35a27df70: Added documentation for `createBackendModule`, with guidelines for choosing a module ID. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + +## @backstage/backend-tasks@0.3.6 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/backend-test-utils@0.1.29 + +### Patch Changes + +- 72549952d1: Fixed handling of root scoped services in `startTestBackend`. +- e91e8e9c55: Increased test database max connection pool size to reduce the risk of resource exhaustion. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/cli@0.20.0 + - @backstage/backend-app-api@0.2.2 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/config@1.0.3 + +## @backstage/catalog-client@1.1.1 + +### Patch Changes + +- 4f2ac624b4: Renamed argument in `validateEntity` from `location` to `locationRef` +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/errors@1.1.2 + +## @backstage/catalog-model@1.1.2 + +### Patch Changes + +- 6f3b8d0962: Defer `ajv` compilation of schema validators to improve module-import performance +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/codemods@0.1.40 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/config@1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/config-loader@1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/core-app-api@1.1.1 + +### Patch Changes + +- 27e6404aba: Fixed a bug where gathered index routes would fail to bind routable extensions. This would typically show up when placing a routable extension in the entity page overview tab. +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-components@0.11.2 + +### Patch Changes + +- bde1e8c8e2: Added `curve` prop to the `DependencyGraph` component to select the type of layout +- 882101cd9b: Deep-import LightAsync component to improve module-import speed +- 0d0f30d87e: Fixed layout for core-components docs where table was broken by splitting with code sample +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-plugin-api@1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/create-app@0.4.32 + +### Patch Changes + +- 58c2264325: Newly created Backstage repositories now use the stable version 6 of + `react-router`, just like the main repo does. Please let us know if you find any + issues with this. + + Migrating to the stable version of `react-router` is optional for the time + being. But if you want to do the same for your existing repository, please + follow [this + guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + +- e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo + +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. + +- 90616bcaa6: Add the new search pagination component to the search page template. + +- 7c6306fc8a: Initializes a git repository when creating an app using @packages/create-app + +- 52f25858a8: Added `*.session.sql` Visual Studio Code database functionality files to `.gitignore` in the default template. This is optional but potentially helpful if your developers use Visual Studio Code; you can add a line with that exact value to your own root `.gitignore` if you want the same. + +- 6d00e80146: Updated the root `test` scripts to use `backstage-cli repo test`. + + To apply this change to an existing app, make the following change to the root `package.json`: + + ```diff + - "test": "backstage-cli test", + - "test:all": "lerna run test -- --coverage", + + "test": "backstage-cli repo test", + + "test:all": "backstage-cli repo test --coverage", + ``` + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/dev-utils@1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/test-utils@1.2.1 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/errors@1.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/integration@1.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/integration-react@1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @techdocs/cli@1.2.2 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-techdocs-node@1.4.1 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + +## @backstage/test-utils@1.2.1 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/core-app-api@1.1.1 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-react@0.4.6 + +## @backstage/plugin-adr@0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-adr-common@0.2.2 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-adr-backend@0.2.2 + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-adr-common@0.2.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-adr-common@0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/integration@1.3.2 + +## @backstage/plugin-airbrake@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/test-utils@1.2.1 + - @backstage/dev-utils@1.0.7 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-airbrake-backend@0.2.10 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## @backstage/plugin-allure@0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-analytics-module-ga@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apache-airflow@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + +## @backstage/plugin-api-docs@0.8.10 + +### Patch Changes + +- 50c6e14aee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.43`. +- 3d5bb521ee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.42`. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apollo-explorer@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-app-backend@0.3.37 + +### Patch Changes + +- 11c9e0ad33: Added alpha plugin implementation for the new backend system. Available at `@backstage/plugin-app-backend/alpha`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-node@0.2.6 + +### Patch Changes + +- f3a3fefb96: Ensure `getIdentity` throws an `AuthenticationError` instead of a `NotAllowed` error when authentication fails +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-azure-devops@0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-azure-devops-backend@0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-badges@0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-badges-backend@0.1.31 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-bazaar@0.1.25 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/cli@0.20.0 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + +## @backstage/plugin-bitrise@0.1.37 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-backend-module-aws@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.4 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.4 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/plugin-bitbucket-cloud-common@0.2.0 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog-backend-module-github@0.1.8 + +### Patch Changes + +- 8749df3d02: `GitHubEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- 7022aebf35: Added `GithubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. + +- 51046b58b0: Use schedule from config at backend module. + + Also, it removes `GithubEntityProviderCatalogModuleOptions` + in favor of config-only for the backend module setup + like at other similar modules. + +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. + +- be9474b103: Replaces in-code uses of `GitHub` by `Github` and deprecates old versions. + + Deprecates + + - `GitHubEntityProvider` replaced by `GithubEntityProvider` + - `GitHubLocationAnalyzer` replaced by `GithubLocationAnalyzer` + - `GitHubLocationAnalyzerOptions` replaced by `GithubLocationAnalyzerOptions` + - `GitHubOrgEntityProvider` replaced by `GithubOrgEntityProvider` + - `GitHubOrgEntityProviderOptions` replaced by `GithubOrgEntityProviderOptions` + + Renames + + - `GitHubLocationAnalyzer` to `GithubLocationAnalyzer` + - `GitHubLocationAnalyzerOptions` to `GithubLocationAnalyzerOptions` + +- a35a27df70: Updated the `moduleId` of the experimental module export. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-common@1.0.7 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + +## @backstage/plugin-catalog-graph@0.2.22 + +### Patch Changes + +- bde1e8c8e2: Added `curve` prop to the `DependencyGraph` component to select the type of layout +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-graphql@0.3.14 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + +## @backstage/plugin-cicd-statistics@0.1.12 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-plugin-api@1.0.7 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-cicd-statistics@0.1.12 + - @backstage/core-plugin-api@1.0.7 + +## @backstage/plugin-circleci@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-cloudbuild@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-climate@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage-backend@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-codescene@0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-config-schema@0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-cost-insights@0.11.32 + +### Patch Changes + +- a94c2ed1b7: Fixed bug in `CostOverviewBreakdownChart` component where some datasets caused the cost overview breakdown chart to tear. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## @backstage/plugin-dynatrace@1.0.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore@0.3.41 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22 + +## @backstage/plugin-explore-react@0.0.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + +## @backstage/plugin-firehydrant@0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-fossa@0.2.42 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcalendar@0.3.6 + +### Patch Changes + +- 4c2ed7ecf1: Fixed loader showing when user not signed in +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcp-projects@0.3.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-git-release-manager@0.3.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-actions@0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-deployments@0.1.41 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-issues@0.1.2 + +### Patch Changes + +- 8c7bff2bb4: Updated the `luxon` dependency to 3.x +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- df226e124c: Add filtering and ordering to the graphql query +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-pull-requests-board@0.1.4 + +### Patch Changes + +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gitops-profiles@0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gocd@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphiql@0.2.42 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphql-backend@0.1.27 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-graphql@0.3.14 + - @backstage/config@1.0.3 + +## @backstage/plugin-home@0.4.26 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-stack-overflow@0.1.6 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-ilert@0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins@0.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.9 + +## @backstage/plugin-jenkins-backend@0.1.27 + +### Patch Changes + +- b19ea927af: Fixed a bug where `extraRequestHeaders` configuration was ignored. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-jenkins-common@0.1.9 + +## @backstage/plugin-jenkins-common@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + +## @backstage/plugin-kafka@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kafka-backend@0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-kubernetes@0.7.3 + +### Patch Changes + +- 51af8361de: Add useCustomResources react hook for fetching Kubernetes Custom Resources +- 35a6cfe257: Fix infinite call bug in `useCustomResources` hook +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-kubernetes-common@0.4.3 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kubernetes-backend@0.7.3 + +### Patch Changes + +- de676888bc: Added missing cluster locator configuration schema entries, for the catalog and local proxy types +- d4a8c683be: kubernetes service locator now take request context parameters +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-kubernetes-common@0.4.3 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-kubernetes-common@0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + +## @backstage/plugin-lighthouse@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic@0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic-dashboard@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + +## @backstage/plugin-org@0.5.10 + +### Patch Changes + +- f2b4b55636: consistently show parent and child relations in group profile card +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-pagerduty@0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop-backend@0.1.8 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## @backstage/plugin-permission-backend@0.5.12 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-permission-react@0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + +## @backstage/plugin-playlist@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-react@1.2.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + - @backstage/plugin-playlist-common@0.1.1 + +## @backstage/plugin-playlist-common@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + +## @backstage/plugin-proxy-backend@0.2.31 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## @backstage/plugin-rollbar@0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-rollbar-backend@0.1.34 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-common@1.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search@1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.0.3 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + +## @backstage/plugin-search-backend-module-pg@0.4.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + +## @backstage/plugin-search-backend-node@1.0.3 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-sentry@0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-shortcuts@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-sonarqube@0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube-backend@0.1.2 + +### Patch Changes + +- 9c595302cb: Normalize on winston version ^3.2.1 +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-splunk-on-call@0.3.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-home@0.4.26 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow-backend@0.1.6 + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/cli@0.20.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + +## @backstage/plugin-tech-insights@0.3.1 + +### Patch Changes + +- f3d272cf57: Make sure to reload score card contents when props change +- a60a6807bd: making available the search for the last FACTS executed +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7 + +## @backstage/plugin-tech-insights-backend@0.5.3 + +### Patch Changes + +- 296aea34da: The Tech Insights plugin supports running fact retrievers across multiple instances. Update the README to remove the stale instructions. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- f7cbfb97ed: Modify router endpoint to handle singular and collections of request parameters similarly. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-tech-insights-node@0.3.5 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-tech-insights-common@0.2.7 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-tech-insights-node@0.3.5 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-tech-insights-common@0.2.7 + +## @backstage/plugin-tech-insights-common@0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/plugin-tech-insights-node@0.3.5 + +### Patch Changes + +- 0963b4d5fb: Updated package role to be `node-library`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7 + +## @backstage/plugin-tech-radar@0.5.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs@1.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/test-utils@1.2.1 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-node@1.4.1 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-techdocs-react@1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-todo@0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-todo-backend@0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-user-settings-backend@0.1.1 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 82ac9bcfe5: Fix wrong import statement in `README.md`. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-vault@0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-vault-backend@0.2.3 + +### Patch Changes + +- 9c595302cb: Normalize on winston version ^3.2.1 +- dae0bbe522: VaultBuilder.tsx renamed to VaultBuilder in order for module to be correctly loaded. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-xcmetrics@0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## example-app@0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.5.0 + - @backstage/plugin-scaffolder@1.7.0 + - @backstage/plugin-org@0.5.10 + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-graph@0.2.22 + - @backstage/core-app-api@1.1.1 + - @backstage/cli@0.20.0 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-catalog-import@0.9.0 + - @backstage/plugin-tech-insights@0.3.1 + - @backstage/plugin-kubernetes@0.7.3 + - @backstage/plugin-gcalendar@0.3.6 + - @backstage/plugin-api-docs@0.8.10 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-cost-insights@0.11.32 + - @backstage/plugin-airbrake@0.3.10 + - @backstage/plugin-azure-devops@0.2.1 + - @backstage/plugin-badges@0.2.34 + - @backstage/plugin-circleci@0.3.10 + - @backstage/plugin-cloudbuild@0.3.10 + - @backstage/plugin-code-coverage@0.2.3 + - @backstage/plugin-dynatrace@1.0.0 + - @backstage/plugin-explore@0.3.41 + - @backstage/plugin-github-actions@0.5.10 + - @backstage/plugin-gocd@0.1.16 + - @backstage/plugin-home@0.4.26 + - @backstage/plugin-jenkins@0.7.9 + - @backstage/plugin-kafka@0.3.10 + - @backstage/plugin-lighthouse@0.3.10 + - @backstage/plugin-newrelic-dashboard@0.2.3 + - @backstage/plugin-pagerduty@0.5.3 + - @backstage/plugin-playlist@0.1.1 + - @backstage/plugin-rollbar@0.4.10 + - @backstage/plugin-search@1.0.3 + - @backstage/plugin-sentry@0.4.3 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/plugin-todo@0.2.12 + - @internal/plugin-catalog-customized@0.0.3 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/plugin-apache-airflow@0.2.3 + - @backstage/plugin-gcp-projects@0.3.29 + - @backstage/plugin-graphiql@0.2.42 + - @backstage/plugin-newrelic@0.3.28 + - @backstage/plugin-shortcuts@0.3.2 + - @backstage/plugin-stack-overflow@0.1.6 + - @backstage/plugin-tech-radar@0.5.17 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + +## example-backend@0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-auth-backend@0.17.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-tech-insights-backend@0.5.3 + - @backstage/plugin-search-backend@1.1.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-playlist-backend@0.2.0 + - @backstage/plugin-jenkins-backend@0.1.27 + - @backstage/plugin-app-backend@0.3.37 + - @backstage/plugin-badges-backend@0.1.31 + - @backstage/plugin-graphql-backend@0.1.27 + - @backstage/plugin-permission-backend@0.5.12 + - @backstage/plugin-rollbar-backend@0.1.34 + - @backstage/plugin-kubernetes-backend@0.7.3 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/plugin-search-backend-module-pg@0.4.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3 + - @backstage/plugin-techdocs-backend@1.4.0 + - @backstage/plugin-tech-insights-node@0.3.5 + - example-app@0.2.76 + - @backstage/plugin-code-coverage-backend@0.2.3 + - @backstage/plugin-kafka-backend@0.2.30 + - @backstage/plugin-todo-backend@0.1.34 + - @backstage/plugin-azure-devops-backend@0.3.16 + - @backstage/plugin-proxy-backend@0.2.31 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + +## example-backend-next@0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/backend-defaults@0.1.2 + - @backstage/plugin-app-backend@0.3.37 + +## techdocs-cli-embedded-app@0.2.75 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/cli@0.20.0 + - @backstage/test-utils@1.2.1 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @internal/plugin-catalog-customized@0.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + +## @internal/plugin-todo-list@1.0.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @internal/plugin-todo-list-backend@1.0.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @internal/plugin-todo-list-common@1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 diff --git a/docs/releases/v1.7.0-next.2-changelog.md b/docs/releases/v1.7.0-next.2-changelog.md new file mode 100644 index 0000000000..d5a9b0ddad --- /dev/null +++ b/docs/releases/v1.7.0-next.2-changelog.md @@ -0,0 +1,2127 @@ +# Release v1.7.0-next.2 + +## @backstage/plugin-bazaar-backend@0.2.0-next.2 + +### Minor Changes + +- 8554533546: **BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. + + These changes are **required** to `packages/backend/src/plugins/bazaar.ts` + + The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. + + ```diff + import { PluginEnvironment } from '../types'; + import { createRouter } from '@backstage/plugin-bazaar-backend'; + import { Router } from 'express'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-catalog-backend@1.5.0-next.2 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## @backstage/plugin-catalog-import@0.9.0-next.2 + +### Minor Changes + +- b2e6cb6acf: **Breaking** + Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials. + + Add the following to your `CatalogBuilder` to have the repo URL ingestion working again. + + ```ts + // catalog.ts + import { GitHubLocationAnalyzer } from '@backstage/plugin-catalog-backend-module-github'; + ... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + }), + ); + ... + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + +## @backstage/plugin-catalog-node@1.2.0-next.2 + +### Minor Changes + +- 404366c853: Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` so make sure imports are updated. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-permission-common@0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-permission-node@0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-playlist-backend@0.2.0-next.2 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-scaffolder-backend@1.7.0-next.2 + +### Minor Changes + +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## @backstage/plugin-techdocs-backend@1.4.0-next.2 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/app-defaults@1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/backend-app-api@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/backend-common@0.15.2-next.2 + +### Patch Changes + +- c44cf412de: Fix BitBucket server integration +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/backend-defaults@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + +## @backstage/backend-plugin-api@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/backend-tasks@0.3.6-next.2 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/backend-test-utils@0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/catalog-client@1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/catalog-model@1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/cli@0.20.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## @backstage/codemods@0.1.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/config@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/config-loader@1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/core-app-api@1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-components@0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-plugin-api@1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/create-app@0.4.32-next.2 + +### Patch Changes + +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/dev-utils@1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + +## @backstage/errors@1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/integration@1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/integration-react@1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @techdocs/cli@1.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + +## @backstage/test-utils@1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-adr@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-adr-common@0.2.2-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-adr-backend@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-adr-common@0.2.2-next.2 + +## @backstage/plugin-adr-common@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-airbrake@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/dev-utils@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-airbrake-backend@0.2.10-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-allure@0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-analytics-module-ga@0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apache-airflow@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-api-docs@0.8.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apollo-explorer@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-app-backend@0.3.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-backend@0.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-node@0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-azure-devops@0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-azure-devops-backend@0.3.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-badges@0.2.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-badges-backend@0.1.31-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-bazaar@0.1.25-next.2 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-bitrise@0.1.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog@1.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-catalog-backend-module-aws@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.4-next.2 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.4-next.2 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-catalog-backend-module-github@0.1.8-next.2 + +### Patch Changes + +- 7022aebf35: Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-common@1.0.7-next.2 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + +## @backstage/plugin-catalog-graph@0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-graphql@0.3.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-react@1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-cicd-statistics@0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/plugin-cicd-statistics@0.1.12-next.2 + +## @backstage/plugin-circleci@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-cloudbuild@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-climate@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage-backend@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-codescene@0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-config-schema@0.1.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-cost-insights@0.11.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## @backstage/plugin-dynatrace@1.0.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore@0.3.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22-next.2 + +## @backstage/plugin-explore-react@0.0.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-firehydrant@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-fossa@0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcalendar@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcp-projects@0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-git-release-manager@0.3.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-actions@0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-deployments@0.1.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-issues@0.1.2-next.2 + +### Patch Changes + +- df226e124c: Add filtering and ordering to the graphql query +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-pull-requests-board@0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gitops-profiles@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gocd@0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphiql@0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphql-backend@0.1.27-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-catalog-graphql@0.3.14-next.2 + +## @backstage/plugin-home@0.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + +## @backstage/plugin-ilert@0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins@0.7.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins-backend@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-jenkins-common@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + +## @backstage/plugin-kafka@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kafka-backend@0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-kubernetes@0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + +## @backstage/plugin-kubernetes-backend@0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + +## @backstage/plugin-kubernetes-common@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + +## @backstage/plugin-lighthouse@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-org@0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-pagerduty@0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop-backend@0.1.8-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-permission-backend@0.5.12-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-permission-react@0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-playlist@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-playlist-common@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + +## @backstage/plugin-proxy-backend@0.2.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-rollbar@0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-rollbar-backend@0.1.34-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-scaffolder@1.7.0-next.2 + +### Patch Changes + +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-common@1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-search-backend@1.1.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-search-backend-node@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-search-common@1.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-react@1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-sentry@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-shortcuts@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-sonarqube@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube-backend@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-splunk-on-call@0.3.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-home@0.4.26-next.2 + +## @backstage/plugin-stack-overflow-backend@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-tech-insights@0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-insights-backend@0.5.3-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-insights-common@0.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/plugin-tech-insights-node@0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-radar@0.5.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs@1.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @backstage/plugin-techdocs-node@1.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-techdocs-react@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-todo@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-todo-backend@0.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-user-settings@0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-user-settings-backend@0.1.1-next.2 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-vault@0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-vault-backend@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-xcmetrics@0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## example-app@0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.7.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-import@0.9.0-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-playlist@0.1.1-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.10-next.2 + - @backstage/plugin-apache-airflow@0.2.3-next.2 + - @backstage/plugin-api-docs@0.8.10-next.2 + - @backstage/plugin-azure-devops@0.2.1-next.2 + - @backstage/plugin-badges@0.2.34-next.2 + - @backstage/plugin-catalog-graph@0.2.22-next.2 + - @backstage/plugin-circleci@0.3.10-next.2 + - @backstage/plugin-cloudbuild@0.3.10-next.2 + - @backstage/plugin-code-coverage@0.2.3-next.2 + - @backstage/plugin-cost-insights@0.11.32-next.2 + - @backstage/plugin-dynatrace@1.0.0-next.2 + - @backstage/plugin-explore@0.3.41-next.2 + - @backstage/plugin-gcalendar@0.3.6-next.2 + - @backstage/plugin-gcp-projects@0.3.29-next.2 + - @backstage/plugin-github-actions@0.5.10-next.2 + - @backstage/plugin-gocd@0.1.16-next.2 + - @backstage/plugin-graphiql@0.2.42-next.2 + - @backstage/plugin-home@0.4.26-next.2 + - @backstage/plugin-jenkins@0.7.9-next.2 + - @backstage/plugin-kafka@0.3.10-next.2 + - @backstage/plugin-kubernetes@0.7.3-next.2 + - @backstage/plugin-lighthouse@0.3.10-next.2 + - @backstage/plugin-newrelic@0.3.28-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + - @backstage/plugin-org@0.5.10-next.2 + - @backstage/plugin-pagerduty@0.5.3-next.2 + - @backstage/plugin-rollbar@0.4.10-next.2 + - @backstage/plugin-search@1.0.3-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-sentry@0.4.3-next.2 + - @backstage/plugin-shortcuts@0.3.2-next.2 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + - @backstage/plugin-tech-insights@0.3.1-next.2 + - @backstage/plugin-tech-radar@0.5.17-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + - @backstage/plugin-todo@0.2.12-next.2 + - @backstage/plugin-user-settings@0.5.0-next.2 + - @internal/plugin-catalog-customized@0.0.3-next.2 + +## example-backend@0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-playlist-backend@0.2.0-next.2 + - @backstage/plugin-badges-backend@0.1.31-next.2 + - @backstage/plugin-graphql-backend@0.1.27-next.2 + - @backstage/plugin-permission-backend@0.5.12-next.2 + - @backstage/plugin-rollbar-backend@0.1.34-next.2 + - @backstage/plugin-search-backend@1.1.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.3-next.2 + - @backstage/plugin-techdocs-backend@1.4.0-next.2 + - example-app@0.2.76-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/plugin-auth-backend@0.17.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-azure-devops-backend@0.3.16-next.2 + - @backstage/plugin-code-coverage-backend@0.2.3-next.2 + - @backstage/plugin-jenkins-backend@0.1.27-next.2 + - @backstage/plugin-kafka-backend@0.2.30-next.2 + - @backstage/plugin-kubernetes-backend@0.7.3-next.2 + - @backstage/plugin-proxy-backend@0.2.31-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + - @backstage/plugin-todo-backend@0.1.34-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + +## example-backend-next@0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/backend-defaults@0.1.2-next.2 + +## techdocs-cli-embedded-app@0.2.75-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @internal/plugin-catalog-customized@0.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + +## @internal/plugin-todo-list@1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @internal/plugin-todo-list-backend@1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @internal/plugin-todo-list-common@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 diff --git a/docs/releases/v1.7.0.md b/docs/releases/v1.7.0.md new file mode 100644 index 0000000000..cb87ead4c9 --- /dev/null +++ b/docs/releases/v1.7.0.md @@ -0,0 +1,125 @@ +--- +id: v1.7.0 +title: v1.7.0 +description: Backstage Release v1.7.0 +--- + +These are the release notes for the v1.7.0 release of +[Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the +amazing Backstage Community for the hard work in getting this release developed +and done. + +## Highlights + +### GitHub Catalog Import now Powered by the Backend + +The analysis performed during catalog imports (i.e. when supplying the URL of a +repository rather than an individual YAML file in the Create flow) is now +powered by the backend rather than frontend code. This means that the catalog +backend needs to be supplied with a location analyzer for this use case to +continue to function. + +If you want to make use of this feature, check out the installation instructions +in [the +changelog](https://github.com/backstage/backstage/blob/master/plugins/catalog-import/CHANGELOG.md#090). + +Contributed by [@kissmikijr](https://github.com/kissmikijr) in +[#13800](https://github.com/backstage/backstage/pull/13800) + +### Permission Rule Changes + +When defining permission rules, it's now necessary to provide a [Zod +Schema](https://github.com/colinhacks/zod) that specifies the parameters the +rule expects. This has been added to help better describe the parameters in the +response of the metadata endpoint and to validate the parameters before a rule +is executed. The signatures of the rule methods (`apply` and `toQuery`) have +changed slightly as well. + +You can read more about this in [the permissions +documentation](https://backstage.io/docs/permissions/overview) and [the +changelog](https://github.com/backstage/backstage/blob/master/plugins/permission-node/CHANGELOG.md#070). + +### Migration: `jest` v29 + +Both `jest`, `jest-runtime`, and `jest-environment-jsdom` as used by the +Backstage CLI were bumped to version 29. This is up from version 27, so check +out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and +[v29](https://jestjs.io/docs/upgrading-to-jest29) (later +[here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides, since +your existing tests may be affected. + +Particular changes that were encountered in the main Backstage repository are: + +- The updated snapshot format. +- `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. +- Error objects collected by `withLogCollector` from `@backstage/test-utils` are + now objects with a `detail` property rather than a string. + +### Migration: `react-router` v6 + +Newly created Backstage repositories now use the stable version 6 of +`react-router`, just like the main repository does. + +Migrating to the stable version of `react-router` is optional for the time +being; Backstage has support for both versions. But if you want to do the same +for your existing repository, please follow [this +guide](https://backstage.io/docs/tutorials/react-router-stable-migration). +Support for the beta version will be removed in a later release. + +### Support for `__mocks__` and `__testUtils__` directories + +The Backstage CLI now has built-in support for `__mocks__` and `__testUtils__` +directories in your code. These can be used for mocks and shared utilities in +tests. + +### New Arguments for the Router of `@backstage/plugin-bazaar-backend` + +The bazaar-backend `createRouter` function now requires that the `identityApi` +is passed to the router. + +### Deprecated plugin: `@backstage/plugin-catalog-backend-module-bitbucket` + +This has been deprecated and split into +`@backstage/plugin-catalog-backend-module-bitbucket-cloud` and +`@backstage/plugin-catalog-backend-module-bitbucket-server`, for BitBucket Cloud +and BitBucket Server respectively. Please update your dependencies accordingly, +depending on which product you use. + +The original package will be removed in a future release. + +Contributed by [@pjungermann](https://github.com/pjungermann) in +[#14070](https://github.com/backstage/backstage/pull/14070) + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest +release. For more guidance on how to upgrade, check out the documentation for +[keeping Backstage +updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and +start using this new release. + +- [Backstage official website](https://backstage.io/), + [documentation](https://backstage.io/docs/), and [getting started + guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support + policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/bFESRKVt) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.7.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), + [Blog](https://backstage.io/blog), + [Roadmap](https://backstage.io/docs/overview/roadmap) and + [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if +you want to be informed about what is happening in the world of Backstage. diff --git a/docs/releases/v1.8.0-next.0-changelog.md b/docs/releases/v1.8.0-next.0-changelog.md new file mode 100644 index 0000000000..c661325d95 --- /dev/null +++ b/docs/releases/v1.8.0-next.0-changelog.md @@ -0,0 +1,2326 @@ +# Release v1.8.0-next.0 + +## @backstage/backend-common@0.16.0-next.0 + +### Minor Changes + +- a7607b5413: **BREAKING CHANGE**: The `UrlReader` interface has been updated to require that `readUrl` is implemented. `readUrl` has previously been optional to implement but a warning has been logged when calling its predecessor `read`. + The `read` method is now deprecated and will be removed in a future release. + +### Patch Changes + +- 55227712dd: Generated development HTTPS backend certificate is now checked for expiration date instead of file age. +- d05e1841ce: This patch adds GiteaURLReader to the available classes. It currently only reads single files via gitea's public repos api +- 210a3b5668: Small update to fix compatibility with newer versions of the `keyv` library +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/cli@0.21.0-next.0 + +### Minor Changes + +- 7539b36748: Added a new ESLint rule that restricts imports of Link from @material-ui + + The rule can be can be overridden in the following way: + + ```diff + module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + + restrictedImports: [ + + { name: '@material-ui/core', importNames: [] }, + + { name: '@material-ui/core/Link', importNames: [] }, + + ], + }); + ``` + +### Patch Changes + +- 4091c73e68: Updated `@swc/core` to version 1.3.9 which fixes a `.tsx` parser bug +- 9c767e8f45: Updated dependency `@svgr/plugin-jsx` to `6.5.x`. + Updated dependency `@svgr/plugin-svgo` to `6.5.x`. + Updated dependency `@svgr/rollup` to `6.5.x`. + Updated dependency `@svgr/webpack` to `6.5.x`. +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/release-manifests@0.0.6 + +## @backstage/core-app-api@1.2.0-next.0 + +### Minor Changes + +- 9b737e5f2e: Updated the React Router wiring to make use of the new `basename` property of the router components in React Router v6 stable. To implement this, a new optional `basename` property has been added to the `Router` app component, which can be forwarded to the concrete router implementation in order to support this new behavior. This is done by default in any app that does not have a `Router` component override. +- 127fcad26d: Deprecated the `homepage` config as the component that used it - `HomepageTimer` - has been removed and replaced by the `HeaderWorldClock` in the home plugin + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-components@0.12.0-next.0 + +### Minor Changes + +- fb3733e446: **BREAKING**: Removed the `HomepageTimer` as it has been replaced by the `HeaderWorldClock` in the Home plugin and was deprecated over a year ago. + +### Patch Changes + +- 5f695c219a: Set the `searchTooltip` to "Filter" to follow how the `searchPlaceholder` is set making this more consistent +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 858986f6b6: Disable base path workaround in `Link` component when React Router v6 stable is used. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-plugin-api@1.1.0-next.0 + +### Minor Changes + +- a228f113d0: The app `Router` component now accepts an optional `basename` property. + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/integration@1.4.0-next.0 + +### Minor Changes + +- d05e1841ce: This patch brings Gitea as a valid integration: target, via the ScmIntegration interface. It adds gitea to the relevant static properties (get integration by name, get integration by type) for plugins to be able to reference the same Gitea server. +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. + + Deprecates: + + - `getGitHubFileFetchUrl` replaced by `getGithubFileFetchUrl` + - `GitHubIntegrationConfig` replaced by `GithubIntegrationConfig` + - `GitHubIntegration` replaced by `GithubIntegration` + - `readGitHubIntegrationConfig` replaced by `readGithubIntegrationConfig` + - `readGitHubIntegrationConfigs` replaced by `readGithubIntegrationConfigs` + - `replaceGitHubUrlType` replaced by `replaceGithubUrlType` + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-azure-sites@0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-azure-sites-common@0.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-azure-sites-backend@0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-azure-sites-common@0.1.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-azure-sites-common@0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +## @backstage/plugin-bazaar@0.2.0-next.0 + +### Minor Changes + +- 28b39e0e0e: The limit prop of BazaarOverviewCard has been removed entirely, and instead replaced with a new optional boolean prop `fullWidth`. The BazaarOverviewCard now always use full height without fixed width. Also fixed problem with link to Bazaar. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.2.0-next.0 + +### Minor Changes + +- 67fe5bc9a9: BREAKING: Support authenticated backends by including a server token for catalog requests. The constructor of `GithubLocationAnalyzer` now requires an instance of `TokenManager` to be supplied: + + ```diff + ... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + + tokenManager: env.tokenManager, + }), + ); + ... + ``` + +- f64d66a45c: Added the ability for the GitHub discovery provider to validate that catalog files exist before emitting them. + + Users can now set the `validateLocationsExist` property to `true` in their GitHub discovery configuration to opt in to this feature. + This feature only works with `catalogPath`s that do not contain wildcards. + + When `validateLocationsExist` is set to `true`, the GitHub discovery provider will retrieve the object from the + repository at the provided `catalogPath`. + If this file exists and is non-empty, then it will be emitted as a location for further processing. + If this file does not exist or is empty, then it will not be emitted. + Not emitting locations that do not exist allows for far fewer calls to the GitHub API to validate locations that do not exist. + +### Patch Changes + +- 67fe5bc9a9: Properly derive Github credentials when making requests in `GithubLocationAnalyzer` to support Github App authentication +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-cost-insights@0.12.0-next.0 + +### Minor Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues + +### Patch Changes + +- cbe11d1e23: Tweak README +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## @backstage/plugin-github-issues@0.2.0-next.0 + +### Minor Changes + +- ead285b9e4: **BREAKING**: Changed the casing of all exported types to have a lowercase "h" in "github". E.g. "GitHubIssuesPage" was renamed to "GithubIssuesPage". Please rename your imports where necessary. + +### Patch Changes + +- c8dd2a8c87: Stripping specific issues URL already present to target base issues URL. +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-ilert@0.2.0-next.0 + +### Minor Changes + +- 0697af30da: Added support for multiple responders in alert list, added new tab with list to support iLert resource 'service', added new tab with list to support iLert resource 'status page' + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kubernetes-backend@0.8.0-next.0 + +### Minor Changes + +- cbf5d11fdf: The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + + - **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. + - Add the `PodStatusFetchResponse` to the `FetchResponse` union type. + - Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-kubernetes-common@0.4.4-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-scaffolder@1.8.0-next.0 + +### Minor Changes + +- edae17309e: Added props to override default Scaffolder page title, subtitle and pageTitleOverride. + Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component). + +### Patch Changes + +- 4830a3569f: Basic analytics instrumentation is now in place: + + - As users make their way through template steps, a `click` event is fired, including the step number. + - After a user clicks "Create" a `create` event is fired, including the name of the software that was just created. The template used at creation is set on the `entityRef` context key. + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + +## @backstage/plugin-scaffolder-backend@1.8.0-next.0 + +### Minor Changes + +- ea14eb62a2: Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels: + + - `scaffolder_task_count`: Tracks successful task runs. + + Labels: + + - `template`: The entity ref of the scaffolded template + - `user`: The entity ref of the user that invoked the template run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + - `scaffolder_task_duration`: a histogram which tracks the duration of a task run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `result`: A boolean describing whether the task ran successfully + + - `scaffolder_step_count`: a count that tracks each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + - `scaffolder_step_duration`: a histogram which tracks the duration of each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + You can find a guide for running Prometheus metrics here: + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + +## @backstage/plugin-techdocs@1.4.0-next.0 + +### Minor Changes + +- 5691baea69: Add ability to configure filters when using EntityListDocsGrid + + The following example will render two sections of cards grid: + + - One section for documentations tagged as `recommended` + - One section for documentations tagged as `runbook` + + ```js + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: "RunBooks Documentation", + filterPredicate: entity => + entity?.metadata?.tags?.includes('runbook') ?? false, + } + ]}} /> + ``` + +### Patch Changes + +- cbe11d1e23: Tweak README +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- 3a1a999b7b: Include query parameters when navigating to relative links in documents +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/app-defaults@1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/backend-app-api@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/backend-defaults@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + +## @backstage/backend-plugin-api@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/backend-tasks@0.3.7-next.0 + +### Patch Changes + +- 30e43717c7: Deprecated the `HumanDuration` type, which should now instead be imported from `@backstage/types`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/backend-test-utils@0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/backend-app-api@0.2.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/catalog-client@1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/catalog-model@1.1.3-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/config@1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## @backstage/config-loader@1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/create-app@0.4.33-next.0 + +### Patch Changes + +- 4091c73e68: Updated `@swc/core` to `v1.3.9` which fixes a `.tsx` parser bug. You may want to run `yarn backstage-cli versions:bump` to get on latest version including the CLI itself. +- 80bfac5266: Updated the create-app command to no longer require Git to be installed and configured. A git repository will only be initialized if possible and if not already in an git repository. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/dev-utils@1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/errors@1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## @backstage/integration-react@1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @techdocs/cli@1.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-techdocs-node@1.4.2-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + +## @backstage/test-utils@1.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/types@1.0.1-next.0 + +### Patch Changes + +- 30e43717c7: Added the `HumanDuration` type, moved here from `@backstage/backend-tasks`. This type matches the `Duration.fromObject` form of `luxon`. + +## @backstage/plugin-adr@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-adr-common@0.2.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-adr-backend@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-adr-common@0.2.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-adr-common@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-airbrake@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/dev-utils@1.0.8-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-airbrake-backend@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-allure@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-analytics-module-ga@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apache-airflow@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + +## @backstage/plugin-api-docs@0.8.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apollo-explorer@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-app-backend@0.3.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + +## @backstage/plugin-auth-backend@0.17.1-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-auth-node@0.2.7-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-azure-devops@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-azure-devops-backend@0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-badges@0.2.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-badges-backend@0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-bazaar-backend@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + +## @backstage/plugin-bitrise@0.1.38-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog@1.6.1-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-catalog-backend@1.5.1-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.1.11-next.0 + +### Patch Changes + +- bae3617be5: `AwsS3EntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- defb389ecd: Add `awsS3EntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.9-next.0 + +### Patch Changes + +- 87ff05892d: `AzureDevOpsEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- 0ca399b31b: Add `azureDevOpsEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.3-next.0 + +### Patch Changes + +- 68f7f5a857: `BitbucketServerEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- cd48ed8370: Add `bitbucketServerEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.6-next.0 + +### Patch Changes + +- 4fba50f5d4: Add `gerritEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- 134b69f478: `GerritEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.9-next.0 + +### Patch Changes + +- 6bb046bcbe: Add `gitlabDiscoveryEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- 81cedb5033: `GitlabDiscoveryEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.4.4-next.0 + +### Patch Changes + +- 8d1a5e08ca: `MicrosoftGraphOrgEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- 384f99c276: Add `microsoftGraphOrgEntityProviderCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.4-next.0 + +### Patch Changes + +- 4ce887400d: Added support to use the `UrlReaders` when `$ref` pointing to a URL. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-catalog-common@1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-catalog-graph@0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-graphql@0.3.15-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-catalog-import@0.9.1-next.0 + +### Patch Changes + +- 1e7b640518: Get rid of `this-is-undefined-in-esm` warning +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + +## @backstage/plugin-catalog-node@1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + +## @backstage/plugin-catalog-react@1.2.1-next.0 + +### Patch Changes + +- a889314692: Both `EntityProvider` and `AsyncEntityProvider` contexts now wrap all children with an `AnalyticsContext` containing the corresponding `entityRef`; this opens up the possibility for all events underneath these contexts to be associated with and aggregated by the corresponding entity. +- e47f466f80: Removed forced capitalization for Entity types in the catalog sidebar. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-catalog-common@1.0.8-next.0 + +## @backstage/plugin-cicd-statistics@0.1.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-cicd-statistics@0.1.13-next.0 + +## @backstage/plugin-circleci@0.3.11-next.0 + +### Patch Changes + +- 383574c49b: Update screenshots in documentation to match latest CircleCI plugin +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-cloudbuild@0.3.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-climate@0.1.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage@0.2.4-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage-backend@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-codescene@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-config-schema@0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-dynatrace@1.0.1-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore@0.3.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-explore-react@0.0.23-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore-react@0.0.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + +## @backstage/plugin-firehydrant@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-fossa@0.2.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcalendar@0.3.7-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcp-projects@0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-git-release-manager@0.3.24-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-actions@0.5.11-next.0 + +### Patch Changes + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-deployments@0.1.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-pull-requests-board@0.1.5-next.0 + +### Patch Changes + +- cc8bfc56c3: Add a new "Team" Filter Options to the Github Pull Requests Dashboard. + + When toggling this option on, the dashboard will displays all of the PRs opened + by the members of that team on any repositories of the organization. + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gitops-profiles@0.3.29-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gocd@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphiql@0.2.43-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphql-backend@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-catalog-graphql@0.3.15-next.0 + +## @backstage/plugin-home@0.4.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-stack-overflow@0.1.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins@0.7.10-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.10-next.0 + +## @backstage/plugin-jenkins-backend@0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-jenkins-common@0.1.10-next.0 + +## @backstage/plugin-jenkins-common@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + +## @backstage/plugin-kafka@0.3.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kafka-backend@0.2.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-kubernetes@0.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-kubernetes-common@0.4.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kubernetes-common@0.4.4-next.0 + +### Patch Changes + +- cbf5d11fdf: The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + + - **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. + - Add the `PodStatusFetchResponse` to the `FetchResponse` union type. + - Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + +## @backstage/plugin-lighthouse@0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic@0.3.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic-dashboard@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-org@0.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-pagerduty@0.5.4-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop@0.1.9-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop-backend@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-permission-backend@0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-permission-common@0.7.1-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 64848c963c: Properly handle rules that have no parameters in `PermissionClient` +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-permission-node@0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-permission-react@0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-playlist@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-playlist-common@0.1.2-next.0 + +## @backstage/plugin-playlist-backend@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-playlist-common@0.1.2-next.0 + +## @backstage/plugin-playlist-common@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + +## @backstage/plugin-proxy-backend@0.2.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-rollbar@0.4.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-rollbar-backend@0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + +## @backstage/plugin-scaffolder-common@1.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + +## @backstage/plugin-search@1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-backend@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-backend-module-pg@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-backend-node@1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-search-common@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + +## @backstage/plugin-search-react@1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-sentry@0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-shortcuts@0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube-backend@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-splunk-on-call@0.3.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow@0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-home@0.4.27-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-stack-overflow-backend@0.1.7-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/cli@0.21.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-tech-insights@0.3.2-next.0 + +### Patch Changes + +- 7095e8bc03: Fixed bug when sending data by Post in `runChecks` and `runBulkChecks` functions of the `TechInsightsClient` class, the default `Content-Type` used was `plain/text` +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## @backstage/plugin-tech-insights-backend@0.5.4-next.0 + +### Patch Changes + +- 06cf8f1cf2: Add a default delay to the fact retrievers to prevent cold-start errors +- 30e43717c7: Use `HumanDuration` from `@backstage/types` +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## @backstage/plugin-tech-insights-common@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## @backstage/plugin-tech-insights-node@0.3.6-next.0 + +### Patch Changes + +- 06cf8f1cf2: Add a default delay to the fact retrievers to prevent cold-start errors +- 30e43717c7: Use `HumanDuration` from `@backstage/types` +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## @backstage/plugin-tech-radar@0.5.18-next.0 + +### Patch Changes + +- 1f888af5f6: Fixed bug in Tech Radar where, on hover, the tech list quadrant would rerender and scroll top +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-backend@1.4.1-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-techdocs-node@1.4.2-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + +### Patch Changes + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-node@1.4.2-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## @backstage/plugin-techdocs-react@1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-todo@0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-todo-backend@0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-user-settings@0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-user-settings-backend@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-vault@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-vault-backend@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @backstage/plugin-xcmetrics@0.2.31-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- dcf9e728de: Removed an unused and hidden build details route. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## example-app@0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.8.0-next.0 + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-tech-insights@0.3.2-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-cost-insights@0.12.0-next.0 + - @backstage/plugin-dynatrace@1.0.1-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/plugin-catalog-import@0.9.1-next.0 + - @backstage/plugin-tech-radar@0.5.18-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-gcalendar@0.3.7-next.0 + - @backstage/plugin-code-coverage@0.2.4-next.0 + - @backstage/plugin-github-actions@0.5.11-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + - @backstage/plugin-circleci@0.3.11-next.0 + - @backstage/plugin-azure-sites@0.1.0-next.0 + - @backstage/plugin-cloudbuild@0.3.11-next.0 + - @backstage/plugin-jenkins@0.7.10-next.0 + - @backstage/plugin-kafka@0.3.11-next.0 + - @backstage/plugin-pagerduty@0.5.4-next.0 + - @backstage/plugin-rollbar@0.4.11-next.0 + - @backstage/plugin-airbrake@0.3.11-next.0 + - @backstage/plugin-api-docs@0.8.11-next.0 + - @backstage/plugin-azure-devops@0.2.2-next.0 + - @backstage/plugin-badges@0.2.35-next.0 + - @internal/plugin-catalog-customized@0.0.4-next.0 + - @backstage/plugin-catalog-graph@0.2.23-next.0 + - @backstage/plugin-explore@0.3.42-next.0 + - @backstage/plugin-gocd@0.1.17-next.0 + - @backstage/plugin-home@0.4.27-next.0 + - @backstage/plugin-kubernetes@0.7.4-next.0 + - @backstage/plugin-lighthouse@0.3.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.4-next.0 + - @backstage/plugin-org@0.5.11-next.0 + - @backstage/plugin-playlist@0.1.2-next.0 + - @backstage/plugin-search@1.0.4-next.0 + - @backstage/plugin-sentry@0.4.4-next.0 + - @backstage/plugin-todo@0.2.13-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-apache-airflow@0.2.4-next.0 + - @backstage/plugin-gcp-projects@0.3.30-next.0 + - @backstage/plugin-graphiql@0.2.43-next.0 + - @backstage/plugin-newrelic@0.3.29-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-shortcuts@0.3.3-next.0 + - @backstage/plugin-stack-overflow@0.1.7-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/plugin-user-settings@0.5.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## example-backend@0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/plugin-techdocs-backend@1.4.1-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.17.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-tech-insights-backend@0.5.4-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/plugin-azure-sites-backend@0.1.0-next.0 + - @backstage/plugin-kubernetes-backend@0.8.0-next.0 + - example-app@0.2.77-next.0 + - @backstage/plugin-app-backend@0.3.38-next.0 + - @backstage/plugin-azure-devops-backend@0.3.17-next.0 + - @backstage/plugin-badges-backend@0.1.32-next.0 + - @backstage/plugin-code-coverage-backend@0.2.4-next.0 + - @backstage/plugin-graphql-backend@0.1.28-next.0 + - @backstage/plugin-jenkins-backend@0.1.28-next.0 + - @backstage/plugin-kafka-backend@0.2.31-next.0 + - @backstage/plugin-permission-backend@0.5.13-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-playlist-backend@0.2.1-next.0 + - @backstage/plugin-proxy-backend@0.2.32-next.0 + - @backstage/plugin-rollbar-backend@0.1.35-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.0 + - @backstage/plugin-search-backend@1.1.1-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.2-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.0 + - @backstage/plugin-todo-backend@0.1.35-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## example-backend-next@0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/plugin-app-backend@0.3.38-next.0 + - @backstage/backend-defaults@0.1.3-next.0 + +## techdocs-cli-embedded-app@0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## @internal/plugin-catalog-customized@0.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + +## @internal/plugin-todo-list@1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## @internal/plugin-todo-list-backend@1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## @internal/plugin-todo-list-common@1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 diff --git a/docs/tutorials/react-router-stable-migration.md b/docs/tutorials/react-router-stable-migration.md index 9636345116..bb01157bdb 100644 --- a/docs/tutorials/react-router-stable-migration.md +++ b/docs/tutorials/react-router-stable-migration.md @@ -178,7 +178,7 @@ When migrating over to React Router v6 stable, you might also see browser consol ```diff - -+ } /> ++ } /> ``` ### `NavLink` diff --git a/docs/tutorials/switching-sqlite-postgres.md b/docs/tutorials/switching-sqlite-postgres.md index c0c35999c6..bd067ee388 100644 --- a/docs/tutorials/switching-sqlite-postgres.md +++ b/docs/tutorials/switching-sqlite-postgres.md @@ -72,6 +72,12 @@ backend: + port: ${POSTGRES_PORT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt + # Refer to Tarn docs for default values on PostgreSQL pool configuration - https://github.com/Vincit/tarn.js + knexConfig: + pool: @@ -79,12 +85,6 @@ backend: + max: 12 + acquireTimeoutMillis: 60000 + idleTimeoutMillis: 60000 -+ # https://node-postgres.com/features/ssl -+ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable -+ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) -+ # ssl: -+ # ca: # if you have a CA file and want to verify it you can uncomment this section -+ # $file: /ca/server.crt ``` ### Using a single database diff --git a/microsite/README.md b/microsite/README.md index 285622f1f8..723baa1a54 100644 --- a/microsite/README.md +++ b/microsite/README.md @@ -167,7 +167,7 @@ For more information about blog posts, click [here](https://docusaurus.io/docs/e ### 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`: +1. Add links to docs, custom pages or external links by editing the `headerLinks` field of `website/siteConfig.js`: `website/siteConfig.js` diff --git a/microsite/data/plugins/adr.yaml b/microsite/data/plugins/adr.yaml index f180c80e36..fabb26e685 100644 --- a/microsite/data/plugins/adr.yaml +++ b/microsite/data/plugins/adr.yaml @@ -7,3 +7,4 @@ description: Browse your project's ADRs. documentation: https://github.com/backstage/backstage/tree/master/plugins/adr iconUrl: img/adr-logo.png npmPackageName: '@backstage/plugin-adr' +addedDate: '2022-04-13' diff --git a/microsite/data/plugins/airbrake.yaml b/microsite/data/plugins/airbrake.yaml index 29c1f394d7..7f946597ef 100644 --- a/microsite/data/plugins/airbrake.yaml +++ b/microsite/data/plugins/airbrake.yaml @@ -7,3 +7,4 @@ description: Access Airbrake error monitoring and other integrations from within documentation: https://github.com/backstage/backstage/blob/master/plugins/airbrake iconUrl: https://wp-assets.airbrake.io/wp-content/uploads/2020/10/05222904/Square-white-A-on-Orange.png npmPackageName: '@backstage/plugin-airbrake' +addedDate: '2022-01-10' diff --git a/microsite/data/plugins/allure.yaml b/microsite/data/plugins/allure.yaml index defa7158c0..fc21849268 100644 --- a/microsite/data/plugins/allure.yaml +++ b/microsite/data/plugins/allure.yaml @@ -7,3 +7,4 @@ description: View Allure reports for your components in Backstage. documentation: https://github.com/backstage/backstage/tree/master/plugins/allure iconUrl: https://avatars.githubusercontent.com/u/5879127 npmPackageName: '@backstage/plugin-allure' +addedDate: '2021-09-02' diff --git a/microsite/data/plugins/api-docs.yaml b/microsite/data/plugins/api-docs.yaml index 03ec818445..645e32c54b 100644 --- a/microsite/data/plugins/api-docs.yaml +++ b/microsite/data/plugins/api-docs.yaml @@ -7,3 +7,4 @@ description: Components to discover and display API entities as an extension to documentation: https://github.com/backstage/backstage/blob/master/plugins/api-docs/README.md iconUrl: https://raw.githubusercontent.com/vscode-icons/vscode-icons/master/icons/file_type_swagger.svg npmPackageName: '@backstage/plugin-api-docs' +addedDate: '2020-11-19' diff --git a/microsite/data/plugins/apollo-explorer.yaml b/microsite/data/plugins/apollo-explorer.yaml index 068e26f0dc..1a95eaa035 100644 --- a/microsite/data/plugins/apollo-explorer.yaml +++ b/microsite/data/plugins/apollo-explorer.yaml @@ -7,3 +7,4 @@ description: Integrates Apollo Explorer graphs as a tool to browse GraphQL API e documentation: https://github.com/backstage/backstage/blob/master/plugins/apollo-explorer/README.md iconUrl: img/apollo-explorer.png npmPackageName: '@backstage/plugin-apollo-explorer' +addedDate: '2022-07-20' diff --git a/microsite/data/plugins/argo-cd.yaml b/microsite/data/plugins/argo-cd.yaml index 121c833cb4..daac6dabd1 100644 --- a/microsite/data/plugins/argo-cd.yaml +++ b/microsite/data/plugins/argo-cd.yaml @@ -10,3 +10,4 @@ npmPackageName: '@roadiehq/backstage-plugin-argo-cd' tags: - cd - ci +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/aws-cloudformation.yaml b/microsite/data/plugins/aws-cloudformation.yaml index 7f0feee143..6ee36e82b0 100644 --- a/microsite/data/plugins/aws-cloudformation.yaml +++ b/microsite/data/plugins/aws-cloudformation.yaml @@ -7,3 +7,4 @@ description: Load Backstage entities from AWS CloudFormation stacks documentation: https://github.com/purple-technology/backstage-aws-cloudformation-plugin#readme iconUrl: https://raw.githubusercontent.com/purple-technology/backstage-aws-cloudformation-plugin/master/docs/cloudformation-logo.png npmPackageName: 'backstage-aws-cloudformation-plugin' +addedDate: '2021-08-30' diff --git a/microsite/data/plugins/aws-lambda.yaml b/microsite/data/plugins/aws-lambda.yaml index f20cc0d393..80cebd06a8 100644 --- a/microsite/data/plugins/aws-lambda.yaml +++ b/microsite/data/plugins/aws-lambda.yaml @@ -7,3 +7,4 @@ description: View AWS Lambda functions for your components in Backstage. documentation: https://roadie.io/backstage/plugins/aws-lambda/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=aws-lambda iconUrl: https://roadie.io/images/logos/lambda.png npmPackageName: '@roadiehq/backstage-plugin-aws-lambda' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/aws-proton.yaml b/microsite/data/plugins/aws-proton.yaml index 022b553b24..f66b556d42 100644 --- a/microsite/data/plugins/aws-proton.yaml +++ b/microsite/data/plugins/aws-proton.yaml @@ -7,3 +7,4 @@ description: Create and view AWS Proton services for your components in Backstag documentation: https://github.com/awslabs/aws-proton-plugins-for-backstage#readme iconUrl: https://github.com/awslabs/aws-proton-plugins-for-backstage/blob/main/docs/images/proton-logo.png?raw=true npmPackageName: '@aws/aws-proton-plugin-for-backstage' +addedDate: '2022-06-21' diff --git a/microsite/data/plugins/azure-pipelines.yaml b/microsite/data/plugins/azure-pipelines.yaml index c591591478..d4caa90507 100644 --- a/microsite/data/plugins/azure-pipelines.yaml +++ b/microsite/data/plugins/azure-pipelines.yaml @@ -7,3 +7,4 @@ description: Easily view your Azure Pipelines within the Software Catalog documentation: https://github.com/backstage/backstage/blob/master/plugins/azure-devops/README.md iconUrl: img/azure-pipelines.svg npmPackageName: '@backstage/plugin-azure-devops' +addedDate: '2021-12-22' diff --git a/microsite/data/plugins/azure-resources.yaml b/microsite/data/plugins/azure-resources.yaml index 2cc5665395..a6cb389b0a 100644 --- a/microsite/data/plugins/azure-resources.yaml +++ b/microsite/data/plugins/azure-resources.yaml @@ -6,3 +6,4 @@ category: Infrastructure description: A plugin showing Azure resource groups and security recommendations in relation to an entity in the catalog. documentation: https://github.com/vippsas/backstage-azure-resource-frontend npmPackageName: '@vippsno/plugin-azure-resources' +addedDate: '2022-09-05' diff --git a/microsite/data/plugins/azure-sites.yaml b/microsite/data/plugins/azure-sites.yaml new file mode 100644 index 0000000000..70e586f30e --- /dev/null +++ b/microsite/data/plugins/azure-sites.yaml @@ -0,0 +1,9 @@ +--- +title: Azure Sites +author: FRISS +authorUrl: https://friss.com +category: Infrastructure +description: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. +documentation: https://github.com/backstage/backstage/tree/master/plugins/azure-sites +npmPackageName: '@backstage/plugin-azure-sites' +addedDate: '2022-10-18' diff --git a/microsite/data/plugins/backstage-analytics-module-ga.yaml b/microsite/data/plugins/backstage-analytics-module-ga.yaml index 0a03ec20e8..051345d43b 100644 --- a/microsite/data/plugins/backstage-analytics-module-ga.yaml +++ b/microsite/data/plugins/backstage-analytics-module-ga.yaml @@ -7,3 +7,4 @@ description: Track usage of your Backstage instance using Google Analytics. documentation: https://github.com/backstage/backstage/blob/master/plugins/analytics-module-ga/README.md iconUrl: img/ga-icon.png npmPackageName: '@backstage/plugin-analytics-module-ga' +addedDate: '2021-10-07' diff --git a/microsite/data/plugins/backstage-kubernetes.yaml b/microsite/data/plugins/backstage-kubernetes.yaml index c821d034c5..a070ea99b0 100644 --- a/microsite/data/plugins/backstage-kubernetes.yaml +++ b/microsite/data/plugins/backstage-kubernetes.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/kubernetes/overview iconUrl: img/backstage-k8s.svg npmPackageName: '@backstage/plugin-kubernetes' order: 4 +addedDate: '2021-03-29' diff --git a/microsite/data/plugins/backstage-plugin-api-linter b/microsite/data/plugins/backstage-plugin-api-linter.yaml similarity index 100% rename from microsite/data/plugins/backstage-plugin-api-linter rename to microsite/data/plugins/backstage-plugin-api-linter.yaml diff --git a/microsite/data/plugins/backstage-search-platform.yaml b/microsite/data/plugins/backstage-search-platform.yaml index 18bbbd1fd1..5a313ae9da 100644 --- a/microsite/data/plugins/backstage-search-platform.yaml +++ b/microsite/data/plugins/backstage-search-platform.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/software-catalog/software-cata iconUrl: img/backstage-search-platform.svg npmPackageName: '@backstage/plugin-search' order: 5 +addedDate: '2022-09-07' diff --git a/microsite/data/plugins/backstage-software-catalog.yaml b/microsite/data/plugins/backstage-software-catalog.yaml index ae16550297..e633e17e8e 100644 --- a/microsite/data/plugins/backstage-software-catalog.yaml +++ b/microsite/data/plugins/backstage-software-catalog.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/software-catalog/software-cata iconUrl: img/backstage-software-catalog.svg npmPackageName: '@backstage/plugin-catalog' order: 1 +addedDate: '2021-06-17' diff --git a/microsite/data/plugins/backstage-software-templates.yaml b/microsite/data/plugins/backstage-software-templates.yaml index 4d478f9435..f111ee30ce 100644 --- a/microsite/data/plugins/backstage-software-templates.yaml +++ b/microsite/data/plugins/backstage-software-templates.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/software-templates/software-te iconUrl: img/backstage-software-templates.svg npmPackageName: '@backstage/plugin-scaffolder' order: 2 +addedDate: '2021-03-29' diff --git a/microsite/data/plugins/backstage-techdocs.yaml b/microsite/data/plugins/backstage-techdocs.yaml index cd2ddefed4..8d89ac5060 100644 --- a/microsite/data/plugins/backstage-techdocs.yaml +++ b/microsite/data/plugins/backstage-techdocs.yaml @@ -8,3 +8,4 @@ documentation: https://backstage.io/docs/features/techdocs/techdocs-overview iconUrl: img/backstage-techdocs.svg npmPackageName: '@backstage/plugin-techdocs' order: 3 +addedDate: '2021-03-29' diff --git a/microsite/data/plugins/badges.yaml b/microsite/data/plugins/badges.yaml index 646a519b25..a5f397843d 100644 --- a/microsite/data/plugins/badges.yaml +++ b/microsite/data/plugins/badges.yaml @@ -7,3 +7,4 @@ description: The badges plugin offers a set of badges that can be used outside o documentation: https://github.com/backstage/backstage/blob/master/plugins/badges/README.md iconUrl: img/badges.svg npmPackageName: '@backstage/plugin-badges' +addedDate: '2021-09-29' diff --git a/microsite/data/plugins/bazaar.yaml b/microsite/data/plugins/bazaar.yaml index 6e27692d78..c408229be2 100644 --- a/microsite/data/plugins/bazaar.yaml +++ b/microsite/data/plugins/bazaar.yaml @@ -6,3 +6,4 @@ description: A marketplace where engineers can propose projects suitable for inn documentation: https://github.com/backstage/backstage/blob/master/plugins/bazaar/README.md iconUrl: img/bazaar.svg npmPackageName: '@backstage/plugin-bazaar' +addedDate: '2022-01-11' diff --git a/microsite/data/plugins/bitrise.yaml b/microsite/data/plugins/bitrise.yaml index 2a362a9d0f..5e5b74f3ad 100644 --- a/microsite/data/plugins/bitrise.yaml +++ b/microsite/data/plugins/bitrise.yaml @@ -7,3 +7,4 @@ description: View Bitrise builds and download the build artifacts within Backsta documentation: https://github.com/backstage/backstage/blob/master/plugins/bitrise/README.md iconUrl: https://avatars.githubusercontent.com/u/7174390?s=400&v=4 npmPackageName: '@backstage/plugin-bitrise' +addedDate: '2021-03-01' diff --git a/microsite/data/plugins/bugsnag.yaml b/microsite/data/plugins/bugsnag.yaml index 8f36153382..dc593ce5e8 100644 --- a/microsite/data/plugins/bugsnag.yaml +++ b/microsite/data/plugins/bugsnag.yaml @@ -7,3 +7,4 @@ description: View and monitor Bugsnag errors. documentation: https://roadie.io/backstage/plugins/bugsnag/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=bugsnag iconUrl: https://roadie.io/images/logos/bugsnag.png npmPackageName: '@roadiehq/backstage-plugin-bugsnag' +addedDate: '2021-09-24' diff --git a/microsite/data/plugins/buildkite.yaml b/microsite/data/plugins/buildkite.yaml index 4e9b1594d3..5713b6b9e4 100644 --- a/microsite/data/plugins/buildkite.yaml +++ b/microsite/data/plugins/buildkite.yaml @@ -10,3 +10,4 @@ npmPackageName: '@roadiehq/backstage-plugin-buildkite' tags: - ci - cd +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/catalog-graph.yaml b/microsite/data/plugins/catalog-graph.yaml index 336954b5dc..6fd7214c4c 100644 --- a/microsite/data/plugins/catalog-graph.yaml +++ b/microsite/data/plugins/catalog-graph.yaml @@ -7,3 +7,4 @@ description: Extend the Backstage Software Catalog with a graph that shows all e documentation: https://github.com/backstage/backstage/blob/master/plugins/catalog-graph/README.md iconUrl: img/catalog-graph.svg npmPackageName: '@backstage/plugin-catalog-graph' +addedDate: '2021-09-15' diff --git a/microsite/data/plugins/circleci.yaml b/microsite/data/plugins/circleci.yaml index cffbedca37..64582440ec 100644 --- a/microsite/data/plugins/circleci.yaml +++ b/microsite/data/plugins/circleci.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-circleci' tags: - ci - cd +addedDate: '2021-04-28' diff --git a/microsite/data/plugins/cloud-build.yaml b/microsite/data/plugins/cloud-build.yaml index 253a935392..67ef13011b 100644 --- a/microsite/data/plugins/cloud-build.yaml +++ b/microsite/data/plugins/cloud-build.yaml @@ -11,3 +11,4 @@ tags: - ci - cd - test +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/cloud-carbon-footprint.yaml b/microsite/data/plugins/cloud-carbon-footprint.yaml index 5a50d02fe3..2a213644ad 100644 --- a/microsite/data/plugins/cloud-carbon-footprint.yaml +++ b/microsite/data/plugins/cloud-carbon-footprint.yaml @@ -13,3 +13,4 @@ tags: - climate - carbon-emissions - carbon-footprint +addedDate: '2022-05-03' diff --git a/microsite/data/plugins/cloudify.yaml b/microsite/data/plugins/cloudify.yaml index e5bcbe1545..93b7a26423 100644 --- a/microsite/data/plugins/cloudify.yaml +++ b/microsite/data/plugins/cloudify.yaml @@ -7,3 +7,4 @@ description: Cloudify provides a remote execution and environment management bac documentation: https://github.com/cloudify-cosmo/backstage-cloudify-plugin#readme iconUrl: https://avatars.githubusercontent.com/u/6260555?s=200&v=4 npmPackageName: 'plugin-cloudify' +addedDate: '2022-05-31' diff --git a/microsite/data/plugins/codescene.yaml b/microsite/data/plugins/codescene.yaml index 4a7c224cf1..bca23fc1fa 100644 --- a/microsite/data/plugins/codescene.yaml +++ b/microsite/data/plugins/codescene.yaml @@ -7,3 +7,4 @@ description: CodeScene is a multi-purpose tool bridging code, business and peopl documentation: https://github.com/backstage/backstage/tree/master/plugins/codescene iconUrl: img/codescene_logo.svg npmPackageName: '@backstage/plugin-codescene' +addedDate: '2022-04-12' diff --git a/microsite/data/plugins/cortex.yaml b/microsite/data/plugins/cortex.yaml index 1f1c57adfd..bf480ccfcd 100644 --- a/microsite/data/plugins/cortex.yaml +++ b/microsite/data/plugins/cortex.yaml @@ -11,3 +11,4 @@ tags: - web - monitoring - sre +addedDate: '2021-06-03' diff --git a/microsite/data/plugins/cost-insights.yaml b/microsite/data/plugins/cost-insights.yaml index ec2d162b9d..824769c174 100644 --- a/microsite/data/plugins/cost-insights.yaml +++ b/microsite/data/plugins/cost-insights.yaml @@ -9,3 +9,4 @@ iconUrl: img/cost-insights.png npmPackageName: '@backstage/plugin-cost-insights' tags: - web +addedDate: '2021-04-28' diff --git a/microsite/data/plugins/datadog.yaml b/microsite/data/plugins/datadog.yaml index 266627009c..0a720698f9 100644 --- a/microsite/data/plugins/datadog.yaml +++ b/microsite/data/plugins/datadog.yaml @@ -7,3 +7,4 @@ description: Embed Datadog graphs and dashboards in Backstage. documentation: https://roadie.io/backstage/plugins/datadog/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=datadog iconUrl: https://roadie.io/images/logos/datadog-white-background.png npmPackageName: '@roadiehq/backstage-plugin-datadog' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/dora-metrics.yaml b/microsite/data/plugins/dora-metrics.yaml index b94499e324..65c2e4fe6e 100644 --- a/microsite/data/plugins/dora-metrics.yaml +++ b/microsite/data/plugins/dora-metrics.yaml @@ -7,3 +7,4 @@ description: Embed dashboards (like DORA metrics) in your team or service pages documentation: https://github.com/OkayHQ/backstage-plugin iconUrl: img/okay.png npmPackageName: '@okayhq/backstage-plugin' +addedDate: '2022-06-16' diff --git a/microsite/data/plugins/dynatrace.yaml b/microsite/data/plugins/dynatrace.yaml index f204c4105c..b68d993e4b 100644 --- a/microsite/data/plugins/dynatrace.yaml +++ b/microsite/data/plugins/dynatrace.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-dynatrace' tags: - dynatrace - monitoring +addedDate: '2022-06-23' diff --git a/microsite/data/plugins/firebase-functions.yaml b/microsite/data/plugins/firebase-functions.yaml index 0b98948f88..176cfc1360 100644 --- a/microsite/data/plugins/firebase-functions.yaml +++ b/microsite/data/plugins/firebase-functions.yaml @@ -7,3 +7,4 @@ description: View Firebase Functions details for your service in Backstage. documentation: https://roadie.io/backstage/plugins/firebase-functions/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=firebase-functions iconUrl: https://roadie.io/images/logos/firebase.png npmPackageName: '@roadiehq/backstage-plugin-firebase-functions' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/firehydrant.yaml b/microsite/data/plugins/firehydrant.yaml index e4dd5e7416..b52357e808 100644 --- a/microsite/data/plugins/firehydrant.yaml +++ b/microsite/data/plugins/firehydrant.yaml @@ -7,3 +7,4 @@ description: View service incidents information from FireHydrant, such as active documentation: https://github.com/backstage/backstage/blob/master/plugins/firehydrant/README.md iconUrl: https://raw.githubusercontent.com/backstage/backstage/master/plugins/firehydrant/doc/firehydrant_logo.png npmPackageName: '@backstage/plugin-firehydrant' +addedDate: '2021-08-18' diff --git a/microsite/data/plugins/fossa.yaml b/microsite/data/plugins/fossa.yaml index c49e89c994..e46228a848 100644 --- a/microsite/data/plugins/fossa.yaml +++ b/microsite/data/plugins/fossa.yaml @@ -7,3 +7,4 @@ description: View FOSSA license compliance of your components in Backstage. documentation: https://github.com/backstage/backstage/blob/master/plugins/fossa/README.md iconUrl: https://avatars0.githubusercontent.com/u/9543448?s=400&v=4 npmPackageName: '@backstage/plugin-fossa' +addedDate: '2020-12-10' diff --git a/microsite/data/plugins/gcp-projects.yaml b/microsite/data/plugins/gcp-projects.yaml index 7c4b75d2a4..02e66db27e 100644 --- a/microsite/data/plugins/gcp-projects.yaml +++ b/microsite/data/plugins/gcp-projects.yaml @@ -11,3 +11,4 @@ tags: - cloud - project - resources +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/git-release-manager.yaml b/microsite/data/plugins/git-release-manager.yaml index 9c2372a6cb..e7000be694 100644 --- a/microsite/data/plugins/git-release-manager.yaml +++ b/microsite/data/plugins/git-release-manager.yaml @@ -1,9 +1,10 @@ --- title: GitHub Release Manager -author: '@Spotify' +author: Spotify authorUrl: https://github.com/spotify category: Release management description: Manage releases without having to juggle git commands. documentation: https://github.com/backstage/backstage/tree/master/plugins/git-release-manager iconUrl: img/git-release-manager-logo.svg npmPackageName: '@backstage/plugin-git-release-manager' +addedDate: '2021-10-04' diff --git a/microsite/data/plugins/github-actions.yaml b/microsite/data/plugins/github-actions.yaml index 8d5a74d9ce..82611c3933 100644 --- a/microsite/data/plugins/github-actions.yaml +++ b/microsite/data/plugins/github-actions.yaml @@ -11,3 +11,4 @@ tags: - ci - cd - github +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/github-insights.yaml b/microsite/data/plugins/github-insights.yaml index 3515065916..87a636f006 100644 --- a/microsite/data/plugins/github-insights.yaml +++ b/microsite/data/plugins/github-insights.yaml @@ -7,3 +7,4 @@ description: View GitHub Insights for your components in Backstage. documentation: https://roadie.io/backstage/plugins/github-insights/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=github-insights iconUrl: https://roadie.io/images/logos/insights.png npmPackageName: '@roadiehq/backstage-plugin-github-insights' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/github-pull-requests-board.yaml b/microsite/data/plugins/github-pull-requests-board.yaml index fa710f5fc8..0c3435b581 100644 --- a/microsite/data/plugins/github-pull-requests-board.yaml +++ b/microsite/data/plugins/github-pull-requests-board.yaml @@ -7,3 +7,4 @@ description: View all open GitHub pull requests owned by your team in Backstage. documentation: https://github.com/backstage/backstage/tree/master/plugins/github-pull-requests-board iconUrl: img/github-pull-requests-board-logo.svg npmPackageName: '@backstage/plugin-github-pull-requests-board' +addedDate: '2022-05-10' diff --git a/microsite/data/plugins/github-pull-requests.yaml b/microsite/data/plugins/github-pull-requests.yaml index 8fc0dd5a6e..57276a634b 100644 --- a/microsite/data/plugins/github-pull-requests.yaml +++ b/microsite/data/plugins/github-pull-requests.yaml @@ -7,3 +7,4 @@ description: View GitHub pull requests for your service in Backstage. documentation: https://roadie.io/backstage/plugins/github-pull-requests/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=github-pull-requests iconUrl: https://roadie.io/images/logos/github.png npmPackageName: '@roadiehq/backstage-plugin-github-pull-requests' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/gitlab.yaml b/microsite/data/plugins/gitlab.yaml index 7a5493c7f4..689e8004fd 100644 --- a/microsite/data/plugins/gitlab.yaml +++ b/microsite/data/plugins/gitlab.yaml @@ -10,3 +10,4 @@ npmPackageName: '@loblaw/backstage-plugin-gitlab' tags: - ci - cd +addedDate: '2021-08-17' diff --git a/microsite/data/plugins/gitops-cluster.yaml b/microsite/data/plugins/gitops-cluster.yaml index ad6e7979aa..1ebe4c40f8 100644 --- a/microsite/data/plugins/gitops-cluster.yaml +++ b/microsite/data/plugins/gitops-cluster.yaml @@ -12,3 +12,4 @@ tags: - gitops - github - eks +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/gke-usage.yaml b/microsite/data/plugins/gke-usage.yaml deleted file mode 100644 index 9668b18fdf..0000000000 --- a/microsite/data/plugins/gke-usage.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: GKE Usage -author: BESTSELLER -authorUrl: https://bestsellerit.com -category: Discovery -description: This plugin will show you the cost and resource usage of your application within Google Kubernetes Engine (GKE). -documentation: https://github.com/BESTSELLER/backstage-plugin-gkeusage/blob/master/README.md -iconUrl: img/gke-logo.png -npmPackageName: '@bestsellerit/backstage-plugin-gkeusage' -tags: - - gke - - cost - - google - - usage - - metering diff --git a/microsite/data/plugins/gocd.yaml b/microsite/data/plugins/gocd.yaml index 7c7190effc..1e5f4c3acb 100644 --- a/microsite/data/plugins/gocd.yaml +++ b/microsite/data/plugins/gocd.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-gocd' tags: - ci - cd +addedDate: '2022-01-15' diff --git a/microsite/data/plugins/grafana.yaml b/microsite/data/plugins/grafana.yaml index ff4e33e3df..c26d10b751 100644 --- a/microsite/data/plugins/grafana.yaml +++ b/microsite/data/plugins/grafana.yaml @@ -11,3 +11,4 @@ tags: - dashboards - monitoring - alerting +addedDate: '2021-10-11' diff --git a/microsite/data/plugins/graphiql.yaml b/microsite/data/plugins/graphiql.yaml index eda91206eb..4cff5ca8ba 100644 --- a/microsite/data/plugins/graphiql.yaml +++ b/microsite/data/plugins/graphiql.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-graphiql' tags: - graphql - graphiql +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/grpc-playground.yaml b/microsite/data/plugins/grpc-playground.yaml index 097759be11..b0f12d4aa2 100644 --- a/microsite/data/plugins/grpc-playground.yaml +++ b/microsite/data/plugins/grpc-playground.yaml @@ -7,3 +7,4 @@ description: Easily view and test your gRPC API with a GUI Client, inspired from documentation: https://github.com/zalopay-oss/backstage-grpc-playground iconUrl: https://raw.githubusercontent.com/zalopay-oss/backstage-grpc-playground/main/images/gprc-logo.png npmPackageName: 'backstage-grpc-playground' +addedDate: '2022-06-08' diff --git a/microsite/data/plugins/harbor.yaml b/microsite/data/plugins/harbor.yaml index f959613d4c..5a142b46fa 100644 --- a/microsite/data/plugins/harbor.yaml +++ b/microsite/data/plugins/harbor.yaml @@ -11,3 +11,4 @@ tags: - goharbor - harbor - docker +addedDate: '2022-06-23' diff --git a/microsite/data/plugins/home.yaml b/microsite/data/plugins/home.yaml index e37b8c5e03..cf67f6b43f 100644 --- a/microsite/data/plugins/home.yaml +++ b/microsite/data/plugins/home.yaml @@ -7,3 +7,4 @@ description: This plugin provides a composable home page, and ability to create documentation: https://github.com/backstage/backstage/blob/master/plugins/home/README.md iconUrl: img/home.png npmPackageName: '@backstage/plugin-home' +addedDate: '2021-08-31' diff --git a/microsite/data/plugins/humanitec.yaml b/microsite/data/plugins/humanitec.yaml index bd5dec9dc6..5b3c459116 100644 --- a/microsite/data/plugins/humanitec.yaml +++ b/microsite/data/plugins/humanitec.yaml @@ -9,3 +9,4 @@ description: | documentation: https://github.com/thefrontside/backstage/tree/main/plugins/humanitec iconUrl: img/humanitec-logo.png npmPackageName: '@frontside/backstage-plugin-humanitec' +addedDate: '2022-06-22' diff --git a/microsite/data/plugins/ilert.yaml b/microsite/data/plugins/ilert.yaml index 29cc4f6aa5..feb54e2d6e 100644 --- a/microsite/data/plugins/ilert.yaml +++ b/microsite/data/plugins/ilert.yaml @@ -13,3 +13,4 @@ tags: - alerting - uptime - on-call +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/jenkins.yaml b/microsite/data/plugins/jenkins.yaml index 7a8f884221..0b1c4d3bf4 100644 --- a/microsite/data/plugins/jenkins.yaml +++ b/microsite/data/plugins/jenkins.yaml @@ -10,3 +10,4 @@ npmPackageName: '@backstage/plugin-jenkins' tags: - ci - cd +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/jira.yaml b/microsite/data/plugins/jira.yaml index 3342a2594c..6c803bcd68 100644 --- a/microsite/data/plugins/jira.yaml +++ b/microsite/data/plugins/jira.yaml @@ -7,3 +7,4 @@ description: View Jira summary for your projects in Backstage. documentation: https://roadie.io/backstage/plugins/jira/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=jira iconUrl: https://roadie.io/images/logos/jira.png npmPackageName: '@roadiehq/backstage-plugin-jira' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/kafka.yaml b/microsite/data/plugins/kafka.yaml index 47d354b44f..9546f8e2fc 100644 --- a/microsite/data/plugins/kafka.yaml +++ b/microsite/data/plugins/kafka.yaml @@ -9,3 +9,4 @@ iconUrl: https://kafka.apache.org/images/apache-kafka.png npmPackageName: '@backstage/plugin-kafka' tags: - monitoring +addedDate: '2021-01-21' diff --git a/microsite/data/plugins/kpt-config-as-data.yaml b/microsite/data/plugins/kpt-config-as-data.yaml index 03d5d66947..2ed67262a8 100644 --- a/microsite/data/plugins/kpt-config-as-data.yaml +++ b/microsite/data/plugins/kpt-config-as-data.yaml @@ -7,3 +7,4 @@ description: Configuration GUI over GitOps using kpt, with WYSIWYG editing, revi documentation: https://github.com/GoogleContainerTools/kpt-backstage-plugins/tree/main/plugins/cad iconUrl: https://github.com/GoogleContainerTools/kpt/blob/main/logo/KptLogoSmall.png?raw=true npmPackageName: '@kpt/backstage-plugin-cad' +addedDate: '2022-05-13' diff --git a/microsite/data/plugins/lighthouse.yaml b/microsite/data/plugins/lighthouse.yaml index be70dfd74a..4f302cdb3b 100644 --- a/microsite/data/plugins/lighthouse.yaml +++ b/microsite/data/plugins/lighthouse.yaml @@ -12,3 +12,4 @@ tags: - seo - accessibility - performance +addedDate: '2021-01-20' diff --git a/microsite/data/plugins/new-relic.yaml b/microsite/data/plugins/new-relic.yaml index d728a9d1c7..a68cc90314 100644 --- a/microsite/data/plugins/new-relic.yaml +++ b/microsite/data/plugins/new-relic.yaml @@ -12,3 +12,4 @@ tags: - monitoring - errors - alerting +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/newrelic-dashboard.yaml b/microsite/data/plugins/newrelic-dashboard.yaml index c314d4c458..bbb1c4be7e 100644 --- a/microsite/data/plugins/newrelic-dashboard.yaml +++ b/microsite/data/plugins/newrelic-dashboard.yaml @@ -13,3 +13,4 @@ tags: - errors - alerting - dashboards +addedDate: '2021-12-23' diff --git a/microsite/data/plugins/okta-entity-providers.yaml b/microsite/data/plugins/okta-entity-providers.yaml index 93e4473fde..047992e785 100644 --- a/microsite/data/plugins/okta-entity-providers.yaml +++ b/microsite/data/plugins/okta-entity-providers.yaml @@ -7,3 +7,4 @@ description: Load users and groups from Okta into the Backstage catalog. documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/tree/main/plugins/backend/catalog-backend-module-okta iconUrl: https://roadie.io/images/logos/okta.png npmPackageName: '@roadiehq/catalog-backend-module-okta' +addedDate: '2022-07-26' diff --git a/microsite/data/plugins/opsgenie.yaml b/microsite/data/plugins/opsgenie.yaml index 3db06c04c8..2a7818f7da 100644 --- a/microsite/data/plugins/opsgenie.yaml +++ b/microsite/data/plugins/opsgenie.yaml @@ -11,3 +11,4 @@ tags: - monitoring - errors - alerting +addedDate: '2022-03-13' diff --git a/microsite/data/plugins/pager-duty.yaml b/microsite/data/plugins/pager-duty.yaml index d0006c09ec..a4e6e42d47 100644 --- a/microsite/data/plugins/pager-duty.yaml +++ b/microsite/data/plugins/pager-duty.yaml @@ -11,3 +11,4 @@ tags: - monitoring - errors - alerting +addedDate: '2020-12-22' diff --git a/microsite/data/plugins/periskop.yaml b/microsite/data/plugins/periskop.yaml index cce8d0dde5..6b6693b3ef 100644 --- a/microsite/data/plugins/periskop.yaml +++ b/microsite/data/plugins/periskop.yaml @@ -7,3 +7,4 @@ description: Periskop is a pull-based, language agnostic exception aggregator fo documentation: https://github.com/backstage/backstage/tree/master/plugins/periskop iconUrl: https://raw.githubusercontent.com/periskop-dev/periskop/master/docs/assets/periskop-logo.png npmPackageName: '@backstage/plugin-periskop' +addedDate: '2022-02-25' diff --git a/microsite/data/plugins/playlist.yaml b/microsite/data/plugins/playlist.yaml index 3d69103d69..7d9c13082d 100644 --- a/microsite/data/plugins/playlist.yaml +++ b/microsite/data/plugins/playlist.yaml @@ -7,3 +7,4 @@ description: Create, share, and follow custom collections of entities available documentation: https://github.com/backstage/backstage/tree/master/plugins/playlist iconUrl: img/playlist-logo.png npmPackageName: '@backstage/plugin-playlist' +addedDate: '2022-07-02' diff --git a/microsite/data/plugins/prometheus.yaml b/microsite/data/plugins/prometheus.yaml index 0d1e02dd74..f7a079d44c 100644 --- a/microsite/data/plugins/prometheus.yaml +++ b/microsite/data/plugins/prometheus.yaml @@ -1,6 +1,6 @@ --- title: Prometheus -author: Roadie +author: roadie.io authorUrl: https://roadie.io category: Monitoring description: Prometheus plugin provides visualization of Prometheus metrics and alerts @@ -11,3 +11,4 @@ tags: - monitoring - graphs - alerting +addedDate: '2021-10-06' diff --git a/microsite/data/plugins/rollbar.yaml b/microsite/data/plugins/rollbar.yaml index 05f0d4ef78..7742e6a231 100644 --- a/microsite/data/plugins/rollbar.yaml +++ b/microsite/data/plugins/rollbar.yaml @@ -7,3 +7,4 @@ description: View Rollbar errors for your services in Backstage. documentation: https://github.com/backstage/backstage/tree/master/plugins/rollbar iconUrl: https://rollbar.com/assets/media/rollbar-mark-color.png npmPackageName: '@backstage/plugin-rollbar' +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/rootly.yaml b/microsite/data/plugins/rootly.yaml index c1769f637a..4aaf10ee7b 100644 --- a/microsite/data/plugins/rootly.yaml +++ b/microsite/data/plugins/rootly.yaml @@ -7,3 +7,4 @@ description: Import your Backstage entities into Rootly services and view incide documentation: https://github.com/rootlyhq/backstage-plugin/blob/master/README.md iconUrl: https://raw.githubusercontent.com/rootlyhq/backstage-plugin/master/docs/logo.png npmPackageName: '@rootly/backstage-plugin' +addedDate: '2022-06-16' diff --git a/microsite/data/plugins/scaffolder-backend-dotnet.yaml b/microsite/data/plugins/scaffolder-backend-dotnet.yaml index 10c6530c94..2b032621c9 100644 --- a/microsite/data/plugins/scaffolder-backend-dotnet.yaml +++ b/microsite/data/plugins/scaffolder-backend-dotnet.yaml @@ -7,3 +7,4 @@ description: Here you can find all .NET related actions to improve your scaffold documentation: https://github.com/alefcarlos/plusultra-dotnet-backstage-plugins/blob/main/plugins/scaffolder-dotnet-backend/README.md iconUrl: img/scaffolder-backend-dotnet-icon.png npmPackageName: '@plusultra/plugin-scaffolder-dotnet-backend' +addedDate: '2022-01-24' diff --git a/microsite/data/plugins/scaffolder-backend-git.yaml b/microsite/data/plugins/scaffolder-backend-git.yaml index dc23a202ba..4378b36a1e 100644 --- a/microsite/data/plugins/scaffolder-backend-git.yaml +++ b/microsite/data/plugins/scaffolder-backend-git.yaml @@ -7,3 +7,4 @@ description: Easily run git CLI commands from your scaffolder actions documentation: https://github.com/arhill05/backstage-plugin-scaffolder-git-actions#readme iconUrl: https://git-scm.com/images/logos/downloads/Git-Logo-2Color.png npmPackageName: '@mdude2314/backstage-plugin-scaffolder-git-actions' +addedDate: '2022-05-13' diff --git a/microsite/data/plugins/scaffolder-backend-module-rails.yaml b/microsite/data/plugins/scaffolder-backend-module-rails.yaml index 5f5a29cf3c..4110dcbed0 100644 --- a/microsite/data/plugins/scaffolder-backend-module-rails.yaml +++ b/microsite/data/plugins/scaffolder-backend-module-rails.yaml @@ -7,3 +7,4 @@ description: Here you can find all Rails related features to improve your scaffo documentation: https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend-module-rails/README.md iconUrl: img/rails-icon.png npmPackageName: '@backstage/plugin-scaffolder-backend-module-rails' +addedDate: '2021-06-24' diff --git a/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml b/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml index 196a9388f9..e8e67dbdad 100644 --- a/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml +++ b/microsite/data/plugins/scaffolder-backend-roadie-aws.yaml @@ -7,3 +7,4 @@ description: Here you can find some AWS cli actions documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/plugins/scaffolder-actions/scaffolder-backend-module-aws/README.md iconUrl: https://upload.wikimedia.org/wikipedia/commons/9/93/Amazon_Web_Services_Logo.svg npmPackageName: '@roadiehq/scaffolder-backend-module-aws' +addedDate: '2022-03-07' diff --git a/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml b/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml index 8e61733ca1..254bebe8e7 100644 --- a/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml +++ b/microsite/data/plugins/scaffolder-backend-roadie-http-request.yaml @@ -7,3 +7,4 @@ description: An action to fire an arbitrary HTTP request documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/plugins/scaffolder-actions/scaffolder-backend-module-http-request/README.md iconUrl: img/scaffolder-http-request-logo.svg npmPackageName: '@roadiehq/scaffolder-backend-module-http-request' +addedDate: '2022-03-03' diff --git a/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml b/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml index 3ed3a646dd..ef3814f8ea 100644 --- a/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml +++ b/microsite/data/plugins/scaffolder-backend-roadie-utils.yaml @@ -7,3 +7,4 @@ description: A collection of utility actions including sleep, zip and file manip documentation: https://github.com/RoadieHQ/roadie-backstage-plugins/blob/main/plugins/scaffolder-actions/scaffolder-backend-module-utils/README.md iconUrl: img/scaffolder-utils-logo.png npmPackageName: '@roadiehq/scaffolder-backend-module-utils' +addedDate: '2022-03-03' diff --git a/microsite/data/plugins/score-card.yaml b/microsite/data/plugins/score-card.yaml index 040d94ed44..c647f8dd15 100644 --- a/microsite/data/plugins/score-card.yaml +++ b/microsite/data/plugins/score-card.yaml @@ -7,3 +7,4 @@ description: Visualization of maturity (and improving it) of services/systems th documentation: https://github.com/Oriflame/backstage-plugins/tree/main/plugins/score-card iconUrl: img/score-card-plugin-logo.png npmPackageName: '@oriflame/backstage-plugin-score-card' +addedDate: '2022-10-06' diff --git a/microsite/data/plugins/security-insights.yaml b/microsite/data/plugins/security-insights.yaml index 1761bdf30a..fd4a001e14 100644 --- a/microsite/data/plugins/security-insights.yaml +++ b/microsite/data/plugins/security-insights.yaml @@ -7,3 +7,4 @@ description: View Security Insights for your components in Backstage. documentation: https://roadie.io/backstage/plugins/security-insights/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=security-insights iconUrl: https://roadie.io/images/logos/github.png npmPackageName: '@roadiehq/backstage-plugin-security-insights' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/sentry.yaml b/microsite/data/plugins/sentry.yaml index 3445d6abb7..7bffdcfd4d 100644 --- a/microsite/data/plugins/sentry.yaml +++ b/microsite/data/plugins/sentry.yaml @@ -7,3 +7,4 @@ description: View Sentry issues in Backstage. documentation: https://github.com/backstage/backstage/tree/master/plugins/sentry iconUrl: https://sentry-brand.storage.googleapis.com/sentry-glyph-white.png npmPackageName: '@backstage/plugin-sentry' +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/shortcuts.yaml b/microsite/data/plugins/shortcuts.yaml index 437a0e44b1..d7c63ba666 100644 --- a/microsite/data/plugins/shortcuts.yaml +++ b/microsite/data/plugins/shortcuts.yaml @@ -7,3 +7,4 @@ description: The shortcuts plugin allows a user to have easy access to pages wit documentation: https://github.com/backstage/backstage/blob/master/plugins/shortcuts/README.md iconUrl: img/shortcuts.svg npmPackageName: '@backstage/plugin-shortcuts' +addedDate: '2021-10-06' diff --git a/microsite/data/plugins/snyk-security.yaml b/microsite/data/plugins/snyk-security.yaml index 35f00595d1..4abd068247 100644 --- a/microsite/data/plugins/snyk-security.yaml +++ b/microsite/data/plugins/snyk-security.yaml @@ -7,3 +7,4 @@ description: View Snyk scanned vulnerabilities and license compliance of your co documentation: https://github.com/snyk-tech-services/backstage-plugin-snyk/blob/main/README.md iconUrl: https://storage.googleapis.com/snyk-technical-services.appspot.com/snyk-logo-vertical-black.png npmPackageName: 'backstage-plugin-snyk' +addedDate: '2021-01-22' diff --git a/microsite/data/plugins/sonarqube.yaml b/microsite/data/plugins/sonarqube.yaml index e9e22d18a4..2eab08efff 100644 --- a/microsite/data/plugins/sonarqube.yaml +++ b/microsite/data/plugins/sonarqube.yaml @@ -7,3 +7,4 @@ description: Components to display code quality metrics from SonarCloud and Sona documentation: https://github.com/backstage/backstage/blob/master/plugins/sonarqube/README.md iconUrl: img/sonarqube-icon.svg npmPackageName: '@backstage/plugin-sonarqube' +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/splunk-on-call.yaml b/microsite/data/plugins/splunk-on-call.yaml index bb0e25111a..7a29ab3bf9 100644 --- a/microsite/data/plugins/splunk-on-call.yaml +++ b/microsite/data/plugins/splunk-on-call.yaml @@ -12,3 +12,4 @@ tags: - errors - alerting - splunk +addedDate: '2021-11-17' diff --git a/microsite/data/plugins/stack-overflow.yaml b/microsite/data/plugins/stack-overflow.yaml index 0d734446d7..27a3a8cf97 100644 --- a/microsite/data/plugins/stack-overflow.yaml +++ b/microsite/data/plugins/stack-overflow.yaml @@ -7,3 +7,4 @@ description: Provides Stack Overflow specific functionality that can be used in documentation: https://github.com/backstage/backstage/blob/master/plugins/stack-overflow iconUrl: img/stack-overflow-logo.svg npmPackageName: '@backstage/plugin-stack-overflow' +addedDate: '2022-06-14' diff --git a/microsite/data/plugins/tech-insights.yaml b/microsite/data/plugins/tech-insights.yaml index b57ca06ee6..fbf96df3e6 100644 --- a/microsite/data/plugins/tech-insights.yaml +++ b/microsite/data/plugins/tech-insights.yaml @@ -1,6 +1,6 @@ --- title: Tech Insights -author: '@RoadieHQ' +author: roadie.io authorUrl: https://github.com/RoadieHQ category: Discovery description: Visualize, understand and optimize your team's tech health. @@ -12,3 +12,4 @@ tags: - reporting - tech health - migrations +addedDate: '2022-03-31' diff --git a/microsite/data/plugins/tech-radar.yaml b/microsite/data/plugins/tech-radar.yaml index f2d5b12663..3187a33d42 100644 --- a/microsite/data/plugins/tech-radar.yaml +++ b/microsite/data/plugins/tech-radar.yaml @@ -7,3 +7,4 @@ description: Visualize the your company's official guidelines of different areas documentation: https://github.com/backstage/backstage/tree/master/plugins/tech-radar iconUrl: https://www.materialui.co/materialIcons/action/track_changes_white_192x192.png npmPackageName: '@backstage/plugin-tech-radar' +addedDate: '2020-11-03' diff --git a/microsite/data/plugins/tekton-pipelines.yaml b/microsite/data/plugins/tekton-pipelines.yaml index e53418037d..2da80d34bf 100644 --- a/microsite/data/plugins/tekton-pipelines.yaml +++ b/microsite/data/plugins/tekton-pipelines.yaml @@ -7,3 +7,4 @@ description: View the status of the Tekton PipelineRun resources. documentation: https://github.com/jquad-group/backstage-jquad#readme iconUrl: https://raw.githubusercontent.com/jquad-group/backstage-jquad/main/img/tekton-horizontal-color.png npmPackageName: '@jquad-group/plugin-tekton-pipelines' +addedDate: '2022-08-08' diff --git a/microsite/data/plugins/todo.yaml b/microsite/data/plugins/todo.yaml index 44128c40b2..67a5151a89 100644 --- a/microsite/data/plugins/todo.yaml +++ b/microsite/data/plugins/todo.yaml @@ -7,3 +7,4 @@ description: Browse TODO comments in your project's source code. documentation: https://github.com/backstage/backstage/tree/master/plugins/todo iconUrl: https://backstage.io/img/todo-logo.png npmPackageName: '@backstage/plugin-todo' +addedDate: '2021-03-16' diff --git a/microsite/data/plugins/travis-ci.yaml b/microsite/data/plugins/travis-ci.yaml index 7cb108a5d8..a5cec13d22 100644 --- a/microsite/data/plugins/travis-ci.yaml +++ b/microsite/data/plugins/travis-ci.yaml @@ -7,3 +7,4 @@ description: View Travis CI builds for your service in Backstage. documentation: https://roadie.io/backstage/plugins/travis-ci/?utm_source=backstage.io&utm_medium=marketplace&utm_campaign=travis-ci iconUrl: https://roadie.io/images/logos/travis.png npmPackageName: '@roadiehq/backstage-plugin-travis-ci' +addedDate: '2021-04-20' diff --git a/microsite/data/plugins/vault.yaml b/microsite/data/plugins/vault.yaml index 3214cf669a..822f5dcc90 100644 --- a/microsite/data/plugins/vault.yaml +++ b/microsite/data/plugins/vault.yaml @@ -9,3 +9,4 @@ iconUrl: img/vault.png npmPackageName: '@backstage/plugin-vault' tags: - vault +addedDate: '2022-06-03' diff --git a/microsite/data/plugins/xcmetrics.yaml b/microsite/data/plugins/xcmetrics.yaml index f2f4f7c013..91db85ebaa 100644 --- a/microsite/data/plugins/xcmetrics.yaml +++ b/microsite/data/plugins/xcmetrics.yaml @@ -7,3 +7,4 @@ description: Discover valuable insights hiding inside Xcode’s build logs. documentation: https://xcmetrics.io/ iconUrl: img/xcmetrics-icon.png npmPackageName: '@backstage/plugin-xcmetrics' +addedDate: '2021-08-06' diff --git a/microsite/pages/en/link.js b/microsite/pages/en/link.js index 6f56aa768c..fadb4f50d0 100644 --- a/microsite/pages/en/link.js +++ b/microsite/pages/en/link.js @@ -6,6 +6,7 @@ const redirects = { 'bind-routes': '/docs/plugins/composability#binding-external-routes-in-the-app', 'scm-auth': '/docs/auth/#scaffolder-configuration-software-templates', + 'backend-system': '/docs/plugins/new-backend-system', }; const fallback = '/docs'; diff --git a/microsite/pages/en/live.js b/microsite/pages/en/live.js index 43b5a0eb67..2accd6d11c 100644 --- a/microsite/pages/en/live.js +++ b/microsite/pages/en/live.js @@ -34,7 +34,7 @@ const Background = props => { diff --git a/microsite/pages/en/plugins.js b/microsite/pages/en/plugins.js index de336444ab..9a0a59bd4b 100644 --- a/microsite/pages/en/plugins.js +++ b/microsite/pages/en/plugins.js @@ -21,6 +21,8 @@ const pluginMetadata = fs const truncate = text => text.length > 170 ? text.substr(0, 170) + '...' : text; +const newForDays = 100; + const addPluginDocsLink = '/docs/plugins/add-to-marketplace'; const defaultIconUrl = 'img/logo-gradient-on-dark.svg'; @@ -100,8 +102,16 @@ const Plugins = () => ( authorUrl, documentation, category, + addedDate, }) => (
+ {Math.trunc( + (Date.now() - new Date(addedDate)) / (1000 * 60 * 60 * 24), + ) < newForDays && ( +
+ NEW +
+ )}
{title} diff --git a/microsite/sidebars.json b/microsite/sidebars.json index a93e148317..e758fe9100 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,6 +1,7 @@ { "releases": { "Release Notes": [ + "releases/v1.7.0", "releases/v1.6.0", "releases/v1.5.0", "releases/v1.4.0", @@ -152,11 +153,6 @@ "integrations/azure/org" ] }, - { - "type": "subcategory", - "label": "Bitbucket", - "ids": ["integrations/bitbucket/discovery"] - }, { "type": "subcategory", "label": "Bitbucket Cloud", @@ -204,6 +200,11 @@ "integrations/gitlab/discovery" ] }, + { + "type": "subcategory", + "label": "Gitea", + "ids": ["integrations/gitea/locations"] + }, { "type": "subcategory", "label": "Google GCS", @@ -226,6 +227,7 @@ "plugins/composability", "plugins/customization", "plugins/analytics", + "plugins/feature-flags", { "type": "subcategory", "label": "Backends and APIs", @@ -233,7 +235,8 @@ "plugins/proxying", "plugins/backend-plugin", "plugins/call-existing-api", - "plugins/url-reader" + "plugins/url-reader", + "plugins/new-backend-system" ] }, { diff --git a/microsite/static/css/plugins.css b/microsite/static/css/plugins.css index b9aebfa84c..55a0c6df40 100644 --- a/microsite/static/css/plugins.css +++ b/microsite/static/css/plugins.css @@ -4,6 +4,7 @@ padding: 16px; display: flex; flex-direction: column; + position: relative; } .PluginGrid { @@ -141,3 +142,31 @@ #add-plugin-card { border: 1px solid #69ddc7; } + +/* RIBBON */ + +.ribbon { + width: 80px; + height: 80px; + overflow: hidden; + position: absolute; +} +.ribbon span { + position: absolute; + display: block; + width: 140px; + padding: 4px 0; + background-color: rgb(54, 186, 162, 0.85); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); + color: #fff; + text-align: center; +} +.ribbon-top-right { + top: 0; + right: 0; +} +.ribbon-top-right span { + left: -17px; + top: 11px; + transform: rotate(45deg); +} diff --git a/mkdocs.yml b/mkdocs.yml index 36ff1c6348..43efc32f8e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -137,6 +137,7 @@ nav: - Backend plugin: 'plugins/backend-plugin.md' - Call existing API: 'plugins/call-existing-api.md' - URL Reader: 'plugins/url-reader.md' + - New Backend System: 'plugins/new-backend-system.md' - Testing: - Testing with Jest: 'plugins/testing.md' - Publishing: diff --git a/package.json b/package.json index 1013b2a049..168f24787d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "backstage-cli repo build --all", "build:api-reports": "yarn build:api-reports:only --tsc", "build:api-reports:only": "ts-node -T -P scripts/tsconfig.json scripts/api-extractor.ts", - "build:api-docs": "yarn build:api-reports --docs", + "build:api-docs": "LANG=en_EN yarn build:api-reports --docs", "tsc": "tsc", "tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false", "clean": "backstage-cli repo clean", @@ -44,7 +44,7 @@ "@types/react": "^17", "@types/react-dom": "^17" }, - "version": "1.7.0-next.1", + "version": "1.8.0-next.0", "dependencies": { "@backstage/errors": "workspace:^", "@manypkg/get-packages": "^1.1.3", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index c4082ad530..12b6a0894f 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/app-defaults +## 1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/theme@0.2.16 + +## 1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 1.0.7-next.1 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 476509730a..6658f0a8bd 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/app-defaults", "description": "Provides the default wiring of a Backstage App", - "version": "1.0.7-next.1", + "version": "1.0.8-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index d492b19a1a..d909773ff4 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,192 @@ # example-app +## 0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.8.0-next.0 + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-tech-insights@0.3.2-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-cost-insights@0.12.0-next.0 + - @backstage/plugin-dynatrace@1.0.1-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/plugin-catalog-import@0.9.1-next.0 + - @backstage/plugin-tech-radar@0.5.18-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-gcalendar@0.3.7-next.0 + - @backstage/plugin-code-coverage@0.2.4-next.0 + - @backstage/plugin-github-actions@0.5.11-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + - @backstage/plugin-circleci@0.3.11-next.0 + - @backstage/plugin-azure-sites@0.1.0-next.0 + - @backstage/plugin-cloudbuild@0.3.11-next.0 + - @backstage/plugin-jenkins@0.7.10-next.0 + - @backstage/plugin-kafka@0.3.11-next.0 + - @backstage/plugin-pagerduty@0.5.4-next.0 + - @backstage/plugin-rollbar@0.4.11-next.0 + - @backstage/plugin-airbrake@0.3.11-next.0 + - @backstage/plugin-api-docs@0.8.11-next.0 + - @backstage/plugin-azure-devops@0.2.2-next.0 + - @backstage/plugin-badges@0.2.35-next.0 + - @internal/plugin-catalog-customized@0.0.4-next.0 + - @backstage/plugin-catalog-graph@0.2.23-next.0 + - @backstage/plugin-explore@0.3.42-next.0 + - @backstage/plugin-gocd@0.1.17-next.0 + - @backstage/plugin-home@0.4.27-next.0 + - @backstage/plugin-kubernetes@0.7.4-next.0 + - @backstage/plugin-lighthouse@0.3.11-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.4-next.0 + - @backstage/plugin-org@0.5.11-next.0 + - @backstage/plugin-playlist@0.1.2-next.0 + - @backstage/plugin-search@1.0.4-next.0 + - @backstage/plugin-sentry@0.4.4-next.0 + - @backstage/plugin-todo@0.2.13-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-apache-airflow@0.2.4-next.0 + - @backstage/plugin-gcp-projects@0.3.30-next.0 + - @backstage/plugin-graphiql@0.2.43-next.0 + - @backstage/plugin-newrelic@0.3.29-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-shortcuts@0.3.3-next.0 + - @backstage/plugin-stack-overflow@0.1.7-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/plugin-user-settings@0.5.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.5.0 + - @backstage/plugin-scaffolder@1.7.0 + - @backstage/plugin-org@0.5.10 + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-graph@0.2.22 + - @backstage/core-app-api@1.1.1 + - @backstage/cli@0.20.0 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-catalog-import@0.9.0 + - @backstage/plugin-tech-insights@0.3.1 + - @backstage/plugin-kubernetes@0.7.3 + - @backstage/plugin-gcalendar@0.3.6 + - @backstage/plugin-api-docs@0.8.10 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-cost-insights@0.11.32 + - @backstage/plugin-airbrake@0.3.10 + - @backstage/plugin-azure-devops@0.2.1 + - @backstage/plugin-badges@0.2.34 + - @backstage/plugin-circleci@0.3.10 + - @backstage/plugin-cloudbuild@0.3.10 + - @backstage/plugin-code-coverage@0.2.3 + - @backstage/plugin-dynatrace@1.0.0 + - @backstage/plugin-explore@0.3.41 + - @backstage/plugin-github-actions@0.5.10 + - @backstage/plugin-gocd@0.1.16 + - @backstage/plugin-home@0.4.26 + - @backstage/plugin-jenkins@0.7.9 + - @backstage/plugin-kafka@0.3.10 + - @backstage/plugin-lighthouse@0.3.10 + - @backstage/plugin-newrelic-dashboard@0.2.3 + - @backstage/plugin-pagerduty@0.5.3 + - @backstage/plugin-playlist@0.1.1 + - @backstage/plugin-rollbar@0.4.10 + - @backstage/plugin-search@1.0.3 + - @backstage/plugin-sentry@0.4.3 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/plugin-todo@0.2.12 + - @internal/plugin-catalog-customized@0.0.3 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/plugin-apache-airflow@0.2.3 + - @backstage/plugin-gcp-projects@0.3.29 + - @backstage/plugin-graphiql@0.2.42 + - @backstage/plugin-newrelic@0.3.28 + - @backstage/plugin-shortcuts@0.3.2 + - @backstage/plugin-stack-overflow@0.1.6 + - @backstage/plugin-tech-radar@0.5.17 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + +## 0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.7.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-import@0.9.0-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-playlist@0.1.1-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.10-next.2 + - @backstage/plugin-apache-airflow@0.2.3-next.2 + - @backstage/plugin-api-docs@0.8.10-next.2 + - @backstage/plugin-azure-devops@0.2.1-next.2 + - @backstage/plugin-badges@0.2.34-next.2 + - @backstage/plugin-catalog-graph@0.2.22-next.2 + - @backstage/plugin-circleci@0.3.10-next.2 + - @backstage/plugin-cloudbuild@0.3.10-next.2 + - @backstage/plugin-code-coverage@0.2.3-next.2 + - @backstage/plugin-cost-insights@0.11.32-next.2 + - @backstage/plugin-dynatrace@1.0.0-next.2 + - @backstage/plugin-explore@0.3.41-next.2 + - @backstage/plugin-gcalendar@0.3.6-next.2 + - @backstage/plugin-gcp-projects@0.3.29-next.2 + - @backstage/plugin-github-actions@0.5.10-next.2 + - @backstage/plugin-gocd@0.1.16-next.2 + - @backstage/plugin-graphiql@0.2.42-next.2 + - @backstage/plugin-home@0.4.26-next.2 + - @backstage/plugin-jenkins@0.7.9-next.2 + - @backstage/plugin-kafka@0.3.10-next.2 + - @backstage/plugin-kubernetes@0.7.3-next.2 + - @backstage/plugin-lighthouse@0.3.10-next.2 + - @backstage/plugin-newrelic@0.3.28-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + - @backstage/plugin-org@0.5.10-next.2 + - @backstage/plugin-pagerduty@0.5.3-next.2 + - @backstage/plugin-rollbar@0.4.10-next.2 + - @backstage/plugin-search@1.0.3-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-sentry@0.4.3-next.2 + - @backstage/plugin-shortcuts@0.3.2-next.2 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + - @backstage/plugin-tech-insights@0.3.1-next.2 + - @backstage/plugin-tech-radar@0.5.17-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + - @backstage/plugin-todo@0.2.12-next.2 + - @backstage/plugin-user-settings@0.5.0-next.2 + - @internal/plugin-catalog-customized@0.0.3-next.2 + ## 0.2.76-next.1 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 76e099bace..9388f305f6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.76-next.1", + "version": "0.2.77-next.0", "private": true, "backstage": { "role": "frontend" @@ -19,6 +19,7 @@ "@backstage/plugin-apache-airflow": "workspace:^", "@backstage/plugin-api-docs": "workspace:^", "@backstage/plugin-azure-devops": "workspace:^", + "@backstage/plugin-azure-sites": "workspace:^", "@backstage/plugin-badges": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-graph": "workspace:^", @@ -83,7 +84,6 @@ }, "devDependencies": { "@backstage/test-utils": "workspace:^", - "@rjsf/core": "^3.2.1", "@testing-library/cypress": "^8.0.2", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index d6b66d57a7..2526f6740f 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -150,7 +150,7 @@ const AppRouter = app.getRouter(); const routes = ( - } /> + } /> {/* TODO(rubenl): Move this to / once its more mature and components exist */} }> {homePage} diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 0c142bafbd..41e5b15db9 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -15,7 +15,7 @@ */ import React, { PropsWithChildren } from 'react'; -import { Link, makeStyles } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ExtensionIcon from '@material-ui/icons/Extension'; import RuleIcon from '@material-ui/icons/AssignmentTurnedIn'; @@ -29,7 +29,6 @@ import MenuIcon from '@material-ui/icons/Menu'; import MoneyIcon from '@material-ui/icons/MonetizationOn'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; -import { NavLink } from 'react-router-dom'; import { GraphiQLIcon } from '@backstage/plugin-graphiql'; import { Settings as SidebarSettings, @@ -46,6 +45,7 @@ import { SidebarPage, SidebarScrollWrapper, SidebarSpace, + Link, useSidebarOpenState, } from '@backstage/core-components'; import { MyGroupsSidebarItem } from '@backstage/plugin-org'; @@ -74,13 +74,7 @@ const SidebarLogo = () => { return (
- + {isOpen ? : }
diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index f7592eb0b9..8e72bb03e4 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -209,7 +209,7 @@ const techdocsContent = ( /** * NOTE: This page is designed to work on small screens such as mobile devices. * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`, - * since this does not default. If no breakpoints are used, the items will equitably share the asvailable space. + * since this does not default. If no breakpoints are used, the items will equitably share the available space. * https://material-ui.com/components/grid/#basic-grid. */ diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 99814130c2..d324b683d8 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -35,6 +35,7 @@ import { SearchBar, SearchFilter, SearchResult, + SearchPagination, SearchResultPager, useSearch, } from '@backstage/plugin-search-react'; @@ -129,6 +130,7 @@ const SearchPage = () => { )} + {({ results }) => ( diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 3823d025ac..d8b2cd7c3b 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/backend-app-api +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.2 + +### Patch Changes + +- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. +- 45857bffae: Properly export `rootLoggerFactory`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/errors@1.1.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.2-next.1 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index c454785aa2..0658176962 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-app-api", "description": "Core API used by Backstage backend apps", - "version": "0.2.2-next.1", + "version": "0.2.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 143b6d28c9..3c219161d0 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/backend-common +## 0.16.0-next.0 + +### Minor Changes + +- a7607b5413: **BREAKING CHANGE**: The `UrlReader` interface has been updated to require that `readUrl` is implemented. `readUrl` has previously been optional to implement but a warning has been logged when calling its predecessor `read`. + The `read` method is now deprecated and will be removed in a future release. + +### Patch Changes + +- 55227712dd: Generated development HTTPS backend certificate is now checked for expiration date instead of file age. +- d05e1841ce: This patch adds GiteaURLReader to the available classes. It currently only reads single files via gitea's public repos api +- 210a3b5668: Small update to fix compatibility with newer versions of the `keyv` library +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.15.2 + +### Patch Changes + +- e8d7976413: Added back support for when no branch is provided for the Bitbucket Server `UrlReader` +- c44cf412de: Fix BitBucket server integration +- c31f7cdfbc: Fixed an issue where `getClient()` for a `pluginId` would return different clients and not share them +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.15.2-next.2 + +### Patch Changes + +- c44cf412de: Fix BitBucket server integration +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.15.2-next.1 ### Patch Changes diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index 600e3edef8..e8dbd8d150 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -21,8 +21,9 @@ import { Duration } from 'luxon'; import { ErrorRequestHandler } from 'express'; import express from 'express'; import { GerritIntegration } from '@backstage/integration'; +import { GiteaIntegration } from '@backstage/integration'; import { GithubCredentialsProvider } from '@backstage/integration'; -import { GitHubIntegration } from '@backstage/integration'; +import { GithubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; import { isChildPath } from '@backstage/cli-common'; import { JsonValue } from '@backstage/types'; @@ -406,10 +407,27 @@ export class Git { resolveRef(options: { dir: string; ref: string }): Promise; } +// @public +export class GiteaUrlReader implements UrlReader { + constructor(integration: GiteaIntegration); + // (undocumented) + static factory: ReaderFactory; + // (undocumented) + read(url: string): Promise; + // (undocumented) + readTree(): Promise; + // (undocumented) + readUrl(url: string, options?: ReadUrlOptions): Promise; + // (undocumented) + search(): Promise; + // (undocumented) + toString(): string; +} + // @public export class GithubUrlReader implements UrlReader { constructor( - integration: GitHubIntegration, + integration: GithubIntegration, deps: { treeResponseFactory: ReadTreeResponseFactory; credentialsProvider: GithubCredentialsProvider; @@ -717,7 +735,7 @@ export interface TokenManager { // @public export type UrlReader = { read(url: string): Promise; - readUrl?(url: string, options?: ReadUrlOptions): Promise; + readUrl(url: string, options?: ReadUrlOptions): Promise; readTree(url: string, options?: ReadTreeOptions): Promise; search(url: string, options?: SearchOptions): Promise; }; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b6b0c5e35a..94b2338ab0 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.15.2-next.1", + "version": "0.16.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { @@ -75,6 +75,7 @@ "morgan": "^1.10.0", "node-abort-controller": "^3.0.1", "node-fetch": "^2.6.7", + "node-forge": "^1.3.1", "raw-body": "^2.4.1", "request": "^2.88.2", "selfsigned": "^2.0.0", @@ -105,6 +106,7 @@ "@types/minimist": "^1.2.0", "@types/mock-fs": "^4.13.0", "@types/morgan": "^1.9.0", + "@types/node-forge": "^1.3.0", "@types/recursive-readdir": "^2.2.0", "@types/stoppable": "^1.1.0", "@types/supertest": "^2.0.8", diff --git a/packages/backend-common/src/cache/NoStore.ts b/packages/backend-common/src/cache/NoStore.ts index 3bb28afc71..85f789d21e 100644 --- a/packages/backend-common/src/cache/NoStore.ts +++ b/packages/backend-common/src/cache/NoStore.ts @@ -14,11 +14,13 @@ * limitations under the License. */ +import { Store } from 'keyv'; + /** * Storage class compatible with Keyv which always results in a no-op. This is * used when no cache store is configured in a Backstage backend instance. */ -export class NoStore extends Map { +export class NoStore implements Store { clear(): void { return; } @@ -28,7 +30,7 @@ export class NoStore extends Map { } get(_key: string) { - return; + return undefined; } has(_key: string): boolean { diff --git a/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts b/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts index 15f1c0d4be..9f231e2ba7 100644 --- a/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts +++ b/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts @@ -102,6 +102,25 @@ describe('BitbucketServerUrlReader', () => { }), ), ), + rest.get( + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches/default', + (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + id: 'refs/heads/master', + displayId: 'master', + type: 'BRANCH', + latestCommit: '3bdd5457286abdf920db4b77bf2fef79a06190c2', + latestChangeset: '3bdd5457286abdf920db4b77bf2fef79a06190c2', + isDefault: true, + }), + ), + ), + rest.get( + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch', + (_, res, ctx) => res(ctx.status(404)), + ), ); }); @@ -119,6 +138,14 @@ describe('BitbucketServerUrlReader', () => { expect(indexMarkdownFile.toString()).toBe('# Test\n'); }); + + it('uses default branch when no branch is provided', async () => { + const response = await reader.readTree( + 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/src', + ); + + expect(response.etag).toBe('3bdd5457286a'); + }); }); describe('readTree without branch', () => { diff --git a/packages/backend-common/src/reading/BitbucketServerUrlReader.ts b/packages/backend-common/src/reading/BitbucketServerUrlReader.ts index 98f95b88cb..bcab226b0f 100644 --- a/packages/backend-common/src/reading/BitbucketServerUrlReader.ts +++ b/packages/backend-common/src/reading/BitbucketServerUrlReader.ts @@ -188,11 +188,13 @@ export class BitbucketServerUrlReader implements UrlReader { private async getLastCommitShortHash(url: string): Promise { const { name: repoName, owner: project, ref: branch } = parseGitUrl(url); - const branchListUrl = `${ - this.integration.config.apiBaseUrl - }/projects/${project}/repos/${repoName}/branches?filterText=${encodeURIComponent( - branch, - )}`; + // If a branch is provided use that otherwise fall back to the default branch + const branchParameter = branch + ? `?filterText=${encodeURIComponent(branch)}` + : '/default'; + + // https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp211 (branches docs) + const branchListUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches${branchParameter}`; const branchListResponse = await fetch( branchListUrl, @@ -216,8 +218,15 @@ export class BitbucketServerUrlReader implements UrlReader { return exactBranchMatch.latestCommit.substring(0, 12); } + // Handle when no branch is provided using the default as the fallback + if (!branch && branchMatches) { + return branchMatches.latestCommit.substring(0, 12); + } + throw new Error( - `Failed to find branch "${branch}" in property "displayId" of response to ${branchListUrl}`, + `Failed to find Last Commit using ${ + branch ? `branch "${branch}"` : 'default branch' + } in response from ${branchListUrl}`, ); } } diff --git a/packages/backend-common/src/reading/GiteaUrlReader.test.ts b/packages/backend-common/src/reading/GiteaUrlReader.test.ts new file mode 100644 index 0000000000..6c8722d2e5 --- /dev/null +++ b/packages/backend-common/src/reading/GiteaUrlReader.test.ts @@ -0,0 +1,204 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { GiteaIntegration, readGiteaConfig } from '@backstage/integration'; +import { JsonObject } from '@backstage/types'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { getVoidLogger } from '../logging'; +import { UrlReaderPredicateTuple } from './types'; +import { DefaultReadTreeResponseFactory } from './tree'; +import getRawBody from 'raw-body'; +import { GiteaUrlReader } from './GiteaUrlReader'; +import { NotFoundError } from '@backstage/errors'; + +const treeResponseFactory = DefaultReadTreeResponseFactory.create({ + config: new ConfigReader({}), +}); + +jest.mock('../scm', () => ({ + Git: { + fromAuth: () => ({ + clone: jest.fn(() => Promise.resolve({})), + }), + }, +})); + +const giteaProcessor = new GiteaUrlReader( + new GiteaIntegration( + readGiteaConfig( + new ConfigReader({ + host: 'gitea.com', + }), + ), + ), +); + +const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { + return GiteaUrlReader.factory({ + config: new ConfigReader(config), + logger: getVoidLogger(), + treeResponseFactory, + }); +}; + +describe('GiteaUrlReader', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + afterAll(() => { + jest.clearAllMocks(); + }); + + describe('reader factory', () => { + it('creates a reader.', () => { + const readers = createReader({ + integrations: { + gitea: [{ host: 'gitea.com' }], + }, + }); + expect(readers).toHaveLength(1); + }); + + it('should not create a default entry.', () => { + const readers = createReader({ + integrations: {}, + }); + expect(readers).toHaveLength(0); + }); + }); + + describe('predicates', () => { + it('returns true for the configured host', () => { + const readers = createReader({ + integrations: { + gitea: [{ host: 'gitea.com' }], + }, + }); + const predicate = readers[0].predicate; + + expect(predicate(new URL('https://gitea.com/path'))).toBe(true); + }); + + it('returns false for a different host.', () => { + const readers = createReader({ + integrations: { + gitea: [{ host: 'gitea.com' }], + }, + }); + const predicate = readers[0].predicate; + + expect(predicate(new URL('https://github.com/path'))).toBe(false); + }); + }); + + describe('readUrl', () => { + const responseBuffer = Buffer.from('Apache License'); + const giteaApiResponse = (content: any) => { + return JSON.stringify({ + encoding: 'base64', + content: Buffer.from(content).toString('base64'), + }); + }; + + it('should be able to read file contents as buffer', async () => { + worker.use( + rest.get( + 'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE', + (req, res, ctx) => { + // Test utils prefers matching URL directly but it is part of Gitea's API + if (req.url.searchParams.get('ref') === 'branch2') { + return res( + ctx.status(200), + ctx.body(giteaApiResponse(responseBuffer.toString())), + ); + } + + return res(ctx.status(500)); + }, + ), + ); + + const result = await giteaProcessor.readUrl( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE', + ); + const buffer = await result.buffer(); + expect(buffer.toString()).toBe(responseBuffer.toString()); + }); + + it('should be able to read file contents as stream', async () => { + worker.use( + rest.get( + 'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE', + (req, res, ctx) => { + if (req.url.searchParams.get('ref') === 'branch2') { + return res( + ctx.status(200), + ctx.body(giteaApiResponse(responseBuffer.toString())), + ); + } + + return res(ctx.status(500)); + }, + ), + ); + + const result = await giteaProcessor.readUrl( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE', + ); + const fromStream = await getRawBody(result.stream!()); + expect(fromStream.toString()).toBe(responseBuffer.toString()); + }); + + it('should raise NotFoundError on 404.', async () => { + worker.use( + rest.get( + 'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE', + (_, res, ctx) => { + return res(ctx.status(404, 'File not found.')); + }, + ), + ); + + await expect( + giteaProcessor.readUrl( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE', + ), + ).rejects.toThrow(NotFoundError); + }); + + it('should throw an error on non 404 errors.', async () => { + worker.use( + rest.get( + 'https://gitea.com/api/v1/repos/owner/project/contents/LICENSE', + (_, res, ctx) => { + return res(ctx.status(500, 'Error!!!')); + }, + ), + ); + + await expect( + giteaProcessor.readUrl( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE', + ), + ).rejects.toThrow( + 'https://gitea.com/owner/project/src/branch/branch2/LICENSE could not be read as https://gitea.com/api/v1/repos/owner/project/contents/LICENSE?ref=branch2, 500 Error!!!', + ); + }); + }); +}); diff --git a/packages/backend-common/src/reading/GiteaUrlReader.ts b/packages/backend-common/src/reading/GiteaUrlReader.ts new file mode 100644 index 0000000000..da6b791949 --- /dev/null +++ b/packages/backend-common/src/reading/GiteaUrlReader.ts @@ -0,0 +1,125 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + getGiteaRequestOptions, + getGiteaFileContentsUrl, + GiteaIntegration, + ScmIntegrations, +} from '@backstage/integration'; +import { ReadUrlOptions, ReadUrlResponse } from './types'; +import { + ReaderFactory, + ReadTreeResponse, + SearchResponse, + UrlReader, +} from './types'; +import fetch, { Response } from 'node-fetch'; +import { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; +import { + AuthenticationError, + NotFoundError, + NotModifiedError, +} from '@backstage/errors'; +import { Readable } from 'stream'; + +/** + * Implements a {@link UrlReader} for the Gitea v1 api. + * + * @public + */ +export class GiteaUrlReader implements UrlReader { + static factory: ReaderFactory = ({ config }) => { + return ScmIntegrations.fromConfig(config) + .gitea.list() + .map(integration => { + const reader = new GiteaUrlReader(integration); + const predicate = (url: URL) => { + return url.host === integration.config.host; + }; + return { reader, predicate }; + }); + }; + + constructor(private readonly integration: GiteaIntegration) {} + + async read(url: string): Promise { + const response = await this.readUrl(url); + return response.buffer(); + } + + async readUrl( + url: string, + options?: ReadUrlOptions, + ): Promise { + let response: Response; + const blobUrl = getGiteaFileContentsUrl(this.integration.config, url); + + try { + response = await fetch(blobUrl, { + method: 'GET', + ...getGiteaRequestOptions(this.integration.config), + signal: options?.signal as any, + }); + } catch (e) { + throw new Error(`Unable to read ${blobUrl}, ${e}`); + } + + if (response.ok) { + // Gitea returns an object with the file contents encoded, not the file itself + const { encoding, content } = await response.json(); + + if (encoding === 'base64') { + return ReadUrlResponseFactory.fromReadable( + Readable.from(Buffer.from(content, 'base64')), + { + etag: response.headers.get('ETag') ?? undefined, + }, + ); + } + + throw new Error(`Unknown encoding: ${encoding}`); + } + + const message = `${url} could not be read as ${blobUrl}, ${response.status} ${response.statusText}`; + if (response.status === 404) { + throw new NotFoundError(message); + } + + if (response.status === 304) { + throw new NotModifiedError(); + } + + if (response.status === 403) { + throw new AuthenticationError(); + } + + throw new Error(message); + } + + readTree(): Promise { + throw new Error('GiteaUrlReader readTree not implemented.'); + } + search(): Promise { + throw new Error('GiteaUrlReader search not implemented.'); + } + + toString() { + const { host } = this.integration.config; + return `gitea{host=${host},authed=${Boolean( + this.integration.config.password, + )}}`; + } +} diff --git a/packages/backend-common/src/reading/GithubUrlReader.test.ts b/packages/backend-common/src/reading/GithubUrlReader.test.ts index fddb728648..a5f9d26de5 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.test.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.test.ts @@ -17,8 +17,8 @@ import { ConfigReader } from '@backstage/config'; import { GithubCredentialsProvider, - GitHubIntegration, - readGitHubIntegrationConfig, + GithubIntegration, + readGithubIntegrationConfig, } from '@backstage/integration'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import fs from 'fs-extra'; @@ -46,8 +46,8 @@ const mockCredentialsProvider = { } as unknown as GithubCredentialsProvider; const githubProcessor = new GithubUrlReader( - new GitHubIntegration( - readGitHubIntegrationConfig( + new GithubIntegration( + readGithubIntegrationConfig( new ConfigReader({ host: 'github.com', apiBaseUrl: 'https://api.github.com', @@ -58,8 +58,8 @@ const githubProcessor = new GithubUrlReader( ); const gheProcessor = new GithubUrlReader( - new GitHubIntegration( - readGitHubIntegrationConfig( + new GithubIntegration( + readGithubIntegrationConfig( new ConfigReader({ host: 'ghe.github.com', apiBaseUrl: 'https://ghe.github.com/api/v3', @@ -539,8 +539,8 @@ describe('GithubUrlReader', () => { expect(() => { /* eslint-disable no-new */ new GithubUrlReader( - new GitHubIntegration( - readGitHubIntegrationConfig( + new GithubIntegration( + readGithubIntegrationConfig( new ConfigReader({ host: 'ghe.mycompany.net', }), diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 3806253a6f..7abf1b2518 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -15,10 +15,10 @@ */ import { - getGitHubFileFetchUrl, + getGithubFileFetchUrl, DefaultGithubCredentialsProvider, GithubCredentialsProvider, - GitHubIntegration, + GithubIntegration, ScmIntegrations, } from '@backstage/integration'; import { RestEndpointMethodTypes } from '@octokit/rest'; @@ -72,7 +72,7 @@ export class GithubUrlReader implements UrlReader { }; constructor( - private readonly integration: GitHubIntegration, + private readonly integration: GithubIntegration, private readonly deps: { treeResponseFactory: ReadTreeResponseFactory; credentialsProvider: GithubCredentialsProvider; @@ -97,7 +97,7 @@ export class GithubUrlReader implements UrlReader { const credentials = await this.deps.credentialsProvider.getCredentials({ url, }); - const ghUrl = getGitHubFileFetchUrl( + const ghUrl = getGithubFileFetchUrl( url, this.integration.config, credentials, diff --git a/packages/backend-common/src/reading/UrlReaderPredicateMux.test.ts b/packages/backend-common/src/reading/UrlReaderPredicateMux.test.ts index 028439365c..5a11e068df 100644 --- a/packages/backend-common/src/reading/UrlReaderPredicateMux.test.ts +++ b/packages/backend-common/src/reading/UrlReaderPredicateMux.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { getVoidLogger } from '../logging'; import { UrlReaderPredicateMux } from './UrlReaderPredicateMux'; describe('UrlReaderPredicateMux', () => { @@ -32,7 +31,7 @@ describe('UrlReaderPredicateMux', () => { search: jest.fn(), }; - const mux = new UrlReaderPredicateMux(getVoidLogger()); + const mux = new UrlReaderPredicateMux(); mux.register({ predicate: url => url.hostname === 'foo', reader: fooReader, @@ -62,7 +61,7 @@ describe('UrlReaderPredicateMux', () => { }); it('throws an error if no predicate matches', async () => { - const mux = new UrlReaderPredicateMux(getVoidLogger()); + const mux = new UrlReaderPredicateMux(); await expect(mux.readUrl('http://foo/1')).rejects.toThrow( /^Reading from 'http:\/\/foo\/1' is not allowed. You may/, diff --git a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts index 13d85be4d4..d5d30605b2 100644 --- a/packages/backend-common/src/reading/UrlReaderPredicateMux.ts +++ b/packages/backend-common/src/reading/UrlReaderPredicateMux.ts @@ -15,7 +15,6 @@ */ import { NotAllowedError } from '@backstage/errors'; -import { Logger } from 'winston'; import { ReadTreeOptions, ReadTreeResponse, @@ -27,8 +26,6 @@ import { UrlReaderPredicateTuple, } from './types'; -const MIN_WARNING_INTERVAL_MS = 1000 * 60 * 15; - function notAllowedMessage(url: string) { return ( `Reading from '${url}' is not allowed. ` + @@ -43,9 +40,6 @@ function notAllowedMessage(url: string) { */ export class UrlReaderPredicateMux implements UrlReader { private readonly readers: UrlReaderPredicateTuple[] = []; - private readonly readerWarnings: Map = new Map(); - - constructor(private readonly logger: Logger) {} register(tuple: UrlReaderPredicateTuple): void { this.readers.push(tuple); @@ -71,23 +65,7 @@ export class UrlReaderPredicateMux implements UrlReader { for (const { predicate, reader } of this.readers) { if (predicate(parsed)) { - if (reader.readUrl) { - return reader.readUrl(url, options); - } - const now = Date.now(); - const lastWarned = this.readerWarnings.get(reader) ?? 0; - if (now > lastWarned + MIN_WARNING_INTERVAL_MS) { - this.readerWarnings.set(reader, now); - this.logger.warn( - `No implementation of readUrl found for ${reader}, this method will be required in the ` + - `future and will replace the 'read' method. See the changelog for more details here: ` + - 'https://github.com/backstage/backstage/blob/master/packages/backend-common/CHANGELOG.md#085', - ); - } - const buffer = await reader.read(url); - return { - buffer: async () => buffer, - }; + return reader.readUrl(url, options); } } diff --git a/packages/backend-common/src/reading/UrlReaders.ts b/packages/backend-common/src/reading/UrlReaders.ts index 72833454c3..9fb403e7e9 100644 --- a/packages/backend-common/src/reading/UrlReaders.ts +++ b/packages/backend-common/src/reading/UrlReaders.ts @@ -29,6 +29,7 @@ import { DefaultReadTreeResponseFactory } from './tree'; import { FetchUrlReader } from './FetchUrlReader'; import { GoogleGcsUrlReader } from './GoogleGcsUrlReader'; import { AwsS3UrlReader } from './AwsS3UrlReader'; +import { GiteaUrlReader } from './GiteaUrlReader'; /** * Creation options for {@link UrlReaders}. @@ -55,7 +56,7 @@ export class UrlReaders { */ static create(options: UrlReadersOptions): UrlReader { const { logger, config, factories } = options; - const mux = new UrlReaderPredicateMux(logger); + const mux = new UrlReaderPredicateMux(); const treeResponseFactory = DefaultReadTreeResponseFactory.create({ config, }); @@ -89,6 +90,7 @@ export class UrlReaders { BitbucketUrlReader.factory, GerritUrlReader.factory, GithubUrlReader.factory, + GiteaUrlReader.factory, GitlabUrlReader.factory, GoogleGcsUrlReader.factory, AwsS3UrlReader.factory, diff --git a/packages/backend-common/src/reading/index.ts b/packages/backend-common/src/reading/index.ts index 9ee86ef3f2..21e82da9b5 100644 --- a/packages/backend-common/src/reading/index.ts +++ b/packages/backend-common/src/reading/index.ts @@ -21,6 +21,7 @@ export { BitbucketServerUrlReader } from './BitbucketServerUrlReader'; export { GerritUrlReader } from './GerritUrlReader'; export { GithubUrlReader } from './GithubUrlReader'; export { GitlabUrlReader } from './GitlabUrlReader'; +export { GiteaUrlReader } from './GiteaUrlReader'; export { AwsS3UrlReader } from './AwsS3UrlReader'; export { FetchUrlReader } from './FetchUrlReader'; export { ReadUrlResponseFactory } from './ReadUrlResponseFactory'; diff --git a/packages/backend-common/src/reading/types.ts b/packages/backend-common/src/reading/types.ts index 6eae4e6cac..2ebf38c08a 100644 --- a/packages/backend-common/src/reading/types.ts +++ b/packages/backend-common/src/reading/types.ts @@ -27,6 +27,7 @@ import { AbortSignal } from 'node-abort-controller'; export type UrlReader = { /** * Reads a single file and return its content. + * @deprecated use readUrl instead. */ read(url: string): Promise; @@ -38,10 +39,9 @@ export type UrlReader = { * This is a replacement for the read method that supports options and * complex responses. * - * Use this whenever it is available, as the read method will be - * deprecated and eventually removed in a future release. + * Use this as the read method will be removed in a future release. */ - readUrl?(url: string, options?: ReadUrlOptions): Promise; + readUrl(url: string, options?: ReadUrlOptions): Promise; /** * Reads a full or partial file tree. diff --git a/packages/backend-common/src/service/lib/hostFactory.ts b/packages/backend-common/src/service/lib/hostFactory.ts index 572d8483b1..57334062ea 100644 --- a/packages/backend-common/src/service/lib/hostFactory.ts +++ b/packages/backend-common/src/service/lib/hostFactory.ts @@ -21,8 +21,9 @@ import * as http from 'http'; import * as https from 'https'; import { Logger } from 'winston'; import { HttpsSettings } from './config'; +import forge from 'node-forge'; -const ALMOST_MONTH_IN_MS = 25 * 24 * 60 * 60 * 1000; +const FIVE_DAYS_IN_MS = 5 * 24 * 60 * 60 * 1000; const IP_HOSTNAME_REGEX = /:|^\d+\.\d+\.\d+\.\d+$/; @@ -82,6 +83,16 @@ export async function createHttpsServer( return https.createServer(credentials, app) as http.Server; } +function getCertificateExpiration(cert: string, logger?: Logger) { + try { + const crt = forge.pki.certificateFromPem(cert); + return crt.validity.notAfter.getTime() - Date.now(); + } catch (error) { + logger?.warn(`Unable to parse self-signed certificate. ${error}`); + return 0; + } +} + async function getGeneratedCertificate(hostname: string, logger?: Logger) { const hasModules = await fs.pathExists('node_modules'); let certPath; @@ -94,23 +105,18 @@ async function getGeneratedCertificate(hostname: string, logger?: Logger) { certPath = resolvePath('.dev-cert.pem'); } - let cert = undefined; if (await fs.pathExists(certPath)) { - const stat = await fs.stat(certPath); - const ageMs = Date.now() - stat.ctimeMs; - if (stat.isFile() && ageMs < ALMOST_MONTH_IN_MS) { - cert = await fs.readFile(certPath); + const cert = await fs.readFile(certPath); + const remainingMs = getCertificateExpiration(cert.toString(), logger); + if (remainingMs > FIVE_DAYS_IN_MS) { + logger?.info('Using existing self-signed certificate'); + return { + key: cert, + cert, + }; } } - if (cert) { - logger?.info('Using existing self-signed certificate'); - return { - key: cert, - cert: cert, - }; - } - logger?.info('Generating new self-signed certificate'); const newCert = await createCertificate(hostname); await fs.writeFile(certPath, newCert.cert + newCert.key, 'utf8'); diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 8d980fe1bc..9defd1025a 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/backend-defaults +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + +## 0.1.2 + +### Patch Changes + +- 96d288a02d: Added root logger service to the set of default services. +- Updated dependencies + - @backstage/backend-app-api@0.2.2 + - @backstage/backend-plugin-api@0.1.3 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + ## 0.1.2-next.1 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 1d0a3f09f6..07fa8c16eb 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.1.2-next.1", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md index e8d6cefa1b..60d4a9567c 100644 --- a/packages/backend-next/CHANGELOG.md +++ b/packages/backend-next/CHANGELOG.md @@ -1,5 +1,35 @@ # example-backend-next +## 0.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/plugin-app-backend@0.3.38-next.0 + - @backstage/backend-defaults@0.1.3-next.0 + +## 0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/backend-defaults@0.1.2 + - @backstage/plugin-app-backend@0.3.37 + +## 0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/backend-defaults@0.1.2-next.2 + ## 0.0.4-next.1 ### Patch Changes diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index 7db8ef9775..003ae5d791 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-next", - "version": "0.0.4-next.1", + "version": "0.0.5-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 21897d0a97..512f0562f6 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/backend-plugin-api +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.1.3 + +### Patch Changes + +- 28377dc89f: Allow interfaces to be used for inferred option types. +- a35a27df70: Added documentation for `createBackendModule`, with guidelines for choosing a module ID. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.3-next.1 ### Patch Changes diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 80e3a8adfa..4804213f5c 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-plugin-api", "description": "Core API used by Backstage backend plugins", - "version": "0.1.3-next.1", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 97468f3856..ecef237ff0 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/backend-tasks +## 0.3.7-next.0 + +### Patch Changes + +- 30e43717c7: Deprecated the `HumanDuration` type, which should now instead be imported from `@backstage/types`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.3.6 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.3.6-next.2 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.3.6-next.1 ### Patch Changes diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md index a60f6dba37..8f53d1e5ae 100644 --- a/packages/backend-tasks/api-report.md +++ b/packages/backend-tasks/api-report.md @@ -7,19 +7,11 @@ import { AbortSignal as AbortSignal_2 } from 'node-abort-controller'; import { Config } from '@backstage/config'; import { DatabaseManager } from '@backstage/backend-common'; import { Duration } from 'luxon'; +import { HumanDuration as HumanDuration_2 } from '@backstage/types'; import { Logger } from 'winston'; -// @public -export type HumanDuration = { - years?: number; - months?: number; - weeks?: number; - days?: number; - hours?: number; - minutes?: number; - seconds?: number; - milliseconds?: number; -}; +// @public @deprecated +export type HumanDuration = HumanDuration_2; // @public export interface PluginTaskScheduler { @@ -59,10 +51,10 @@ export interface TaskScheduleDefinition { cron: string; } | Duration - | HumanDuration; - initialDelay?: Duration | HumanDuration; + | HumanDuration_2; + initialDelay?: Duration | HumanDuration_2; scope?: 'global' | 'local'; - timeout: Duration | HumanDuration; + timeout: Duration | HumanDuration_2; } // @public @@ -72,10 +64,10 @@ export interface TaskScheduleDefinitionConfig { cron: string; } | string - | HumanDuration; - initialDelay?: string | HumanDuration; + | HumanDuration_2; + initialDelay?: string | HumanDuration_2; scope?: 'global' | 'local'; - timeout: string | HumanDuration; + timeout: string | HumanDuration_2; } // @public diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 9fc7d6cbae..69ab5713dd 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.3.6-next.1", + "version": "0.3.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-tasks/src/deprecated.ts b/packages/backend-tasks/src/deprecated.ts new file mode 100644 index 0000000000..4d86cb1700 --- /dev/null +++ b/packages/backend-tasks/src/deprecated.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { HumanDuration as TypesHumanDuration } from '@backstage/types'; + +/** + * Human friendly durations object. + * + * @public + * @deprecated Import from `@backstage/types` instead + */ +export type HumanDuration = TypesHumanDuration; diff --git a/packages/backend-tasks/src/index.ts b/packages/backend-tasks/src/index.ts index dd75aca68c..d35917056c 100644 --- a/packages/backend-tasks/src/index.ts +++ b/packages/backend-tasks/src/index.ts @@ -21,3 +21,4 @@ */ export * from './tasks'; +export * from './deprecated'; diff --git a/packages/backend-tasks/src/tasks/index.ts b/packages/backend-tasks/src/tasks/index.ts index 8f30e5c8dd..f2037ac9dd 100644 --- a/packages/backend-tasks/src/tasks/index.ts +++ b/packages/backend-tasks/src/tasks/index.ts @@ -23,5 +23,4 @@ export type { TaskRunner, TaskScheduleDefinition, TaskScheduleDefinitionConfig, - HumanDuration, } from './types'; diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts index 115ef89168..fc2a045614 100644 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts @@ -15,9 +15,9 @@ */ import { ConfigReader } from '@backstage/config'; +import { HumanDuration } from '@backstage/types'; import { Duration } from 'luxon'; import { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; -import { HumanDuration } from './types'; describe('readTaskScheduleDefinitionFromConfig', () => { it('all valid values', () => { diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts index 3267d8ef14..1448a328f8 100644 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts @@ -15,8 +15,8 @@ */ import { Config } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; -import { HumanDuration, TaskScheduleDefinition } from './types'; +import { HumanDuration, JsonObject } from '@backstage/types'; +import { TaskScheduleDefinition } from './types'; import { Duration } from 'luxon'; const propsOfHumanDuration = [ diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts index 92ec045605..dbf2595e1d 100644 --- a/packages/backend-tasks/src/tasks/types.ts +++ b/packages/backend-tasks/src/tasks/types.ts @@ -14,26 +14,12 @@ * limitations under the License. */ +import { HumanDuration } from '@backstage/types'; import { CronTime } from 'cron'; import { Duration } from 'luxon'; import { AbortSignal } from 'node-abort-controller'; import { z } from 'zod'; -/** - * Human friendly durations object - * @public - */ -export type HumanDuration = { - years?: number; - months?: number; - weeks?: number; - days?: number; - hours?: number; - minutes?: number; - seconds?: number; - milliseconds?: number; -}; - /** * A function that can be called as a scheduled task. * diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 753ad24289..6847198298 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/backend-test-utils +## 0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/backend-app-api@0.2.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.1.29 + +### Patch Changes + +- 72549952d1: Fixed handling of root scoped services in `startTestBackend`. +- e91e8e9c55: Increased test database max connection pool size to reduce the risk of resource exhaustion. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/cli@0.20.0 + - @backstage/backend-app-api@0.2.2 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/config@1.0.3 + +## 0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.29-next.1 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 5249196860..96fb7657bd 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-test-utils", "description": "Test helpers library for Backstage backends", - "version": "0.1.29-next.1", + "version": "0.1.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index c5dc2f71bd..098c836bd5 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,132 @@ # example-backend +## 0.2.77-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/plugin-techdocs-backend@1.4.1-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/plugin-auth-backend@0.17.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-tech-insights-backend@0.5.4-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/plugin-azure-sites-backend@0.1.0-next.0 + - @backstage/plugin-kubernetes-backend@0.8.0-next.0 + - example-app@0.2.77-next.0 + - @backstage/plugin-app-backend@0.3.38-next.0 + - @backstage/plugin-azure-devops-backend@0.3.17-next.0 + - @backstage/plugin-badges-backend@0.1.32-next.0 + - @backstage/plugin-code-coverage-backend@0.2.4-next.0 + - @backstage/plugin-graphql-backend@0.1.28-next.0 + - @backstage/plugin-jenkins-backend@0.1.28-next.0 + - @backstage/plugin-kafka-backend@0.2.31-next.0 + - @backstage/plugin-permission-backend@0.5.13-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-playlist-backend@0.2.1-next.0 + - @backstage/plugin-proxy-backend@0.2.32-next.0 + - @backstage/plugin-rollbar-backend@0.1.35-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.0 + - @backstage/plugin-search-backend@1.1.1-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.4-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.2-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.22-next.0 + - @backstage/plugin-todo-backend@0.1.35-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-auth-backend@0.17.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-tech-insights-backend@0.5.3 + - @backstage/plugin-search-backend@1.1.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-playlist-backend@0.2.0 + - @backstage/plugin-jenkins-backend@0.1.27 + - @backstage/plugin-app-backend@0.3.37 + - @backstage/plugin-badges-backend@0.1.31 + - @backstage/plugin-graphql-backend@0.1.27 + - @backstage/plugin-permission-backend@0.5.12 + - @backstage/plugin-rollbar-backend@0.1.34 + - @backstage/plugin-kubernetes-backend@0.7.3 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/plugin-search-backend-module-pg@0.4.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3 + - @backstage/plugin-techdocs-backend@1.4.0 + - @backstage/plugin-tech-insights-node@0.3.5 + - example-app@0.2.76 + - @backstage/plugin-code-coverage-backend@0.2.3 + - @backstage/plugin-kafka-backend@0.2.30 + - @backstage/plugin-todo-backend@0.1.34 + - @backstage/plugin-azure-devops-backend@0.3.16 + - @backstage/plugin-proxy-backend@0.2.31 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + +## 0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-playlist-backend@0.2.0-next.2 + - @backstage/plugin-badges-backend@0.1.31-next.2 + - @backstage/plugin-graphql-backend@0.1.27-next.2 + - @backstage/plugin-permission-backend@0.5.12-next.2 + - @backstage/plugin-rollbar-backend@0.1.34-next.2 + - @backstage/plugin-search-backend@1.1.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.3-next.2 + - @backstage/plugin-techdocs-backend@1.4.0-next.2 + - example-app@0.2.76-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/plugin-auth-backend@0.17.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-azure-devops-backend@0.3.16-next.2 + - @backstage/plugin-code-coverage-backend@0.2.3-next.2 + - @backstage/plugin-jenkins-backend@0.1.27-next.2 + - @backstage/plugin-kafka-backend@0.2.30-next.2 + - @backstage/plugin-kubernetes-backend@0.7.3-next.2 + - @backstage/plugin-proxy-backend@0.2.31-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + - @backstage/plugin-todo-backend@0.1.34-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.2.76-next.1 ### Patch Changes diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index 21ff07deb4..60c6ffd27f 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -13,9 +13,10 @@ FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. @@ -38,7 +39,8 @@ COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz # Note that this install is not immutable, which is one of the reasons we don't recommend Yarn 3 yet -RUN yarn workspaces focus --all --production && rm -rf "$(yarn cache clean)" +RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,sharing=locked,uid=1000,gid=1000 \ + yarn workspaces focus --all --production # Then copy the rest of the backend bundle, along with any other files we might want. COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ diff --git a/packages/backend/package.json b/packages/backend/package.json index 75a116ee2a..0852985d82 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.76-next.1", + "version": "0.2.77-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,6 +36,7 @@ "@backstage/plugin-auth-backend": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-azure-devops-backend": "workspace:^", + "@backstage/plugin-azure-sites-backend": "workspace:^", "@backstage/plugin-badges-backend": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", "@backstage/plugin-code-coverage-backend": "workspace:^", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 0e5a847703..f5b0e5b2c3 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/catalog-client +## 1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + +## 1.1.1 + +### Patch Changes + +- 4f2ac624b4: Renamed argument in `validateEntity` from `location` to `locationRef` +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/errors@1.1.2 + +## 1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + ## 1.1.1-next.1 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 31b8654556..b5f4d69858 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-client", "description": "An isomorphic client for the catalog backend", - "version": "1.1.1-next.1", + "version": "1.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 6352f13760..87ca344f30 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/catalog-model +## 1.1.3-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 1.1.2 + +### Patch Changes + +- 6f3b8d0962: Defer `ajv` compilation of schema validators to improve module-import performance +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 1.1.2-next.1 ### Patch Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index b1a83fbeb0..0651b6d3ba 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-model", "description": "Types and validators that help describe the model of a Backstage Catalog", - "version": "1.1.2-next.1", + "version": "1.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-model/src/location/helpers.ts b/packages/catalog-model/src/location/helpers.ts index 8f8acc3b1d..6f9800c5a2 100644 --- a/packages/catalog-model/src/location/helpers.ts +++ b/packages/catalog-model/src/location/helpers.ts @@ -14,9 +14,8 @@ * limitations under the License. */ -import { ANNOTATION_SOURCE_LOCATION } from '.'; import { Entity, stringifyEntityRef } from '../entity'; -import { ANNOTATION_LOCATION } from './annotation'; +import { ANNOTATION_LOCATION, ANNOTATION_SOURCE_LOCATION } from './annotation'; /** * Parses a string form location reference. diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 90caca8f18..c3f0dde1fe 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,102 @@ # @backstage/cli +## 0.21.0-next.0 + +### Minor Changes + +- 7539b36748: Added a new ESLint rule that restricts imports of Link from @material-ui + + The rule can be can be overridden in the following way: + + ```diff + module.exports = require('@backstage/cli/config/eslint-factory')(__dirname, { + + restrictedImports: [ + + { name: '@material-ui/core', importNames: [] }, + + { name: '@material-ui/core/Link', importNames: [] }, + + ], + }); + ``` + +### Patch Changes + +- 4091c73e68: Updated `@swc/core` to version 1.3.9 which fixes a `.tsx` parser bug +- 9c767e8f45: Updated dependency `@svgr/plugin-jsx` to `6.5.x`. + Updated dependency `@svgr/plugin-svgo` to `6.5.x`. + Updated dependency `@svgr/rollup` to `6.5.x`. + Updated dependency `@svgr/webpack` to `6.5.x`. +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/release-manifests@0.0.6 + +## 0.20.0 + +### Minor Changes + +- f368ad7279: **BREAKING**: Bumped `jest`, `jest-runtime`, and `jest-environment-jsdom` to v29. This is up from v27, so check out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and [v29](https://jestjs.io/docs/upgrading-to-jest29) (later [here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides. + + Particular changes that where encountered in the main Backstage repo are: + + - The updated snapshot format. + - `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. + - Error objects collected by `withLogCollector` from `@backstage/test-utils` are now objects with a `detail` property rather than a string. + +### Patch Changes + +- 78d5eb299e: Tweak the Jest Caching loader to only operate when in `watch` mode +- 9c595302cb: Normalize on winston version ^3.2.1 +- 24b40140c4: Treat files in `__testUtils__` and `__mocks__` directories as test files for linting + purposes. + + Updates the parts of the eslint configuration generator that specify which files + should be treated as test code to include any files under two additional + directories: + + - `__mocks__`: this is the directory used by Jest[0] for mock code. + - `__testUtils__`: a suggested location for utility code executed only when + running tests. + + [0]: https://jestjs.io/docs/manual-mocks#mocking-user-modules + +- 3e309107ca: Updated fallback versions of dependencies in all templates. +- 292a088807: Added a new `repo test` command. +- ba63cae41c: Updated lockfile parsing to have better support for Yarn 3. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 2dddb32fea: Switched the Jest transform for YAML files to use a custom one available at `@backstage/cli/config/jestYamlTransform.js`. +- a541a3a78a: Switch to upfront resolution of `swc-loader` in Webpack config. +- cfb3598410: Removed `tsx` and `jsx` as supported extensions in backend packages. For most + repos, this will not have any effect. But if you inadvertently had added some + `tsx`/`jsx` files to your backend package, you may now start to see `code: 'MODULE_NOT_FOUND'` errors when launching the backend locally. The reason for + this is that the offending files get ignored during transpilation. Hence, the + importing file can no longer find anything to import. + + The fix is to rename any `.tsx` files in your backend packages to `.ts` instead, + or `.jsx` to `.js`. + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/errors@1.1.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## 0.20.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + ## 0.20.0-next.1 ### Patch Changes diff --git a/packages/cli/config/eslint-factory.js b/packages/cli/config/eslint-factory.js index 6e2a510c4a..04e23b2077 100644 --- a/packages/cli/config/eslint-factory.js +++ b/packages/cli/config/eslint-factory.js @@ -96,12 +96,16 @@ function createConfig(dir, extraConfig = {}) { `!${joinPath(dir, 'src/**')}`, joinPath(dir, 'src/**/*.test.*'), joinPath(dir, 'src/**/*.stories.*'), + joinPath(dir, 'src/**/__testUtils__/**'), + joinPath(dir, 'src/**/__mocks__/**'), joinPath(dir, 'src/setupTests.*'), ] : [ // Legacy config for packages that don't provide a dir '**/*.test.*', '**/*.stories.*', + '**/__testUtils__/**', + '**/__mocks__/**', '**/src/setupTests.*', '**/dev/**', ], @@ -137,6 +141,8 @@ function createConfig(dir, extraConfig = {}) { // Prevent imports of stories or tests '*.stories*', '*.test*', + '**/__testUtils__/**', + '**/__mocks__/**', ], }, ], @@ -159,7 +165,14 @@ function createConfig(dir, extraConfig = {}) { }, }, { - files: ['**/*.test.*', '**/*.stories.*', 'src/setupTests.*', '!src/**'], + files: [ + '**/*.test.*', + '**/*.stories.*', + '**/__testUtils__/**', + '**/__mocks__/**', + 'src/setupTests.*', + '!src/**', + ], rules: { ...testRules, 'no-restricted-syntax': [ @@ -226,6 +239,17 @@ function createConfigForRole(dir, role, extraConfig = {}) { name: '@material-ui/icons/', // because this is possible too ._. message: "Please import '@material-ui/icons/' instead.", }, + { + name: '@material-ui/core', + importNames: ['Link'], + message: + 'Prefer using `Link` from `@backstage/core-components` rather than material-UI', + }, + { + name: '@material-ui/core/Link', + message: + 'Prefer using `Link` from `@backstage/core-components` rather than material-UI', + }, ...require('module').builtinModules, ...(extraConfig.restrictedImports ?? []), ], diff --git a/packages/cli/package.json b/packages/cli/package.json index 66f69772d5..b62d3f8691 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.20.0-next.1", + "version": "0.21.0-next.0", "publishConfig": { "access": "public" }, @@ -48,11 +48,11 @@ "@spotify/eslint-config-typescript": "^14.0.0", "@sucrase/jest-plugin": "^2.1.1", "@sucrase/webpack-loader": "^2.0.0", - "@svgr/plugin-jsx": "6.3.x", - "@svgr/plugin-svgo": "6.3.x", - "@svgr/rollup": "6.3.x", - "@svgr/webpack": "6.3.x", - "@swc/core": "^1.2.239", + "@svgr/plugin-jsx": "6.5.x", + "@svgr/plugin-svgo": "6.5.x", + "@svgr/rollup": "6.5.x", + "@svgr/webpack": "6.5.x", + "@swc/core": "^1.3.9", "@swc/helpers": "^0.4.7", "@swc/jest": "^0.2.22", "@types/jest": "^29.0.0", diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index 0a72a5d415..df381159f0 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -33,7 +33,7 @@ "@types/express": "{{versionQuery '@types/express' '4.17.13'}}", "express": "{{versionQuery 'express' '4.18.1'}}", "express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}", - "winston": "{{versionQuery 'winston' '3.8.1'}}", + "winston": "{{versionQuery 'winston' '3.2.1'}}", "node-fetch": "{{versionQuery 'node-fetch' '2.6.7'}}", "yn": "{{versionQuery 'yn' '4.0.0'}}" }, diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 673054c186..161a8dbdf9 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/codemods +## 0.1.40 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## 0.1.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + ## 0.1.40-next.1 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 4996887eed..cc3ad6e468 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.40-next.1", + "version": "0.1.40", "publishConfig": { "access": "public", "main": "dist/index.cjs.js" diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index 0c1afa75c1..d6597c29a5 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/config-loader +## 1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 1.1.5-next.1 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 3fb1da9e1f..e322636d35 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "1.1.5-next.1", + "version": "1.1.6-next.0", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 151201bfa7..35b324bd24 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/config +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## 1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 1.0.3-next.1 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 2305784051..4c962dbf87 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "1.0.3-next.1", + "version": "1.0.4-next.0", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 8914390d89..07845f1031 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/core-app-api +## 1.2.0-next.0 + +### Minor Changes + +- 9b737e5f2e: Updated the React Router wiring to make use of the new `basename` property of the router components in React Router v6 stable. To implement this, a new optional `basename` property has been added to the `Router` app component, which can be forwarded to the concrete router implementation in order to support this new behavior. This is done by default in any app that does not have a `Router` component override. +- 127fcad26d: Deprecated the `homepage` config as the component that used it - `HomepageTimer` - has been removed and replaced by the `HeaderWorldClock` in the home plugin + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1 + +### Patch Changes + +- 27e6404aba: Fixed a bug where gathered index routes would fail to bind routable extensions. This would typically show up when placing a routable extension in the entity page overview tab. +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + ## 1.1.1-next.1 ### Patch Changes diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 4d1db6fde1..70312a54a5 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -144,7 +144,9 @@ export type AppComponents = { NotFoundErrorPage: ComponentType<{}>; BootErrorPage: ComponentType; Progress: ComponentType<{}>; - Router: ComponentType<{}>; + Router: ComponentType<{ + basename?: string; + }>; ErrorBoundaryFallback: ComponentType; ThemeProvider?: ComponentType<{}>; SignInPage?: ComponentType; diff --git a/packages/core-app-api/config.d.ts b/packages/core-app-api/config.d.ts index d88c818d11..ae876f7fe0 100644 --- a/packages/core-app-api/config.d.ts +++ b/packages/core-app-api/config.d.ts @@ -89,6 +89,11 @@ export interface Config { name?: string; }; + /** + * This config was used by the HomepageTimer but has been replaced by the HeaderWorldClock in the home plugin + * + * @deprecated in favor of the HeaderWorldClock which is found in the home plugin + */ homepage?: { clocks?: Array<{ /** @visibility frontend */ diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 219244eacc..09d5c75fa8 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "1.1.1-next.1", + "version": "1.2.0-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-app-api/src/app/AppManager.compat.test.tsx b/packages/core-app-api/src/app/AppManager.compat.test.tsx new file mode 100644 index 0000000000..bdebd79dc4 --- /dev/null +++ b/packages/core-app-api/src/app/AppManager.compat.test.tsx @@ -0,0 +1,107 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 tlr, { render } from '@testing-library/react'; +import React from 'react'; + +describe.each(['beta', 'stable'])('react-router %s', rrVersion => { + beforeAll(() => { + jest.doMock('react', () => React); + // This has some side effects, so need this to be stable to avoid re-require + jest.doMock('@testing-library/react', () => tlr); + jest.doMock('react-router', () => + rrVersion === 'beta' + ? jest.requireActual('react-router-beta') + : jest.requireActual('react-router-stable'), + ); + jest.doMock('react-router-dom', () => + rrVersion === 'beta' + ? jest.requireActual('react-router-dom-beta') + : jest.requireActual('react-router-dom-stable'), + ); + }); + + afterAll(() => { + jest.resetModules(); + }); + + function requireDeps() { + return { + ...(require('./AppManager') as typeof import('./AppManager')), + ...(require('../routing') as typeof import('../routing')), + ...(require('react-router-dom') as typeof import('react-router-dom')), + ...(require('@backstage/test-utils') as typeof import('@backstage/test-utils')), + }; + } + + describe('AppManager', () => { + it('supports base path', async () => { + const { AppManager, MemoryRouter, Navigate, Route, FlatRoutes } = + requireDeps(); + const app = new AppManager({ + apis: [], + defaultApis: [], + themes: [ + { + id: 'light', + title: 'Light Theme', + variant: 'light', + Provider: ({ children }) => <>{children}, + }, + ], + icons: {} as any, + plugins: [], + components: { + NotFoundErrorPage: () => null, + BootErrorPage: () => null, + Progress: () => null, + Router: ({ children, basename }) => ( + + ), + ErrorBoundaryFallback: () => null, + ThemeProvider: ({ children }) => <>{children}, + }, + configLoader: async () => [ + { + context: 'test', + data: { app: { baseUrl: 'http://localhost/foo' } }, + }, + ], + bindRoutes: () => {}, + }); + + const AppProvider = app.getProvider(); + const AppRouter = app.getRouter(); + + const rendered = render( + + + + } /> + bar} /> + + + , + ); + + await expect(rendered.findByText('bar')).resolves.toBeInTheDocument(); + }); + }); +}); diff --git a/packages/core-app-api/src/app/AppManager.stable.test.tsx b/packages/core-app-api/src/app/AppManager.stable.test.tsx new file mode 100644 index 0000000000..a9948a6070 --- /dev/null +++ b/packages/core-app-api/src/app/AppManager.stable.test.tsx @@ -0,0 +1,118 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter, Navigate, Route } from 'react-router-dom'; +import { FlatRoutes } from '../routing'; +import { AppManager } from './AppManager'; +import { AppOptions } from './types'; + +jest.mock('react-router', () => jest.requireActual('react-router-stable')); +jest.mock('react-router-dom', () => + jest.requireActual('react-router-dom-stable'), +); + +const mockAppOptions: AppOptions = { + apis: [], + defaultApis: [], + themes: [ + { + id: 'light', + title: 'Light Theme', + variant: 'light', + Provider: ({ children }) => <>{children}, + }, + ], + icons: {} as any, + plugins: [], + components: { + NotFoundErrorPage: () => null, + BootErrorPage: () => null, + Progress: () => null, + Router: props => , + ErrorBoundaryFallback: () => null, + ThemeProvider: ({ children }) => <>{children}, + }, + configLoader: async () => [], + bindRoutes: () => {}, +}; + +describe('AppManager', () => { + it('supports base path', async () => { + const app = new AppManager({ + ...mockAppOptions, + components: { + ...mockAppOptions.components, + Router: props => , + }, + configLoader: async () => [ + { + context: 'test', + data: { app: { baseUrl: 'http://localhost/foo' } }, + }, + ], + }); + + const AppProvider = app.getProvider(); + const AppRouter = app.getRouter(); + + const rendered = render( + + + + } /> + bar} /> + + + , + ); + + await expect(rendered.findByText('bar')).resolves.toBeInTheDocument(); + }); + + it('supports base path with absolute navigation', async () => { + const app = new AppManager({ + ...mockAppOptions, + components: { + ...mockAppOptions.components, + Router: props => , + }, + configLoader: async () => [ + { + context: 'test', + data: { app: { baseUrl: 'http://localhost/foo' } }, + }, + ], + }); + + const AppProvider = app.getProvider(); + const AppRouter = app.getRouter(); + + const rendered = render( + + + + } /> + bar} /> + + + , + ); + + await expect(rendered.findByText('bar')).resolves.toBeInTheDocument(); + }); +}); diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx index 89402476e3..0ca09c0a0c 100644 --- a/packages/core-app-api/src/app/AppManager.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -99,6 +99,21 @@ const InternalAppContext = createContext<{ * The returned path does not have a trailing slash. */ function getBasePath(configApi: Config) { + if (!isReactRouterBeta()) { + // When using rr v6 stable the base path is handled through the + // basename prop on the router component instead. + return ''; + } + + return readBasePath(configApi); +} + +/** + * Read the configured base path. + * + * The returned path does not have a trailing slash. + */ +function readBasePath(configApi: ConfigApi) { let { pathname } = new URL( configApi.getOptionalString('app.baseUrl') ?? '/', 'http://dummy.dev', // baseUrl can be specified as just a path @@ -361,7 +376,7 @@ export class AppManager implements BackstageApp { const AppRouter = ({ children }: PropsWithChildren<{}>) => { const configApi = useApi(configApiRef); - const basePath = getBasePath(configApi); + const basePath = readBasePath(configApi); const mountPath = `${basePath}/*`; const { routeObjects } = useContext(InternalAppContext); @@ -390,23 +405,43 @@ export class AppManager implements BackstageApp { { signOutTargetUrl: basePath || '/' }, ); + if (isReactRouterBeta()) { + return ( + + + + {children}} /> + + + ); + } + + return ( + + + {children} + + ); + } + + if (isReactRouterBeta()) { return ( - - {children}} /> - + + + {children}} /> + + ); } return ( - + - - {children}} /> - + <>{children} ); diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 39bf9c0554..70022534a1 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -69,7 +69,7 @@ export type AppComponents = { NotFoundErrorPage: ComponentType<{}>; BootErrorPage: ComponentType; Progress: ComponentType<{}>; - Router: ComponentType<{}>; + Router: ComponentType<{ basename?: string }>; ErrorBoundaryFallback: ComponentType; ThemeProvider?: ComponentType<{}>; diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index a27be20b9f..00b1c55fdc 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/core-components +## 0.12.0-next.0 + +### Minor Changes + +- fb3733e446: **BREAKING**: Removed the `HomepageTimer` as it has been replaced by the `HeaderWorldClock` in the Home plugin and was deprecated over a year ago. + +### Patch Changes + +- 5f695c219a: Set the `searchTooltip` to "Filter" to follow how the `searchPlaceholder` is set making this more consistent +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 858986f6b6: Disable base path workaround in `Link` component when React Router v6 stable is used. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## 0.11.2 + +### Patch Changes + +- bde1e8c8e2: Added `curve` prop to the `DependencyGraph` component to select the type of layout +- 882101cd9b: Deep-import LightAsync component to improve module-import speed +- 0d0f30d87e: Fixed layout for core-components docs where table was broken by splitting with code sample +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## 0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + ## 0.11.2-next.1 ### Patch Changes diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 99d18b9b12..fc3ab4e5fc 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -242,6 +242,7 @@ export interface DependencyGraphProps acyclicer?: 'greedy'; // Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver align?: Alignment; + curve?: 'curveStepBefore' | 'curveMonotoneX'; defs?: SVGDefsElement | SVGDefsElement[]; // Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver direction?: Direction; @@ -494,9 +495,6 @@ export type HeaderTabsClassKey = // @public (undocumented) export function HelpIcon(props: IconComponentProps): JSX.Element; -// @public @deprecated -export function HomepageTimer(_props: {}): JSX.Element | null; - // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // // @public diff --git a/packages/core-components/package.json b/packages/core-components/package.json index a32d8c9af8..33dfc762c9 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.11.2-next.1", + "version": "0.12.0-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index 9f447f80cc..5159f67d9e 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -162,6 +162,14 @@ export interface DependencyGraphProps * Default: `enabled` */ zoom?: 'enabled' | 'disabled' | 'enable-on-click'; + /** + * A factory for curve generators addressing both lines and areas. + * + * @remarks + * + * Default: 'curveMonotoneX' + */ + curve?: 'curveStepBefore' | 'curveMonotoneX'; } const WORKSPACE_ID = 'workspace'; @@ -194,6 +202,7 @@ export function DependencyGraph( renderLabel, defs, zoom = 'enabled', + curve = 'curveMonotoneX', ...svgProps } = props; const theme: BackstageTheme = useTheme(); @@ -424,6 +433,7 @@ export function DependencyGraph( setEdge={setEdge} render={renderLabel} edge={edge} + curve={curve} /> ); })} diff --git a/packages/core-components/src/components/DependencyGraph/Edge.test.tsx b/packages/core-components/src/components/DependencyGraph/Edge.test.tsx index db6fbe7552..21d677388e 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.test.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.test.tsx @@ -21,6 +21,7 @@ import { RenderLabelProps } from './types'; const fromNode = 'node'; const toNode = 'other-node'; +const curve: 'curveStepBefore' | 'curveMonotoneX' = 'curveMonotoneX'; const edge = { points: [ @@ -46,6 +47,7 @@ const minProps = { setEdge, renderElement, edge, + curve, }; const label = 'label'; diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx index cc6a044beb..aae737b9ea 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx @@ -25,7 +25,7 @@ import { DependencyEdge, LabelPosition, } from './types'; -import { ARROW_MARKER_ID, EDGE_TEST_ID, LABEL_TEST_ID } from './constants'; +import { EDGE_TEST_ID, LABEL_TEST_ID } from './constants'; import { DefaultLabel } from './DefaultLabel'; import dagre from 'dagre'; @@ -47,7 +47,7 @@ export type DependencyGraphEdgeClassKey = 'path' | 'label'; const useStyles = makeStyles( (theme: BackstageTheme) => ({ path: { - strokeWidth: 2, + strokeWidth: 1, stroke: theme.palette.textSubtle, fill: 'none', transition: `${theme.transitions.duration.shortest}ms`, @@ -70,23 +70,19 @@ export type EdgeComponentProps = { id: dagre.Edge, edge: DependencyEdge, ) => dagre.graphlib.Graph<{}>; + curve: 'curveStepBefore' | 'curveMonotoneX'; }; const renderDefault = (props: RenderLabelProps) => ( ); -const createPath = d3Shape - .line() - .x(d => d.x) - .y(d => d.y) - .curve(d3Shape.curveMonotoneX); - export function Edge({ render = renderDefault, setEdge, id, edge, + curve, }: EdgeComponentProps) { const { x = 0, y = 0, width, height, points } = edge; const labelProps: DependencyEdge = edge; @@ -114,6 +110,16 @@ export function Edge({ let path: string = ''; + const createPath = React.useMemo( + () => + d3Shape + .line() + .x(d => d.x) + .y(d => d.y) + .curve(d3Shape[curve]), + [curve], + ); + if (points) { const finitePoints = points.filter( (point: EdgePoint) => isFinite(point.x) && isFinite(point.y), @@ -124,12 +130,7 @@ export function Edge({ return ( <> {path && ( - + )} {labelProps.label ? ( +
{icon} {label} - +
); } @@ -96,7 +91,6 @@ export function IconLinkVertical({ title={title} className={classnames(classes.link, classes[color])} to={href} - component={RouterLink} onClick={onClick} > {icon} diff --git a/packages/core-components/src/components/Link/Link.test.tsx b/packages/core-components/src/components/Link/Link.test.tsx index 52e687d2e4..d65351d840 100644 --- a/packages/core-components/src/components/Link/Link.test.tsx +++ b/packages/core-components/src/components/Link/Link.test.tsx @@ -107,58 +107,6 @@ describe('', () => { }); }); - describe('resolves a sub-path correctly', () => { - it('when it starts with base path', async () => { - const testString = 'This is test string'; - const linkText = 'Navigate!'; - const configApi = new ConfigReader({ - app: { baseUrl: 'http://localhost:3000/example' }, - }); - - const { getByText } = render( - wrapInTestApp( - - {linkText} - - {testString}

} /> -
-
, - ), - ); - - expect(() => getByText(testString)).toThrow(); - fireEvent.click(getByText(linkText)); - await waitFor(() => { - expect(getByText(testString)).toBeInTheDocument(); - }); - }); - - it('when it does not start with base path', async () => { - const testString = 'This is test string'; - const linkText = 'Navigate!'; - const configApi = new ConfigReader({ - app: { baseUrl: 'http://localhost:3000/example' }, - }); - - const { getByText } = render( - wrapInTestApp( - - {linkText} - - {testString}

} /> -
-
, - ), - ); - - expect(() => getByText(testString)).toThrow(); - fireEvent.click(getByText(linkText)); - await waitFor(() => { - expect(getByText(testString)).toBeInTheDocument(); - }); - }); - }); - describe('isExternalUri', () => { it.each([ [true, 'http://'], diff --git a/packages/core-components/src/components/Link/Link.tsx b/packages/core-components/src/components/Link/Link.tsx index 92ac92daad..66b7453abd 100644 --- a/packages/core-components/src/components/Link/Link.tsx +++ b/packages/core-components/src/components/Link/Link.tsx @@ -16,6 +16,7 @@ import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api'; import classnames from 'classnames'; +// eslint-disable-next-line no-restricted-imports import MaterialLink, { LinkProps as MaterialLinkProps, } from '@material-ui/core/Link'; @@ -26,6 +27,12 @@ import { LinkProps as RouterLinkProps, } from 'react-router-dom'; import { trimEnd } from 'lodash'; +import { createRoutesFromChildren, Route } from 'react-router-dom'; + +export function isReactRouterBeta(): boolean { + const [obj] = createRoutesFromChildren(} />); + return !obj.index; +} const useStyles = makeStyles( { @@ -79,6 +86,7 @@ const useBasePath = () => { return trimEnd(pathname, '/'); }; +/** @deprecated Remove once we no longer support React Router v6 beta */ export const useResolvedPath = (uri: LinkProps['to']) => { let resolvedPath = String(uri); @@ -125,7 +133,12 @@ export const Link = React.forwardRef( ({ onClick, noTrack, ...props }, ref) => { const classes = useStyles(); const analytics = useAnalytics(); - const to = useResolvedPath(props.to); + + // Adding the base path to URLs breaks react-router v6 stable, so we only + // do it for beta. The react router version won't change at runtime so it is + // fine to ignore the rules of hooks. + // eslint-disable-next-line react-hooks/rules-of-hooks + const to = isReactRouterBeta() ? useResolvedPath(props.to) : props.to; const linkText = getNodeText(props.children) || to; const external = isExternalUri(to); const newWindow = external && !!/^https?:/.exec(to); diff --git a/packages/core-components/src/components/LogViewer/LogViewer.tsx b/packages/core-components/src/components/LogViewer/LogViewer.tsx index bdb3973015..20530cffaf 100644 --- a/packages/core-components/src/components/LogViewer/LogViewer.tsx +++ b/packages/core-components/src/components/LogViewer/LogViewer.tsx @@ -44,6 +44,7 @@ export interface LogViewerProps { /** * A component that displays logs in a scrollable text area. * + * @remarks * The LogViewer has support for search and filtering, as well as displaying * text content with ANSI color escape codes. * diff --git a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx index d97172bf75..425e723052 100644 --- a/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx +++ b/packages/core-components/src/components/MarkdownContent/MarkdownContent.tsx @@ -110,10 +110,10 @@ const components: Options['components'] = { }; /** - * MarkdownContent - * -- - * Renders markdown with the default dialect [gfm - GitHub flavored Markdown](https://github.github.com/gfm/) to backstage theme styled HTML. - * If you just want to render to plain [CommonMark](https://commonmark.org/), set the dialect to `'common-mark'` + * Renders markdown with the default dialect {@link https://github.github.com/gfm/ | gfm - GitHub flavored Markdown} to backstage theme styled HTML. + * + * @remarks + * If you just want to render to plain {@link https://commonmark.org/ | CommonMark}, set the dialect to `'common-mark'` */ export function MarkdownContent(props: Props) { const { diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx index c61069f810..2fea7fa5e1 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.test.tsx @@ -16,8 +16,7 @@ import React from 'react'; import { render } from '@testing-library/react'; - -import { OverflowTooltip } from '.'; +import { OverflowTooltip } from './OverflowTooltip'; describe('', () => { it('renders without exploding', async () => { diff --git a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx index 9600a3f179..f51c73195c 100644 --- a/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx +++ b/packages/core-components/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx @@ -44,6 +44,7 @@ const useStyles = makeStyles( /** * Renders a warning panel as the effect of a failed server request. * + * @remarks * Has special treatment for ResponseError errors, to display rich * server-provided information about what happened. */ diff --git a/packages/core-components/src/components/TabbedLayout/TabbedLayout.tsx b/packages/core-components/src/components/TabbedLayout/TabbedLayout.tsx index 26e70e33b5..66a9b498cc 100644 --- a/packages/core-components/src/components/TabbedLayout/TabbedLayout.tsx +++ b/packages/core-components/src/components/TabbedLayout/TabbedLayout.tsx @@ -71,6 +71,7 @@ export function createSubRoutesFromChildren( * TabbedLayout is a compound component, which allows you to define a layout for * pages using a sub-navigation mechanism. * + * @remarks * Consists of two parts: TabbedLayout and TabbedLayout.Route * * @example diff --git a/packages/core-components/src/components/Table/Table.stories.tsx b/packages/core-components/src/components/Table/Table.stories.tsx index b057d0a12c..807d58057d 100644 --- a/packages/core-components/src/components/Table/Table.stories.tsx +++ b/packages/core-components/src/components/Table/Table.stories.tsx @@ -17,8 +17,8 @@ import { makeStyles } from '@material-ui/core/styles'; import React from 'react'; import { Link } from '../Link'; -import { SubvalueCell, Table, TableColumn } from '.'; -import { TableFilter } from './Table'; +import { SubvalueCell } from './SubvalueCell'; +import { Table, TableColumn, TableFilter } from './Table'; export default { title: 'Data Display/Table', diff --git a/packages/core-components/src/components/Table/Table.tsx b/packages/core-components/src/components/Table/Table.tsx index 49538b680a..81471324a0 100644 --- a/packages/core-components/src/components/Table/Table.tsx +++ b/packages/core-components/src/components/Table/Table.tsx @@ -516,7 +516,9 @@ export function Table(props: TableProps) { } data={typeof data === 'function' ? data : tableData} style={{ width: '100%' }} - localization={{ toolbar: { searchPlaceholder: 'Filter' } }} + localization={{ + toolbar: { searchPlaceholder: 'Filter', searchTooltip: 'Filter' }, + }} {...restProps} />
diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx index 661bb1da22..2ddcef52ca 100644 --- a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx +++ b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx @@ -134,7 +134,7 @@ const capitalize = (s: string) => { }; /** - * WarningPanel. Show a user friendly error message to a user similar to + * Show a user friendly error message to a user similar to * ErrorPanel except that the warning panel only shows the warning message to * the user. * diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx index f9348ffa9a..a6bfc79040 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.test.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ -import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; -import { HeaderTabs } from '.'; -import { makeStyles } from '@material-ui/core/styles'; import Badge from '@material-ui/core/Badge'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { HeaderTabs } from './HeaderTabs'; const mockTabs = [ { id: 'overview', label: 'Overview' }, diff --git a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx b/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx deleted file mode 100644 index 9a7a8ac47d..0000000000 --- a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * 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 { - renderWithEffects, - TestApiProvider, - withLogCollector, -} from '@backstage/test-utils'; -import { HomepageTimer } from './HomepageTimer'; -import React from 'react'; -import { lightTheme } from '@backstage/theme'; -import { ThemeProvider } from '@material-ui/core/styles'; -import { ConfigReader } from '@backstage/core-app-api'; -import { ConfigApi, configApiRef } from '@backstage/core-plugin-api'; - -it('changes default timezone to GMT', async () => { - const configApi: ConfigApi = new ConfigReader({ - homepage: { - clocks: [ - { - label: 'New York', - timezone: 'America/New_Pork', - }, - ], - }, - context: 'test', - }); - - const { warn } = await withLogCollector(async () => { - const rendered = await renderWithEffects( - - - - - , - ); - - expect(rendered.getByText('GMT')).toBeInTheDocument(); - }); - expect(warn).toEqual([ - 'The timezone America/New_Pork is invalid. Defaulting to GMT', - ]); -}); diff --git a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx b/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx deleted file mode 100644 index 71e764987b..0000000000 --- a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { HeaderLabel } from '../HeaderLabel'; -import { ConfigApi, useApi, configApiRef } from '@backstage/core-plugin-api'; - -const timeFormat: Intl.DateTimeFormatOptions = { - hour: '2-digit', - minute: '2-digit', -}; - -type TimeObj = { - time: string; - label: string; -}; - -function getTimes(configApi: ConfigApi) { - const d = new Date(); - const lang = window.navigator.language; - - const clocks: TimeObj[] = []; - - if (!configApi.has('homepage.clocks')) { - return clocks; - } - - const clockConfigs = configApi.getConfigArray('homepage.clocks'); - - for (const clock of clockConfigs) { - if (clock.has('label') && clock.has('timezone')) { - let label = clock.getString('label'); - - const options: Intl.DateTimeFormatOptions = { - timeZone: clock.getString('timezone'), - ...timeFormat, - }; - - try { - new Date().toLocaleString(lang, options); - } catch (e) { - // eslint-disable-next-line no-console - console.warn( - `The timezone ${options.timeZone} is invalid. Defaulting to GMT`, - ); - options.timeZone = 'GMT'; - label = 'GMT'; - } - - const time = d.toLocaleTimeString(lang, options); - clocks.push({ time, label }); - } - } - return clocks; -} - -/** - * Please use the HeaderWorldClock in the home plugin - * - * @public - * @deprecated in favor of the HeaderWorldClock which is found in the to home plugin - */ -export function HomepageTimer(_props: {}) { - const configApi = useApi(configApiRef); - - const defaultTimes: TimeObj[] = []; - const [clocks, setTimes] = React.useState(defaultTimes); - - React.useEffect(() => { - setTimes(getTimes(configApi)); - - const intervalId = setInterval(() => { - setTimes(getTimes(configApi)); - }, 1000); - - return () => { - clearInterval(intervalId); - }; - }, [configApi]); - - if (clocks.length !== 0) { - return ( - <> - {clocks.map(clock => ( - - ))} - - ); - } - return null; -} diff --git a/packages/core-components/src/layout/ItemCard/ItemCard.tsx b/packages/core-components/src/layout/ItemCard/ItemCard.tsx index e2efd0c136..fedcf7de8f 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCard.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCard.tsx @@ -40,6 +40,7 @@ type ItemCardProps = { * This card type has been deprecated. Instead use plain MUI Card and helpers * where appropriate. * + * @example * ``` * * diff --git a/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx b/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx index b27d224418..885b43ca34 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx @@ -49,6 +49,7 @@ export type ItemCardGridProps = Partial> & { * A default grid to use when arranging "item cards" - cards that let users * select among several options. * + * @remarks * The immediate children are expected to be MUI Card components. * * Styles for the grid can be overridden using the `classes` prop, e.g.: diff --git a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx index d9443ffc9a..76cf668079 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx @@ -64,6 +64,7 @@ export type ItemCardHeaderProps = Partial> & { * A simple card header, rendering a default look for "item cards" - cards that * are arranged in a grid for users to select among several options. * + * @remarks * This component expects to be placed within a MUI ``. * * Styles for the header can be overridden using the `classes` prop, e.g.: diff --git a/packages/core-components/src/layout/Page/Page.stories.tsx b/packages/core-components/src/layout/Page/Page.stories.tsx index 12ddd51d04..e8fdeea17b 100644 --- a/packages/core-components/src/layout/Page/Page.stories.tsx +++ b/packages/core-components/src/layout/Page/Page.stories.tsx @@ -14,22 +14,12 @@ * limitations under the License. */ +import { wrapInTestApp } from '@backstage/test-utils'; import Box from '@material-ui/core/Box'; import Chip from '@material-ui/core/Chip'; import Grid from '@material-ui/core/Grid'; -import Link from '@material-ui/core/Link'; import Typography from '@material-ui/core/Typography'; import React, { useState } from 'react'; -import { wrapInTestApp } from '@backstage/test-utils'; -import { - Content, - ContentHeader, - Header, - HeaderLabel, - HeaderTabs, - InfoCard, - Page, -} from '..'; import { GaugeCard, StatusOK, @@ -37,7 +27,15 @@ import { Table, TableColumn, TrendLine, + Link, } from '../../components'; +import { Content } from '../Content'; +import { ContentHeader } from '../ContentHeader'; +import { Header } from '../Header'; +import { HeaderLabel } from '../HeaderLabel'; +import { HeaderTabs } from '../HeaderTabs'; +import { InfoCard } from '../InfoCard'; +import { Page } from '../Page'; export default { title: 'Plugins/Examples', @@ -77,7 +75,7 @@ const columns: TableColumn[] = [ highlight: true, render: (row: Partial) => ( <> - {row.branch} + {row.branch} {row.hash} ), @@ -171,7 +169,8 @@ const DataGrid = () => ( able to function. - Contact #cost-awareness for information and support. + Contact #cost-awareness for + information and support. diff --git a/packages/core-components/src/layout/Sidebar/Bar.test.tsx b/packages/core-components/src/layout/Sidebar/Bar.test.tsx index 87e62f15e0..fb92e491f8 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.test.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.test.tsx @@ -22,15 +22,11 @@ import MenuBookIcon from '@material-ui/icons/MenuBook'; import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; -import { - Sidebar, - SidebarExpandButton, - SidebarItem, - SidebarSearchField, - SidebarPinStateProvider, - SidebarSubmenu, - SidebarSubmenuItem, -} from '.'; +import { Sidebar } from './Bar'; +import { SidebarExpandButton, SidebarItem, SidebarSearchField } from './Items'; +import { SidebarPinStateProvider } from './SidebarPinStateContext'; +import { SidebarSubmenu } from './SidebarSubmenu'; +import { SidebarSubmenuItem } from './SidebarSubmenuItem'; async function renderScalableSidebar() { await renderInTestApp( diff --git a/packages/core-components/src/layout/Sidebar/Intro.tsx b/packages/core-components/src/layout/Sidebar/Intro.tsx index 05d580c960..91e188b203 100644 --- a/packages/core-components/src/layout/Sidebar/Intro.tsx +++ b/packages/core-components/src/layout/Sidebar/Intro.tsx @@ -16,8 +16,8 @@ import { BackstageTheme } from '@backstage/theme'; import Collapse from '@material-ui/core/Collapse'; -import Link from '@material-ui/core/Link'; import { makeStyles } from '@material-ui/core/styles'; +import IconButton from '@material-ui/core/IconButton'; import Typography from '@material-ui/core/Typography'; import CloseIcon from '@material-ui/icons/Close'; import React, { useContext, useState } from 'react'; @@ -106,17 +106,12 @@ export function IntroCard(props: IntroCardProps) {
{text}
- + Dismiss - +
); diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 5a0b92f558..fe8dede499 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -53,16 +53,13 @@ import { SidebarItemWithSubmenuContext, SidebarConfig, } from './config'; -import { - SidebarSubmenuItemProps, - SidebarSubmenuProps, - SidebarSubmenu, -} from '.'; +import { SidebarSubmenuProps, SidebarSubmenu } from './SidebarSubmenu'; import DoubleArrowLeft from './icons/DoubleArrowLeft'; import DoubleArrowRight from './icons/DoubleArrowRight'; import { isLocationMatch } from './utils'; import { Location } from 'history'; import { useSidebarOpenState } from './SidebarOpenStateContext'; +import { SidebarSubmenuItemProps } from './SidebarSubmenuItem'; /** @public */ export type SidebarItemClassKey = @@ -214,7 +211,7 @@ function useMemoStyles(sidebarConfig: SidebarConfig) { /** * Evaluates the routes of the SubmenuItems & nested DropdownItems. - * The reeveluation is only triggered, if the `locationPathname` changes, as `useElementFilter` uses memorization. + * The reevaluation is only triggered, if the `locationPathname` changes, as `useElementFilter` uses memorization. * * @param submenu SidebarSubmenu component * @param location Location @@ -510,6 +507,7 @@ const SidebarItemWithSubmenu = ({ /** * Creates a `SidebarItem` * + * @remarks * If children contain a `SidebarSubmenu` component the `SidebarItem` will have a expandable submenu */ export const SidebarItem = forwardRef((props, ref) => { @@ -663,6 +661,8 @@ export const SidebarScrollWrapper = styled('div')(({ theme }) => { /** * A button which allows you to expand the sidebar when clicked. + * + * @remarks * Use optionally to replace sidebar's expand-on-hover feature with expand-on-click. * * If you are using this you might want to set the `disableExpandOnHover` of the `Sidebar` to `true`. diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx index 8dd06b6e2b..7012afabf1 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.test.tsx @@ -21,13 +21,11 @@ import LayersIcon from '@material-ui/icons/Layers'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import { fireEvent } from '@testing-library/react'; import React from 'react'; -import { - MobileSidebar, - Sidebar, - SidebarGroup, - SidebarItem, - SidebarPage, -} from '.'; +import { Sidebar } from './Bar'; +import { SidebarItem } from './Items'; +import { MobileSidebar } from './MobileSidebar'; +import { SidebarPage } from './Page'; +import { SidebarGroup } from './SidebarGroup'; const MobileSidebarWithGroups = () => ( diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx index c85e6d11f2..bfa841690d 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx @@ -159,6 +159,7 @@ export const MobileSidebarContext = createContext({ /** * A navigation component for mobile screens, which sticks to the bottom. * + * @remarks * It alternates the normal sidebar by grouping the `SidebarItems` based on provided `SidebarGroup`s * either rendering them as a link or an overlay menu. * If no `SidebarGroup`s are provided the sidebar content is wrapped in an default overlay menu. diff --git a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx index 9b5d76a32c..8daf7b69b6 100644 --- a/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx +++ b/packages/core-components/src/layout/Sidebar/Sidebar.stories.tsx @@ -24,19 +24,19 @@ import CloudQueueIcon from '@material-ui/icons/CloudQueue'; import AcUnitIcon from '@material-ui/icons/AcUnit'; import AppsIcon from '@material-ui/icons/Apps'; import React, { ComponentType } from 'react'; +import { SidebarPage } from './Page'; +import { Sidebar } from './Bar'; +import { SidebarGroup } from './SidebarGroup'; import { - Sidebar, SidebarDivider, - SidebarGroup, SidebarExpandButton, - SidebarIntro, SidebarItem, - SidebarPage, SidebarSearchField, SidebarSpace, - SidebarSubmenu, - SidebarSubmenuItem, -} from '.'; +} from './Items'; +import { SidebarIntro } from './Intro'; +import { SidebarSubmenu } from './SidebarSubmenu'; +import { SidebarSubmenuItem } from './SidebarSubmenuItem'; const routeRef = createRouteRef({ id: 'storybook.test-route', diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx index b8f1eb5ebc..ce60b8cb8a 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.test.tsx @@ -20,8 +20,10 @@ import LayersIcon from '@material-ui/icons/Layers'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import { fireEvent } from '@testing-library/react'; import React from 'react'; +import { SidebarItem } from './Items'; import { MobileSidebarContext } from './MobileSidebar'; -import { SidebarGroup, SidebarItem, SidebarPage } from '.'; +import { SidebarPage } from './Page'; +import { SidebarGroup } from './SidebarGroup'; const SidebarGroupWithItems = () => ( diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx index 1158cc14df..5254199563 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx @@ -22,10 +22,10 @@ import BottomNavigationAction, { import { makeStyles } from '@material-ui/core/styles'; import React, { useContext } from 'react'; import { useLocation } from 'react-router-dom'; -import { useSidebarPinState } from '.'; import { Link } from '../../components'; import { SidebarConfigContext, SidebarConfig } from './config'; import { MobileSidebarContext } from './MobileSidebar'; +import { useSidebarPinState } from './SidebarPinStateContext'; /** * Props for the `SidebarGroup` @@ -115,6 +115,7 @@ const MobileSidebarGroup = (props: SidebarGroupProps) => { /** * Groups items of the `Sidebar` together. * + * @remarks * On bigger screens, this won't have any effect at the moment. * On small screens, it will add an action to the bottom navigation - either triggering an overlay menu or acting as a link * diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx index a1565a348f..51092d7e81 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx @@ -111,6 +111,7 @@ export type SidebarSubmenuItemDropdownItem = { /** * Holds submenu item content. * + * @remarks * title: Text content of submenu item * subtitle: A subtitle displayed under the main title * to: Path to navigate to when item is clicked diff --git a/packages/core-components/src/layout/Sidebar/config.ts b/packages/core-components/src/layout/Sidebar/config.ts index bb0ff207fb..dacf4bd0a5 100644 --- a/packages/core-components/src/layout/Sidebar/config.ts +++ b/packages/core-components/src/layout/Sidebar/config.ts @@ -20,13 +20,13 @@ const drawerWidthClosed = 72; const iconPadding = 24; const userBadgePadding = 18; -/** @public **/ +/** @public */ export type SidebarOptions = { drawerWidthClosed?: number; drawerWidthOpen?: number; }; -/** @public **/ +/** @public */ export type SubmenuOptions = { drawerWidthClosed?: number; drawerWidthOpen?: number; diff --git a/packages/core-components/src/layout/SignInPage/types.ts b/packages/core-components/src/layout/SignInPage/types.ts index 0909e7242d..8a0430a27d 100644 --- a/packages/core-components/src/layout/SignInPage/types.ts +++ b/packages/core-components/src/layout/SignInPage/types.ts @@ -32,7 +32,7 @@ export type SignInProviderConfig = { apiRef: ApiRef; }; -/** @public **/ +/** @public */ export type IdentityProviders = ('guest' | 'custom' | SignInProviderConfig)[]; export type ProviderComponent = ComponentType< diff --git a/packages/core-components/src/layout/index.ts b/packages/core-components/src/layout/index.ts index 4b594f0e94..a024cfbbc4 100644 --- a/packages/core-components/src/layout/index.ts +++ b/packages/core-components/src/layout/index.ts @@ -23,7 +23,6 @@ export * from './Header'; export * from './HeaderActionMenu'; export * from './HeaderLabel'; export * from './HeaderTabs'; -export * from './HomepageTimer'; export * from './InfoCard'; export * from './ItemCard'; export * from './Page'; diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 26e18ea244..4f93cebf5d 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/core-plugin-api +## 1.1.0-next.0 + +### Minor Changes + +- a228f113d0: The app `Router` component now accepts an optional `basename` property. + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + ## 1.0.7-next.1 ### Patch Changes diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index 0738d7bac5..cebf09e908 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -139,7 +139,9 @@ export type AppComponents = { NotFoundErrorPage: ComponentType<{}>; BootErrorPage: ComponentType; Progress: ComponentType<{}>; - Router: ComponentType<{}>; + Router: ComponentType<{ + basename?: string; + }>; ErrorBoundaryFallback: ComponentType; ThemeProvider?: ComponentType<{}>; SignInPage?: ComponentType; diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 48b78a1daf..7de98c55f9 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "1.0.7-next.1", + "version": "1.1.0-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.ts b/packages/core-plugin-api/src/routing/SubRouteRef.ts index 61d89fcbdb..14a614c2ba 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.ts @@ -72,7 +72,7 @@ export type ParamNames = export type PathParams = { [name in ParamNames]: string }; /** - * Merges a param object type with with an optional params type into a params object. + * Merges a param object type with an optional params type into a params object. * @public */ export type MergeParams< diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 0e1d1a98af..d8d97e178c 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/create-app +## 0.4.33-next.0 + +### Patch Changes + +- 4091c73e68: Updated `@swc/core` to `v1.3.9` which fixes a `.tsx` parser bug. You may want to run `yarn backstage-cli versions:bump` to get on latest version including the CLI itself. +- 80bfac5266: Updated the create-app command to no longer require Git to be installed and configured. A git repository will only be initialized if possible and if not already in an git repository. +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## 0.4.32 + +### Patch Changes + +- 58c2264325: Newly created Backstage repositories now use the stable version 6 of + `react-router`, just like the main repo does. Please let us know if you find any + issues with this. + + Migrating to the stable version of `react-router` is optional for the time + being. But if you want to do the same for your existing repository, please + follow [this + guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + +- e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. +- 90616bcaa6: Add the new search pagination component to the search page template. +- 7c6306fc8a: Initializes a git repository when creating an app using @packages/create-app +- 52f25858a8: Added `*.session.sql` Visual Studio Code database functionality files to `.gitignore` in the default template. This is optional but potentially helpful if your developers use Visual Studio Code; you can add a line with that exact value to your own root `.gitignore` if you want the same. +- 6d00e80146: Updated the root `test` scripts to use `backstage-cli repo test`. + + To apply this change to an existing app, make the following change to the root `package.json`: + + ```diff + - "test": "backstage-cli test", + - "test:all": "lerna run test -- --coverage", + + "test": "backstage-cli repo test", + + "test:all": "backstage-cli repo test --coverage", + ``` + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## 0.4.32-next.2 + +### Patch Changes + +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. +- Updated dependencies + - @backstage/cli-common@0.1.10 + ## 0.4.32-next.1 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 9633c890fa..a2a168b263 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.4.32-next.1", + "version": "0.4.33-next.0", "publishConfig": { "access": "public" }, @@ -34,7 +34,6 @@ "dependencies": { "@backstage/cli-common": "workspace:^", "chalk": "^4.0.0", - "command-exists": "^1.2.9", "commander": "^9.1.0", "fs-extra": "10.1.0", "handlebars": "^4.7.3", diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index ec8d15ead1..f4c94d337a 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -32,7 +32,7 @@ const promptMock = jest.spyOn(inquirer, 'prompt'); const checkPathExistsMock = jest.spyOn(tasks, 'checkPathExistsTask'); const templatingMock = jest.spyOn(tasks, 'templatingTask'); const checkAppExistsMock = jest.spyOn(tasks, 'checkAppExistsTask'); -const initGitRepositoryMock = jest.spyOn(tasks, 'initGitRepository'); +const tryInitGitRepositoryMock = jest.spyOn(tasks, 'tryInitGitRepository'); const readGitConfig = jest.spyOn(tasks, 'readGitConfig'); const createTemporaryAppFolderMock = jest.spyOn( tasks, @@ -59,8 +59,6 @@ describe('command entrypoint', () => { dbType: 'PostgreSQL', }); readGitConfig.mockResolvedValue({ - name: 'git-user', - email: 'git-email', defaultBranch: 'git-default-branch', }); }); @@ -74,7 +72,7 @@ describe('command entrypoint', () => { await createApp(cmd); expect(checkAppExistsMock).toHaveBeenCalled(); expect(createTemporaryAppFolderMock).toHaveBeenCalled(); - expect(initGitRepositoryMock).toHaveBeenCalled(); + expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(moveAppMock).toHaveBeenCalled(); expect(buildAppMock).toHaveBeenCalled(); @@ -84,7 +82,7 @@ describe('command entrypoint', () => { const cmd = { path: 'myDirectory' } as unknown as Command; await createApp(cmd); expect(checkPathExistsMock).toHaveBeenCalled(); - expect(initGitRepositoryMock).toHaveBeenCalled(); + expect(tryInitGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(buildAppMock).toHaveBeenCalled(); }); @@ -97,8 +95,8 @@ describe('command entrypoint', () => { it('should not call `initGitRepository` when `gitConfig` is undefined', async () => { const cmd = {} as unknown as Command; - readGitConfig.mockResolvedValue({}); + readGitConfig.mockResolvedValue(undefined); await createApp(cmd); - expect(initGitRepositoryMock).not.toHaveBeenCalled(); + expect(tryInitGitRepositoryMock).not.toHaveBeenCalled(); }); }); diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index 182e043565..6267081c8b 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -28,7 +28,7 @@ import { createTemporaryAppFolderTask, moveAppTask, templatingTask, - initGitRepository, + tryInitGitRepository, readGitConfig, } from './lib/tasks'; @@ -109,13 +109,16 @@ export default async (opts: OptionValues): Promise => { await moveAppTask(tempDir, appDir, answers.name); } - if (gitConfig?.name && gitConfig?.email) { - Task.section('Initializing git repository'); - await initGitRepository(appDir); + if (gitConfig) { + if (await tryInitGitRepository(appDir)) { + // Since we don't know whether we were able to init git before we + // try, we can't track the actual task execution + Task.forItem('init', 'git repository', async () => {}); + } } if (!opts.skipInstall) { - Task.section('Building the app'); + Task.section('Installing dependencies'); await buildAppTask(appDir); } diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index b918fc0180..8dc351ac97 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -27,12 +27,10 @@ import { createTemporaryAppFolderTask, moveAppTask, templatingTask, - initGitRepository, + tryInitGitRepository, readGitConfig, } from './tasks'; -const commandExists = jest.fn(); - jest.spyOn(Task, 'log').mockReturnValue(undefined); jest.spyOn(Task, 'error').mockReturnValue(undefined); jest.spyOn(Task, 'section').mockReturnValue(undefined); @@ -41,12 +39,6 @@ jest .mockImplementation((_a, _b, taskFunc) => taskFunc()); jest.mock('child_process'); -jest.mock( - 'command-exists', - () => - (...args: any[]) => - commandExists(...args), -); // By mocking this the filesystem mocks won't mess with reading all of the package.jsons jest.mock('./versions', () => ({ @@ -102,7 +94,10 @@ describe('tasks', () => { ( command: string, options: any, - callback: (error: null, stdout: any, stderr: any) => void, + callback: ( + error: Error | null, + result: { stdout: string; stderr: string }, + ) => void, ) => void >; @@ -293,29 +288,15 @@ describe('tasks', () => { it('should return git config if git package is installed and git credentials are set', async () => { mockExec.mockImplementation((_command, _options, callback) => { - callback(null, { stdout: 'main' }, 'standard error'); + callback(null, { stdout: 'main', stderr: '' }); }); - commandExists.mockResolvedValue(true); - const gitConfig = await readGitConfig(); - expect(gitConfig).toBeTruthy(); expect(gitConfig).toEqual({ - name: 'main', - email: 'main', defaultBranch: 'main', }); - expect(mockExec).toHaveBeenCalledWith( - 'git config user.name', - { cwd: tmpDir }, - expect.any(Function), - ); - expect(mockExec).toHaveBeenCalledWith( - 'git config user.email', - { cwd: tmpDir }, - expect.any(Function), - ); + expect(mockExec).toHaveBeenCalledTimes(3); expect(mockExec).toHaveBeenCalledWith( 'git init', { cwd: tmpDir }, @@ -326,58 +307,82 @@ describe('tasks', () => { { cwd: tmpDir }, expect.any(Function), ); + expect(mockExec).toHaveBeenCalledWith( + 'git branch --format="%(refname:short)"', + { cwd: tmpDir }, + expect.any(Function), + ); }); - it('should return false if git package is not installed', async () => { - commandExists.mockResolvedValue(false); - - const gitConfig = await readGitConfig(); - - expect(gitConfig).toEqual({}); - }); - - it('should return false if git package is installed but git credentials are not set', async () => { + it('should return false if git config is invalid', async () => { mockExec.mockImplementation((_command, _options, callback) => { - callback(null, { stdout: null }, 'standard error'); + callback(null, { stdout: '', stderr: '' }); }); - commandExists.mockResolvedValue(true); - const gitConfig = await readGitConfig(); - expect(gitConfig).toEqual({}); - expect(mockExec).toHaveBeenCalledWith( - 'git config user.name', - { cwd: tmpDir }, - expect.any(Function), - ); - expect(mockExec).toHaveBeenCalledWith( - 'git config user.email', - { cwd: tmpDir }, - expect.any(Function), - ); + expect(gitConfig).toEqual({ + defaultBranch: undefined, + }); + expect(mockExec).toHaveBeenCalledTimes(3); }); }); - describe('initGitRepository', () => { + describe('tryInitGitRepository', () => { it('should initialize a git repository at the given path', async () => { - const destinationDir = 'tmp/mockApp/'; + const destinationDir = 'tmp/mockApp'; - mockExec.mockImplementation((_command, callback) => { - callback(null, { stdout: 'main' }, 'standard error'); + mockExec.mockImplementation((command, _opts, callback) => { + if (command.startsWith('git rev-parse')) { + callback(new Error('not a git repo'), { stdout: '', stderr: '' }); + } else { + callback(null, { stdout: '', stderr: '' }); + } }); - await initGitRepository(destinationDir); + await expect(tryInitGitRepository(destinationDir)).resolves.toBe(true); - expect(mockExec).toHaveBeenCalledTimes(2); + expect(mockExec).toHaveBeenCalledTimes(4); expect(mockExec).toHaveBeenNthCalledWith( 1, - 'git init', + 'git rev-parse --is-inside-work-tree', + { cwd: destinationDir }, expect.any(Function), ); expect(mockExec).toHaveBeenNthCalledWith( 2, - 'git commit --allow-empty -m "Initial commit"', + 'git init', + { cwd: destinationDir }, + expect.any(Function), + ); + expect(mockExec).toHaveBeenNthCalledWith( + 3, + 'git add .', + { cwd: destinationDir }, + expect.any(Function), + ); + expect(mockExec).toHaveBeenNthCalledWith( + 4, + 'git commit -m "Initial commit"', + { cwd: destinationDir }, + expect.any(Function), + ); + }); + + it('should not initialize a git repository if in one already', async () => { + const destinationDir = 'tmp/mockApp'; + + mockExec.mockImplementation((_command, _opts, callback) => { + callback(null, { stdout: '', stderr: '' }); + }); + + await expect(tryInitGitRepository(destinationDir)).resolves.toBe(false); + + expect(mockExec).toHaveBeenCalledTimes(1); + expect(mockExec).toHaveBeenNthCalledWith( + 1, + 'git rev-parse --is-inside-work-tree', + { cwd: destinationDir }, expect.any(Function), ); }); diff --git a/packages/create-app/src/lib/tasks.ts b/packages/create-app/src/lib/tasks.ts index 1e30f9d33a..ff519912a8 100644 --- a/packages/create-app/src/lib/tasks.ts +++ b/packages/create-app/src/lib/tasks.ts @@ -28,15 +28,12 @@ import { import { exec as execCb } from 'child_process'; import { packageVersions } from './versions'; import { promisify } from 'util'; -import commandExists from 'command-exists'; import os from 'os'; const TASK_NAME_MAX_LENGTH = 14; const exec = promisify(execCb); export type GitConfig = { - name?: string; - email?: string; defaultBranch?: string; }; @@ -250,71 +247,59 @@ export async function moveAppTask( * * @throws if `exec` fails */ -export async function readGitConfig(): Promise { +export async function readGitConfig(): Promise { const tempDir = resolvePath(os.tmpdir(), 'git-temp-dir'); - const runCmd = (cmd: string) => - exec(cmd, { cwd: tempDir }).catch(error => { - process.stdout.write(error.stderr); - process.stdout.write(error.stdout); - throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); - }); - - const isGitAvailable = await commandExists('git').catch(() => false); - - if (!isGitAvailable) return {}; - try { await fs.mkdir(tempDir); - const [gitUsername, gitEmail] = await Promise.all([ - runCmd('git config user.name'), - runCmd('git config user.email'), - ]); + await exec('git init', { cwd: tempDir }); + await exec('git commit --allow-empty -m "Initial commit"', { + cwd: tempDir, + }); - const gitCredentials = Boolean( - gitUsername.stdout?.trim() && gitEmail.stdout?.trim(), - ); - - if (!gitCredentials) return {}; - - await runCmd('git init'); - await runCmd('git commit --allow-empty -m "Initial commit"'); - - const gitDefaultBranch = await runCmd( + const getDefaultBranch = await exec( 'git branch --format="%(refname:short)"', + { cwd: tempDir }, ); return { - name: gitUsername.stdout?.trim(), - email: gitEmail.stdout?.trim(), - defaultBranch: gitDefaultBranch.stdout?.trim(), + defaultBranch: getDefaultBranch.stdout?.trim() || undefined, }; } catch (error) { - throw new Error(`Failed to read git config, ${error}`); + return undefined; } finally { await fs.rm(tempDir, { recursive: true }); } } /** - * Initializes a git repository in the destination folder + * Initializes a git repository in the destination folder if possible * * @param dir - source path to initialize git repository in - * @throws if `exec` fails + * @returns true if git repository was initialized */ -export async function initGitRepository(dir: string) { - const runCmd = (cmd: string) => - exec(cmd).catch(error => { - process.stdout.write(error.stderr); - process.stdout.write(error.stdout); - throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); - }); +export async function tryInitGitRepository(dir: string) { + try { + // Check if we're already in a git repo + await exec('git rev-parse --is-inside-work-tree', { cwd: dir }); + return false; + } catch { + /* ignored */ + } - await Task.forItem('init', 'git repository', async () => { - process.chdir(dir); + try { + await exec('git init', { cwd: dir }); + await exec('git add .', { cwd: dir }); + await exec('git commit -m "Initial commit"', { cwd: dir }); + return true; + } catch (error) { + try { + await fs.rm(resolvePath(dir, '.git'), { recursive: true, force: true }); + } catch { + throw new Error('Failed to remove .git folder'); + } - await runCmd('git init'); - await runCmd('git commit --allow-empty -m "Initial commit"'); - }); + return false; + } } diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 4d5ace5aa4..46cb786399 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -58,7 +58,7 @@ const AppRouter = app.getRouter(); const routes = ( - } /> + } /> } /> { return (
- + {isOpen ? : }
diff --git a/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx index 928b8201ea..9f11d0c80c 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx @@ -14,6 +14,7 @@ import { SearchBar, SearchFilter, SearchResult, + SearchPagination, useSearch, } from '@backstage/plugin-search-react'; import { @@ -109,6 +110,7 @@ const SearchPage = () => { + {({ results }) => ( diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index 682798b826..f0be3ac1d3 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -13,13 +13,18 @@ FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. USER node + +# This should create the app dir as `node`. +# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. +# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. WORKDIR /app # This switches many Node.js dependencies to production mode. @@ -31,7 +36,8 @@ ENV NODE_ENV production COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz -RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --production --network-timeout 300000 # Then copy the rest of the backend bundle, along with any other files we might want. COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 01c97a4c74..7177a25fb2 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/dev-utils +## 1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + +## 1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/test-utils@1.2.1 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + ## 1.0.7-next.1 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 60c877f736..0decdbe985 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "1.0.7-next.1", + "version": "1.0.8-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index 26f45611fc..f64792a5a3 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/errors +## 1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## 1.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 1.1.2-next.1 ### Patch Changes diff --git a/packages/errors/package.json b/packages/errors/package.json index ad10bb7ed5..9b9e0dcd1d 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/errors", "description": "Common utilities for error handling within Backstage", - "version": "1.1.2-next.1", + "version": "1.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 00439a9771..b396593d12 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/integration-react +## 1.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## 1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + ## 1.1.5-next.1 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index cab328f88d..7efea2e74a 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration-react", "description": "Frontend package for managing integrations towards external systems", - "version": "1.1.5-next.1", + "version": "1.1.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 134dc34c11..4c34e91f8d 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/integration +## 1.4.0-next.0 + +### Minor Changes + +- d05e1841ce: This patch brings Gitea as a valid integration: target, via the ScmIntegration interface. It adds gitea to the relevant static properties (get integration by name, get integration by type) for plugins to be able to reference the same Gitea server. +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. + + Deprecates: + + - `getGitHubFileFetchUrl` replaced by `getGithubFileFetchUrl` + - `GitHubIntegrationConfig` replaced by `GithubIntegrationConfig` + - `GitHubIntegration` replaced by `GithubIntegration` + - `readGitHubIntegrationConfig` replaced by `readGithubIntegrationConfig` + - `readGitHubIntegrationConfigs` replaced by `readGithubIntegrationConfigs` + - `replaceGitHubUrlType` replaced by `replaceGithubUrlType` + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 1.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 1.3.2-next.1 ### Patch Changes diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index cf52bec65b..d45c8ca06b 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -324,15 +324,29 @@ export function getGerritRequestOptions(config: GerritIntegrationConfig): { }; // @public -export function getGitHubFileFetchUrl( +export function getGiteaFileContentsUrl( + config: GiteaIntegrationConfig, url: string, - config: GitHubIntegrationConfig, +): string; + +// @public +export function getGiteaRequestOptions(config: GiteaIntegrationConfig): { + headers?: Record; +}; + +// @public @deprecated (undocumented) +export const getGitHubFileFetchUrl: typeof getGithubFileFetchUrl; + +// @public +export function getGithubFileFetchUrl( + url: string, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): string; // @public @deprecated export function getGitHubRequestOptions( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): { headers: Record; @@ -354,6 +368,35 @@ export function getGitLabRequestOptions(config: GitLabIntegrationConfig): { headers: Record; }; +// @public +export class GiteaIntegration implements ScmIntegration { + constructor(config: GiteaIntegrationConfig); + // (undocumented) + readonly config: GiteaIntegrationConfig; + // (undocumented) + static factory: ScmIntegrationsFactory; + // (undocumented) + resolveEditUrl(url: string): string; + // (undocumented) + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string; + // (undocumented) + get title(): string; + // (undocumented) + get type(): string; +} + +// @public +export type GiteaIntegrationConfig = { + host: string; + baseUrl?: string; + username?: string; + password?: string; +}; + // @public export type GithubAppConfig = { appId: number; @@ -366,7 +409,7 @@ export type GithubAppConfig = { // @public export class GithubAppCredentialsMux { - constructor(config: GitHubIntegrationConfig); + constructor(config: GithubIntegrationConfig); // (undocumented) getAllInstallations(): Promise< RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] @@ -393,13 +436,22 @@ export interface GithubCredentialsProvider { // @public export type GithubCredentialType = 'app' | 'token'; -// @public -export class GitHubIntegration implements ScmIntegration { +// @public @deprecated (undocumented) +export class GitHubIntegration extends GithubIntegration { constructor(integrationConfig: GitHubIntegrationConfig); // (undocumented) get config(): GitHubIntegrationConfig; // (undocumented) static factory: ScmIntegrationsFactory; +} + +// @public +export class GithubIntegration implements ScmIntegration { + constructor(integrationConfig: GithubIntegrationConfig); + // (undocumented) + get config(): GithubIntegrationConfig; + // (undocumented) + static factory: ScmIntegrationsFactory; // (undocumented) resolveEditUrl(url: string): string; // (undocumented) @@ -414,8 +466,11 @@ export class GitHubIntegration implements ScmIntegration { get type(): string; } +// @public @deprecated (undocumented) +export type GitHubIntegrationConfig = GithubIntegrationConfig; + // @public -export type GitHubIntegrationConfig = { +export type GithubIntegrationConfig = { host: string; apiBaseUrl?: string; rawBaseUrl?: string; @@ -473,7 +528,9 @@ export interface IntegrationsByType { // (undocumented) gerrit: ScmIntegrationsGroup; // (undocumented) - github: ScmIntegrationsGroup; + gitea: ScmIntegrationsGroup; + // (undocumented) + github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; } @@ -552,14 +609,23 @@ export function readGerritIntegrationConfigs( ): GerritIntegrationConfig[]; // @public -export function readGitHubIntegrationConfig( - config: Config, -): GitHubIntegrationConfig; +export function readGiteaConfig(config: Config): GiteaIntegrationConfig; + +// @public @deprecated (undocumented) +export const readGitHubIntegrationConfig: typeof readGithubIntegrationConfig; // @public -export function readGitHubIntegrationConfigs( +export function readGithubIntegrationConfig( + config: Config, +): GithubIntegrationConfig; + +// @public @deprecated (undocumented) +export const readGitHubIntegrationConfigs: typeof readGithubIntegrationConfigs; + +// @public +export function readGithubIntegrationConfigs( configs: Config[], -): GitHubIntegrationConfig[]; +): GithubIntegrationConfig[]; // @public export function readGitLabIntegrationConfig( @@ -576,8 +642,11 @@ export function readGoogleGcsIntegrationConfig( config: Config, ): GoogleGcsIntegrationConfig; +// @public @deprecated (undocumented) +export const replaceGitHubUrlType: typeof replaceGithubUrlType; + // @public -export function replaceGitHubUrlType( +export function replaceGithubUrlType( url: string, type: 'blob' | 'tree' | 'edit', ): string; @@ -616,7 +685,9 @@ export interface ScmIntegrationRegistry // (undocumented) gerrit: ScmIntegrationsGroup; // (undocumented) - github: ScmIntegrationsGroup; + gitea: ScmIntegrationsGroup; + // (undocumented) + github: ScmIntegrationsGroup; // (undocumented) gitlab: ScmIntegrationsGroup; resolveEditUrl(url: string): string; @@ -649,7 +720,9 @@ export class ScmIntegrations implements ScmIntegrationRegistry { // (undocumented) get gerrit(): ScmIntegrationsGroup; // (undocumented) - get github(): ScmIntegrationsGroup; + get gitea(): ScmIntegrationsGroup; + // (undocumented) + get github(): ScmIntegrationsGroup; // (undocumented) get gitlab(): ScmIntegrationsGroup; // (undocumented) @@ -681,7 +754,7 @@ export class SingleInstanceGithubCredentialsProvider implements GithubCredentialsProvider { // (undocumented) - static create: (config: GitHubIntegrationConfig) => GithubCredentialsProvider; + static create: (config: GithubIntegrationConfig) => GithubCredentialsProvider; getCredentials(opts: { url: string }): Promise; } ``` diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 7c897d637a..49a121aff3 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -164,7 +164,6 @@ export interface Config { /** * GitHub Apps configuration - * @visibility backend */ apps?: Array<{ /** @@ -233,7 +232,6 @@ export interface Config { googleGcs?: { /** * Service account email used to authenticate requests. - * @visibility backend */ clientEmail?: string; /** @@ -265,7 +263,6 @@ export interface Config { /** * Account access key used to authenticate requests. - * @visibility backend */ accessKeyId?: string; /** @@ -276,15 +273,39 @@ export interface Config { /** * ARN of the role to be assumed - * @visibility backend */ roleArn?: string; /** * External ID to use when assuming role - * @visibility backend */ externalId?: string; }>; + + /** Integration configuration for Gitea */ + gitea?: Array<{ + /** + * The hostname of the given Gitea instance + * @visibility frontend + */ + host: string; + /** + * The base url for the Gitea instance. + * @visibility frontend + */ + baseUrl?: string; + + /** + * The username to use for authenticated requests. + * @visibility secret + */ + username?: string; + /** + * Gitea password used to authenticate requests. This can be either a password + * or a generated access token. + * @visibility secret + */ + password?: string; + }>; }; } diff --git a/packages/integration/package.json b/packages/integration/package.json index 169bf56353..bbed7de2d1 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration", "description": "Helpers for managing integrations towards external systems", - "version": "1.3.2-next.1", + "version": "1.4.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration/src/ScmIntegrations.test.ts b/packages/integration/src/ScmIntegrations.test.ts index d9fcaf36f0..4c64b2f0de 100644 --- a/packages/integration/src/ScmIntegrations.test.ts +++ b/packages/integration/src/ScmIntegrations.test.ts @@ -29,12 +29,13 @@ import { } from './bitbucketServer'; import { GerritIntegrationConfig } from './gerrit'; import { GerritIntegration } from './gerrit/GerritIntegration'; -import { GitHubIntegrationConfig } from './github'; -import { GitHubIntegration } from './github/GitHubIntegration'; +import { GithubIntegrationConfig } from './github'; +import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegrationConfig } from './gitlab'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { basicIntegrations } from './helpers'; import { ScmIntegrations } from './ScmIntegrations'; +import { GiteaIntegration, GiteaIntegrationConfig } from './gitea'; describe('ScmIntegrations', () => { const awsS3 = new AwsS3Integration({ @@ -61,14 +62,18 @@ describe('ScmIntegrations', () => { host: 'gerrit.local', } as GerritIntegrationConfig); - const github = new GitHubIntegration({ + const github = new GithubIntegration({ host: 'github.local', - } as GitHubIntegrationConfig); + } as GithubIntegrationConfig); const gitlab = new GitLabIntegration({ host: 'gitlab.local', } as GitLabIntegrationConfig); + const gitea = new GiteaIntegration({ + host: 'gitea.local', + } as GiteaIntegrationConfig); + const i = new ScmIntegrations({ awsS3: basicIntegrations([awsS3], item => item.config.host), azure: basicIntegrations([azure], item => item.config.host), @@ -81,6 +86,7 @@ describe('ScmIntegrations', () => { gerrit: basicIntegrations([gerrit], item => item.config.host), github: basicIntegrations([github], item => item.config.host), gitlab: basicIntegrations([gitlab], item => item.config.host), + gitea: basicIntegrations([gitea], item => item.config.host), }); it('can get the specifics', () => { @@ -96,6 +102,7 @@ describe('ScmIntegrations', () => { expect(i.gerrit.byUrl('https://gerrit.local')).toBe(gerrit); expect(i.github.byUrl('https://github.local')).toBe(github); expect(i.gitlab.byUrl('https://gitlab.local')).toBe(gitlab); + expect(i.gitea.byUrl('https://gitea.local')).toBe(gitea); }); it('can list', () => { @@ -109,6 +116,7 @@ describe('ScmIntegrations', () => { gerrit, github, gitlab, + gitea, ]), ); }); @@ -122,6 +130,7 @@ describe('ScmIntegrations', () => { expect(i.byUrl('https://gerrit.local')).toBe(gerrit); expect(i.byUrl('https://github.local')).toBe(github); expect(i.byUrl('https://gitlab.local')).toBe(gitlab); + expect(i.byUrl('https://gitea.local')).toBe(gitea); expect(i.byHost('awss3.local')).toBe(awsS3); expect(i.byHost('azure.local')).toBe(azure); @@ -131,6 +140,7 @@ describe('ScmIntegrations', () => { expect(i.byHost('gerrit.local')).toBe(gerrit); expect(i.byHost('github.local')).toBe(github); expect(i.byHost('gitlab.local')).toBe(gitlab); + expect(i.byHost('gitea.local')).toBe(gitea); }); it('can resolveUrl using fallback', () => { diff --git a/packages/integration/src/ScmIntegrations.ts b/packages/integration/src/ScmIntegrations.ts index 6d745dcf1c..24c28ce25e 100644 --- a/packages/integration/src/ScmIntegrations.ts +++ b/packages/integration/src/ScmIntegrations.ts @@ -21,11 +21,12 @@ import { BitbucketCloudIntegration } from './bitbucketCloud/BitbucketCloudIntegr import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerIntegration'; import { GerritIntegration } from './gerrit/GerritIntegration'; -import { GitHubIntegration } from './github/GitHubIntegration'; +import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; import { defaultScmResolveUrl } from './helpers'; import { ScmIntegration, ScmIntegrationsGroup } from './types'; import { ScmIntegrationRegistry } from './registry'; +import { GiteaIntegration } from './gitea'; /** * The set of supported integrations. @@ -42,8 +43,9 @@ export interface IntegrationsByType { bitbucketCloud: ScmIntegrationsGroup; bitbucketServer: ScmIntegrationsGroup; gerrit: ScmIntegrationsGroup; - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; + gitea: ScmIntegrationsGroup; } /** @@ -62,8 +64,9 @@ export class ScmIntegrations implements ScmIntegrationRegistry { bitbucketCloud: BitbucketCloudIntegration.factory({ config }), bitbucketServer: BitbucketServerIntegration.factory({ config }), gerrit: GerritIntegration.factory({ config }), - github: GitHubIntegration.factory({ config }), + github: GithubIntegration.factory({ config }), gitlab: GitLabIntegration.factory({ config }), + gitea: GiteaIntegration.factory({ config }), }); } @@ -98,7 +101,7 @@ export class ScmIntegrations implements ScmIntegrationRegistry { return this.byType.gerrit; } - get github(): ScmIntegrationsGroup { + get github(): ScmIntegrationsGroup { return this.byType.github; } @@ -106,6 +109,10 @@ export class ScmIntegrations implements ScmIntegrationRegistry { return this.byType.gitlab; } + get gitea(): ScmIntegrationsGroup { + return this.byType.gitea; + } + list(): ScmIntegration[] { return Object.values(this.byType).flatMap( i => i.list() as ScmIntegration[], diff --git a/packages/integration/src/gerrit/GerritIntegration.ts b/packages/integration/src/gerrit/GerritIntegration.ts index d57b54df8e..6ea426e407 100644 --- a/packages/integration/src/gerrit/GerritIntegration.ts +++ b/packages/integration/src/gerrit/GerritIntegration.ts @@ -20,7 +20,7 @@ import { GerritIntegrationConfig, readGerritIntegrationConfigs, } from './config'; -import { parseGerritGitilesUrl, builldGerritGitilesUrl } from './core'; +import { parseGerritGitilesUrl, buildGerritGitilesUrl } from './core'; /** * A Gerrit based integration. @@ -61,7 +61,7 @@ export class GerritIntegration implements ScmIntegration { let updated; if (url.startsWith('/')) { const { branch, project } = parseGerritGitilesUrl(this.config, base); - return builldGerritGitilesUrl(this.config, project, branch, url); + return buildGerritGitilesUrl(this.config, project, branch, url); } if (url) { updated = new URL(url, base); diff --git a/packages/integration/src/gerrit/core.test.ts b/packages/integration/src/gerrit/core.test.ts index 351e7ce58e..e226492edb 100644 --- a/packages/integration/src/gerrit/core.test.ts +++ b/packages/integration/src/gerrit/core.test.ts @@ -20,7 +20,7 @@ import fetch from 'cross-fetch'; import { setupRequestMockHandlers } from '@backstage/test-utils'; import { GerritIntegrationConfig } from './config'; import { - builldGerritGitilesUrl, + buildGerritGitilesUrl, getGerritBranchApiUrl, getGerritCloneRepoUrl, getGerritRequestOptions, @@ -33,14 +33,14 @@ describe('gerrit core', () => { const worker = setupServer(); setupRequestMockHandlers(worker); - describe('builldGerritGitilesUrl', () => { + describe('buildGerritGitilesUrl', () => { it('can create an url from arguments', () => { const config: GerritIntegrationConfig = { host: 'gerrit.com', gitilesBaseUrl: 'https://gerrit.com/gitiles', }; expect( - builldGerritGitilesUrl(config, 'repo', 'dev', 'catalog-info.yaml'), + buildGerritGitilesUrl(config, 'repo', 'dev', 'catalog-info.yaml'), ).toEqual( 'https://gerrit.com/gitiles/repo/+/refs/heads/dev/catalog-info.yaml', ); diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts index cd2b919e09..0403d8fc75 100644 --- a/packages/integration/src/gerrit/core.ts +++ b/packages/integration/src/gerrit/core.ts @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { trimStart } from 'lodash'; -import { GerritIntegrationConfig } from '.'; +import { GerritIntegrationConfig } from './config'; const GERRIT_BODY_PREFIX = ")]}'"; @@ -79,7 +80,7 @@ export function parseGerritGitilesUrl( * @param filePath - The absolute file path. * @public */ -export function builldGerritGitilesUrl( +export function buildGerritGitilesUrl( config: GerritIntegrationConfig, project: string, branch: string, diff --git a/packages/integration/src/gitea/GiteaIntegration.test.ts b/packages/integration/src/gitea/GiteaIntegration.test.ts new file mode 100644 index 0000000000..1c8169f417 --- /dev/null +++ b/packages/integration/src/gitea/GiteaIntegration.test.ts @@ -0,0 +1,128 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { GiteaIntegration } from './GiteaIntegration'; + +describe('GiteaIntegration', () => { + it('has a working factory', () => { + const integrations = GiteaIntegration.factory({ + config: new ConfigReader({ + integrations: { + gitea: [ + { + host: 'gitea.example.com', + username: 'git', + baseUrl: 'https://gitea.example.com/route', + password: '1234', + }, + ], + }, + }), + }); + expect(integrations.list().length).toBe(1); + expect(integrations.list()[0].config.host).toBe('gitea.example.com'); + expect(integrations.list()[0].config.baseUrl).toBe( + 'https://gitea.example.com/route', + ); + }); + + it('returns the basics', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + expect(integration.type).toBe('gitea'); + expect(integration.title).toBe('gitea.example.com'); + }); + + describe('resolveUrl', () => { + it('works for valid urls, ignoring line number', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveUrl({ + url: 'https://gitea.example.com/catalog-info.yaml', + base: 'https://gitea.example.com/catalog-info.yaml', + lineNumber: 9, + }), + ).toBe('https://gitea.example.com/catalog-info.yaml'); + }); + + it('handles line numbers', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveUrl({ + url: '', + base: 'https://gitea.example.com/catalog-info.yaml#4', + lineNumber: 9, + }), + ).toBe('https://gitea.example.com/catalog-info.yaml#L9'); + }); + }); + + describe('resolves with a relative url', () => { + it('works for valid urls', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveUrl({ + url: './skeleton', + base: 'https://gitea.example.com/git/plugins/repo/+/refs/heads/master/template.yaml', + }), + ).toBe( + 'https://gitea.example.com/git/plugins/repo/+/refs/heads/master/skeleton', + ); + }); + }); + + describe('resolves with an absolute url', () => { + it('works for valid urls', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveUrl({ + url: '/catalog-info.yaml', + base: 'https://gitea.example.com/git/repo/+/refs/heads/master/', + }), + ).toBe( + 'https://gitea.example.com/git/repo/+/refs/heads/master/catalog-info.yaml', + ); + }); + }); + + it('resolve edit URL', () => { + const integration = new GiteaIntegration({ + host: 'gitea.example.com', + }); + + expect( + integration.resolveEditUrl( + 'https://gitea.example.com/owner/repo/src/branch/branch_name/path/to/c.yaml', + ), + ).toBe( + 'https://gitea.example.com/owner/repo/_edit/branch_name/path/to/c.yaml', + ); + }); +}); diff --git a/packages/integration/src/gitea/GiteaIntegration.ts b/packages/integration/src/gitea/GiteaIntegration.ts new file mode 100644 index 0000000000..25144dcca9 --- /dev/null +++ b/packages/integration/src/gitea/GiteaIntegration.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { basicIntegrations, defaultScmResolveUrl } from '../helpers'; +import { ScmIntegration, ScmIntegrationsFactory } from '../types'; +import { GiteaIntegrationConfig, readGiteaConfig } from './config'; +import { getGiteaEditContentsUrl } from './core'; + +/** + * A Gitea based integration. + * + * @public + */ +export class GiteaIntegration implements ScmIntegration { + static factory: ScmIntegrationsFactory = ({ config }) => { + const configs = config.getOptionalConfigArray('integrations.gitea') ?? []; + const giteaConfigs = configs.map(c => readGiteaConfig(c)); + + return basicIntegrations( + giteaConfigs.map(c => new GiteaIntegration(c)), + (gitea: GiteaIntegration) => gitea.config.host, + ); + }; + + constructor(readonly config: GiteaIntegrationConfig) {} + + get type(): string { + return 'gitea'; + } + + get title(): string { + return this.config.host; + } + + resolveUrl(options: { + url: string; + base: string; + lineNumber?: number | undefined; + }): string { + return defaultScmResolveUrl(options); + } + + resolveEditUrl(url: string): string { + return getGiteaEditContentsUrl(this.config, url); + } +} diff --git a/packages/integration/src/gitea/config.test.ts b/packages/integration/src/gitea/config.test.ts new file mode 100644 index 0000000000..cdcc7e3f3e --- /dev/null +++ b/packages/integration/src/gitea/config.test.ts @@ -0,0 +1,108 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config, ConfigReader } from '@backstage/config'; +import { loadConfigSchema } from '@backstage/config-loader'; +import { GiteaIntegrationConfig, readGiteaConfig } from './config'; + +describe('readGiteaConfig', () => { + function buildConfig(data: Partial): Config { + return new ConfigReader(data); + } + + async function buildFrontendConfig( + data: Partial, + ): Promise { + const fullSchema = await loadConfigSchema({ + dependencies: ['@backstage/integration'], + }); + const serializedSchema = fullSchema.serialize() as { + schemas: { value: { properties?: { integrations?: object } } }[]; + }; + const schema = await loadConfigSchema({ + serialized: { + ...serializedSchema, // only include schemas that apply to integrations + schemas: serializedSchema.schemas.filter( + s => s.value?.properties?.integrations, + ), + }, + }); + const processed = schema.process( + [{ data: { integrations: { gitea: [data] } }, context: 'app' }], + { visibility: ['frontend'] }, + ); + return new ConfigReader((processed[0].data as any).integrations.gitea[0]); + } + + it('reads all values', () => { + const output = readGiteaConfig( + buildConfig({ + host: 'a.com', + baseUrl: 'https://a.com/route/api', + username: 'u', + password: 'p', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com/route/api', + username: 'u', + password: 'p', + }); + }); + + it('can create a default value if the API base URL is missing', () => { + const output = readGiteaConfig( + buildConfig({ + host: 'a.com', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com', + username: undefined, + password: undefined, + }); + }); + + it('rejects funky configs', () => { + const valid: any = { + host: 'a.com', + }; + expect(() => readGiteaConfig(buildConfig({ ...valid, host: 2 }))).toThrow( + /host/, + ); + expect(() => + readGiteaConfig(buildConfig({ ...valid, baseUrl: 2 })), + ).toThrow(/baseUrl/); + }); + + it('works on the frontend', async () => { + expect( + readGiteaConfig( + await buildFrontendConfig({ + host: 'a.com', + baseUrl: 'https://a.com/route', + username: 'u', + password: 'p', + }), + ), + ).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com/route', + }); + }); +}); diff --git a/packages/integration/src/gitea/config.ts b/packages/integration/src/gitea/config.ts new file mode 100644 index 0000000000..99c5b5c1f7 --- /dev/null +++ b/packages/integration/src/gitea/config.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { trimEnd } from 'lodash'; +import { isValidHost } from '../helpers'; + +/** + * The configuration for a single Gitea integration. + * + * @public + */ +export type GiteaIntegrationConfig = { + /** + * The host of the target that this matches on, e.g. "gitea.website.com" + */ + host: string; + /** + * The optional base URL of the Gitea instance. It is assumed that https + * is used and that the base path is "/" on the host. If that is not the + * case set the complete base url to the gitea instance, e.g. + * "https://gitea.website.com/". This is the url that you would open + * in a browser. + */ + baseUrl?: string; + /** + * The username to use for requests to gitea. + */ + username?: string; + + /** + * The password or http token to use for authentication. + */ + password?: string; +}; + +/** + * Parses a location config block for use in GiteaIntegration + * + * @public + */ +export function readGiteaConfig(config: Config): GiteaIntegrationConfig { + const host = config.getString('host'); + let baseUrl = config.getOptionalString('baseUrl'); + const username = config.getOptionalString('username'); + const password = config.getOptionalString('password'); + + if (!isValidHost(host)) { + throw new Error( + `Invalid Gitea integration config, '${host}' is not a valid host`, + ); + } + + if (baseUrl) { + baseUrl = trimEnd(baseUrl, '/'); + } else { + baseUrl = `https://${host}`; + } + + return { + host, + baseUrl, + username, + password, + }; +} diff --git a/packages/integration/src/gitea/core.test.ts b/packages/integration/src/gitea/core.test.ts new file mode 100644 index 0000000000..520b0f549d --- /dev/null +++ b/packages/integration/src/gitea/core.test.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { GiteaIntegrationConfig } from './config'; +import { + getGiteaEditContentsUrl, + getGiteaFileContentsUrl, + getGiteaRequestOptions, +} from './core'; + +describe('gitea core', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + describe('getGiteaFileContentsUrl', () => { + it('can create an url from arguments', () => { + const config: GiteaIntegrationConfig = { + host: 'gitea.com', + }; + expect( + getGiteaFileContentsUrl( + config, + 'https://gitea.com/a/b/src/branch/branch_name/path/to/c.yaml', + ), + ).toEqual( + 'https://gitea.com/api/v1/repos/a/b/contents/path/to/c.yaml?ref=branch_name', + ); + }); + }); + + describe('getGiteaEditContentsUrl', () => { + it('can create an url from arguments', () => { + const config: GiteaIntegrationConfig = { + host: 'gitea.example.com', + }; + expect( + getGiteaEditContentsUrl( + config, + 'https://gitea.example.com/owner/repo/src/branch/branch_name/path/to/c.yaml', + ), + ).toEqual( + 'https://gitea.example.com/owner/repo/_edit/branch_name/path/to/c.yaml', + ); + }); + }); + + describe('getGerritRequestOptions', () => { + it('adds token header when only a password is specified', () => { + const authRequest: GiteaIntegrationConfig = { + host: 'gerrit.com', + password: 'P', + }; + const anonymousRequest: GiteaIntegrationConfig = { + host: 'gerrit.com', + }; + expect( + (getGiteaRequestOptions(authRequest).headers as any).Authorization, + ).toEqual('token P'); + expect(getGiteaRequestOptions(anonymousRequest).headers).toBeUndefined(); + }); + + it('adds basic auth when username and password are specified', () => { + const authRequest: GiteaIntegrationConfig = { + host: 'gerrit.com', + username: 'username', + password: 'P', + }; + + const basicAuthentication = `basic ${Buffer.from( + `${authRequest.username}:${authRequest.password}`, + ).toString('base64')}`; + + expect( + (getGiteaRequestOptions(authRequest).headers as any).Authorization, + ).toEqual(basicAuthentication); + }); + }); +}); diff --git a/packages/integration/src/gitea/core.ts b/packages/integration/src/gitea/core.ts new file mode 100644 index 0000000000..ee3fec12fc --- /dev/null +++ b/packages/integration/src/gitea/core.ts @@ -0,0 +1,106 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { GiteaIntegrationConfig } from './config'; + +/** + * Given a URL pointing to a file, returns a URL + * for editing the contents of the data. + * + * @remarks + * + * Converts + * from: https://gitea.com/a/b/src/branchname/path/to/c.yaml + * or: https://gitea.com/a/b/_edit/branchname/path/to/c.yaml + * + * @param url - A URL pointing to a file + * @param config - The relevant provider config + * @public + */ +export function getGiteaEditContentsUrl( + config: GiteaIntegrationConfig, + url: string, +) { + try { + const baseUrl = config.baseUrl ?? `https://${config.host}`; + const [_blank, owner, name, _src, _branch, ref, ...path] = url + .replace(baseUrl, '') + .split('/'); + const pathWithoutSlash = path.join('/').replace(/^\//, ''); + return `${baseUrl}/${owner}/${name}/_edit/${ref}/${pathWithoutSlash}`; + } catch (e) { + throw new Error(`Incorrect URL: ${url}, ${e}`); + } +} + +/** + * Given a URL pointing to a file, returns an api URL + * for fetching the contents of the data. + * + * @remarks + * + * Converts + * from: https://gitea.com/a/b/src/branch/branchname/path/to/c.yaml + * to: https://gitea.com/api/v1/repos/a/b/contents/path/to/c.yaml?ref=branchname + * + * @param url - A URL pointing to a file + * @param config - The relevant provider config + * @public + */ +export function getGiteaFileContentsUrl( + config: GiteaIntegrationConfig, + url: string, +) { + try { + const baseUrl = config.baseUrl ?? `https://${config.host}`; + const [_blank, owner, name, _src, _branch, ref, ...path] = url + .replace(baseUrl, '') + .split('/'); + const pathWithoutSlash = path.join('/').replace(/^\//, ''); + + return `${baseUrl}/api/v1/repos/${owner}/${name}/contents/${pathWithoutSlash}?ref=${ref}`; + } catch (e) { + throw new Error(`Incorrect URL: ${url}, ${e}`); + } +} + +/** + * Return request headers for a Gitea provider. + * + * @param config - A Gitea provider config + * @public + */ +export function getGiteaRequestOptions(config: GiteaIntegrationConfig): { + headers?: Record; +} { + const headers: Record = {}; + const { username, password } = config; + + if (!password) { + return headers; + } + + if (username) { + headers.Authorization = `basic ${Buffer.from( + `${username}:${password}`, + ).toString('base64')}`; + } else { + headers.Authorization = `token ${password}`; + } + + return { + headers, + }; +} diff --git a/packages/integration/src/gitea/index.ts b/packages/integration/src/gitea/index.ts new file mode 100644 index 0000000000..6b951b1fb0 --- /dev/null +++ b/packages/integration/src/gitea/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { GiteaIntegration } from './GiteaIntegration'; +export { getGiteaRequestOptions, getGiteaFileContentsUrl } from './core'; +export { readGiteaConfig } from './config'; +export type { GiteaIntegrationConfig } from './config'; diff --git a/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts b/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts index 7c2b53e822..98b8edecef 100644 --- a/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/DefaultGithubCredentialsProvider.test.ts @@ -15,7 +15,7 @@ */ import { ScmIntegrations } from '../ScmIntegrations'; -import { GitHubIntegrationConfig } from './config'; +import { GithubIntegrationConfig } from './config'; import { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider'; import { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider'; @@ -65,7 +65,7 @@ describe('DefaultGithubCredentialsProvider tests', () => { ); jest.resetAllMocks(); SingleInstanceGithubCredentialsProvider.create = ( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, ) => { return { getCredentials: (_opts: { url: string }) => { diff --git a/packages/integration/src/github/GitHubIntegration.test.ts b/packages/integration/src/github/GithubIntegration.test.ts similarity index 86% rename from packages/integration/src/github/GitHubIntegration.test.ts rename to packages/integration/src/github/GithubIntegration.test.ts index ccceb34d88..d8e6020a64 100644 --- a/packages/integration/src/github/GitHubIntegration.test.ts +++ b/packages/integration/src/github/GithubIntegration.test.ts @@ -15,11 +15,11 @@ */ import { ConfigReader } from '@backstage/config'; -import { GitHubIntegration, replaceGitHubUrlType } from './GitHubIntegration'; +import { GithubIntegration, replaceGithubUrlType } from './GithubIntegration'; -describe('GitHubIntegration', () => { +describe('GithubIntegration', () => { it('has a working factory', () => { - const integrations = GitHubIntegration.factory({ + const integrations = GithubIntegration.factory({ config: new ConfigReader({ integrations: { github: [ @@ -39,7 +39,7 @@ describe('GitHubIntegration', () => { }); it('returns the basics', () => { - const integration = new GitHubIntegration({ + const integration = new GithubIntegration({ host: 'h.com', apiBaseUrl: 'a', rawBaseUrl: 'r', @@ -51,7 +51,7 @@ describe('GitHubIntegration', () => { }); it('resolveUrl', () => { - const integration = new GitHubIntegration({ host: 'h.com' }); + const integration = new GithubIntegration({ host: 'h.com' }); expect( integration.resolveUrl({ @@ -70,7 +70,7 @@ describe('GitHubIntegration', () => { }); it('resolve edit URL', () => { - const integration = new GitHubIntegration({ host: 'h.com' }); + const integration = new GithubIntegration({ host: 'h.com' }); expect( integration.resolveEditUrl( @@ -80,28 +80,28 @@ describe('GitHubIntegration', () => { }); }); -describe('replaceGitHubUrlType', () => { +describe('replaceGithubUrlType', () => { it('should replace with expected type', () => { expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/backstage/backstage/blob/master/README.md', 'edit', ), ).toBe('https://github.com/backstage/backstage/edit/master/README.md'); expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/webmodules/blob/blob/master/test', 'tree', ), ).toBe('https://github.com/webmodules/blob/tree/master/test'); expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/blob/blob/blob/master/test', 'tree', ), ).toBe('https://github.com/blob/blob/tree/master/test'); expect( - replaceGitHubUrlType( + replaceGithubUrlType( 'https://github.com/backstage/backstage/edit/tree/README.md', 'blob', ), diff --git a/packages/integration/src/github/GitHubIntegration.ts b/packages/integration/src/github/GithubIntegration.ts similarity index 78% rename from packages/integration/src/github/GitHubIntegration.ts rename to packages/integration/src/github/GithubIntegration.ts index 1db0b1fae5..50fc129ccf 100644 --- a/packages/integration/src/github/GitHubIntegration.ts +++ b/packages/integration/src/github/GithubIntegration.ts @@ -17,8 +17,8 @@ import { basicIntegrations, defaultScmResolveUrl } from '../helpers'; import { ScmIntegration, ScmIntegrationsFactory } from '../types'; import { - GitHubIntegrationConfig, - readGitHubIntegrationConfigs, + GithubIntegrationConfig, + readGithubIntegrationConfigs, } from './config'; /** @@ -26,18 +26,18 @@ import { * * @public */ -export class GitHubIntegration implements ScmIntegration { - static factory: ScmIntegrationsFactory = ({ config }) => { - const configs = readGitHubIntegrationConfigs( +export class GithubIntegration implements ScmIntegration { + static factory: ScmIntegrationsFactory = ({ config }) => { + const configs = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], ); return basicIntegrations( - configs.map(c => new GitHubIntegration(c)), + configs.map(c => new GithubIntegration(c)), i => i.config.host, ); }; - constructor(private readonly integrationConfig: GitHubIntegrationConfig) {} + constructor(private readonly integrationConfig: GithubIntegrationConfig) {} get type(): string { return 'github'; @@ -47,7 +47,7 @@ export class GitHubIntegration implements ScmIntegration { return this.integrationConfig.host; } - get config(): GitHubIntegrationConfig { + get config(): GithubIntegrationConfig { return this.integrationConfig; } @@ -59,11 +59,11 @@ export class GitHubIntegration implements ScmIntegration { // GitHub uses blob URLs for files and tree urls for directory listings. But // there is a redirect from tree to blob for files, so we can always return // tree urls here. - return replaceGitHubUrlType(defaultScmResolveUrl(options), 'tree'); + return replaceGithubUrlType(defaultScmResolveUrl(options), 'tree'); } resolveEditUrl(url: string): string { - return replaceGitHubUrlType(url, 'edit'); + return replaceGithubUrlType(url, 'edit'); } } @@ -74,7 +74,7 @@ export class GitHubIntegration implements ScmIntegration { * @param type - The desired type, e.g. "blob" * @public */ -export function replaceGitHubUrlType( +export function replaceGithubUrlType( url: string, type: 'blob' | 'tree' | 'edit', ): string { diff --git a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts index 02f050f69f..d42ad4597d 100644 --- a/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts +++ b/packages/integration/src/github/SingleInstanceGithubCredentialsProvider.ts @@ -15,7 +15,7 @@ */ import parseGitUrl from 'git-url-parse'; -import { GithubAppConfig, GitHubIntegrationConfig } from './config'; +import { GithubAppConfig, GithubIntegrationConfig } from './config'; import { createAppAuth } from '@octokit/auth-app'; import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'; import { DateTime } from 'luxon'; @@ -199,7 +199,7 @@ class GithubAppManager { export class GithubAppCredentialsMux { private readonly apps: GithubAppManager[]; - constructor(config: GitHubIntegrationConfig) { + constructor(config: GithubIntegrationConfig) { this.apps = config.apps?.map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? []; } @@ -259,7 +259,7 @@ export class SingleInstanceGithubCredentialsProvider implements GithubCredentialsProvider { static create: ( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, ) => GithubCredentialsProvider = config => { return new SingleInstanceGithubCredentialsProvider( new GithubAppCredentialsMux(config), diff --git a/packages/integration/src/github/config.test.ts b/packages/integration/src/github/config.test.ts index 73efb4f81a..c4d9eb1900 100644 --- a/packages/integration/src/github/config.test.ts +++ b/packages/integration/src/github/config.test.ts @@ -17,18 +17,18 @@ import { Config, ConfigReader } from '@backstage/config'; import { loadConfigSchema } from '@backstage/config-loader'; import { - GitHubIntegrationConfig, - readGitHubIntegrationConfig, - readGitHubIntegrationConfigs, + GithubIntegrationConfig, + readGithubIntegrationConfig, + readGithubIntegrationConfigs, } from './config'; -describe('readGitHubIntegrationConfig', () => { - function buildConfig(provider: Partial) { +describe('readGithubIntegrationConfig', () => { + function buildConfig(provider: Partial) { return new ConfigReader(provider); } async function buildFrontendConfig( - data: Partial, + data: Partial, ): Promise { const fullSchema = await loadConfigSchema({ dependencies: ['@backstage/integration'], @@ -52,7 +52,7 @@ describe('readGitHubIntegrationConfig', () => { } it('reads all values', () => { - const output = readGitHubIntegrationConfig( + const output = readGithubIntegrationConfig( buildConfig({ host: 'a.com', apiBaseUrl: 'https://a.com/api', @@ -69,7 +69,7 @@ describe('readGitHubIntegrationConfig', () => { }); it('injects the correct GitHub API base URL when missing', () => { - const output = readGitHubIntegrationConfig( + const output = readGithubIntegrationConfig( buildConfig({ host: 'github.com' }), ); expect(output).toEqual({ @@ -87,22 +87,22 @@ describe('readGitHubIntegrationConfig', () => { token: 't', }; expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, host: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, host: 7 })), ).toThrow(/host/); expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, apiBaseUrl: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, apiBaseUrl: 7 })), ).toThrow(/apiBaseUrl/); expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, rawBaseUrl: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, rawBaseUrl: 7 })), ).toThrow(/rawBaseUrl/); expect(() => - readGitHubIntegrationConfig(buildConfig({ ...valid, token: 7 })), + readGithubIntegrationConfig(buildConfig({ ...valid, token: 7 })), ).toThrow(/token/); }); it('works on the frontend', async () => { expect( - readGitHubIntegrationConfig( + readGithubIntegrationConfig( await buildFrontendConfig({ host: 'a.com', apiBaseUrl: 'https://a.com/api', @@ -118,15 +118,15 @@ describe('readGitHubIntegrationConfig', () => { }); }); -describe('readGitHubIntegrationConfigs', () => { +describe('readGithubIntegrationConfigs', () => { function buildConfig( - providers: Partial[], + providers: Partial[], ): Config[] { return providers.map(provider => new ConfigReader(provider)); } it('reads all values', () => { - const output = readGitHubIntegrationConfigs( + const output = readGithubIntegrationConfigs( buildConfig([ { host: 'a.com', @@ -145,7 +145,7 @@ describe('readGitHubIntegrationConfigs', () => { }); it('adds a default GitHub entry when missing', () => { - const output = readGitHubIntegrationConfigs(buildConfig([])); + const output = readGithubIntegrationConfigs(buildConfig([])); expect(output).toEqual([ { host: 'github.com', diff --git a/packages/integration/src/github/config.ts b/packages/integration/src/github/config.ts index 93801e96fb..cd76eba3d3 100644 --- a/packages/integration/src/github/config.ts +++ b/packages/integration/src/github/config.ts @@ -27,7 +27,7 @@ const GITHUB_RAW_BASE_URL = 'https://raw.githubusercontent.com'; * * @public */ -export type GitHubIntegrationConfig = { +export type GithubIntegrationConfig = { /** * The host of the target that this matches on, e.g. "github.com" */ @@ -117,9 +117,9 @@ export type GithubAppConfig = { * @param config - The config object of a single integration * @public */ -export function readGitHubIntegrationConfig( +export function readGithubIntegrationConfig( config: Config, -): GitHubIntegrationConfig { +): GithubIntegrationConfig { const host = config.getOptionalString('host') ?? GITHUB_HOST; let apiBaseUrl = config.getOptionalString('apiBaseUrl'); let rawBaseUrl = config.getOptionalString('rawBaseUrl'); @@ -163,11 +163,11 @@ export function readGitHubIntegrationConfig( * @param configs - All of the integration config objects * @public */ -export function readGitHubIntegrationConfigs( +export function readGithubIntegrationConfigs( configs: Config[], -): GitHubIntegrationConfig[] { +): GithubIntegrationConfig[] { // First read all the explicit integrations - const result = configs.map(readGitHubIntegrationConfig); + const result = configs.map(readGithubIntegrationConfig); // If no explicit github.com integration was added, put one in the list as // a convenience diff --git a/packages/integration/src/github/core.test.ts b/packages/integration/src/github/core.test.ts index c5d04f5b4f..982a096b43 100644 --- a/packages/integration/src/github/core.test.ts +++ b/packages/integration/src/github/core.test.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { GitHubIntegrationConfig } from './config'; -import { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; +import { GithubIntegrationConfig } from './config'; +import { getGithubFileFetchUrl, getGitHubRequestOptions } from './core'; import { GithubCredentials } from './types'; describe('github core', () => { @@ -37,12 +37,12 @@ describe('github core', () => { describe('getGitHubRequestOptions', () => { it('inserts a token when needed', () => { - const withToken: GitHubIntegrationConfig = { + const withToken: GithubIntegrationConfig = { host: '', rawBaseUrl: '', token: 'A', }; - const withoutToken: GitHubIntegrationConfig = { + const withoutToken: GithubIntegrationConfig = { host: '', rawBaseUrl: '', }; @@ -57,21 +57,21 @@ describe('github core', () => { }); }); - describe('getGitHubFileFetchUrl', () => { + describe('getGithubFileFetchUrl', () => { it('rejects targets that do not look like URLs', () => { - const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' }; - expect(() => getGitHubFileFetchUrl('a/b', config, noCredentials)).toThrow( + const config: GithubIntegrationConfig = { host: '', apiBaseUrl: '' }; + expect(() => getGithubFileFetchUrl('a/b', config, noCredentials)).toThrow( /Incorrect URL: a\/b/, ); }); it('happy path for github api', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'github.com', apiBaseUrl: 'https://api.github.com', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, appCredentials, @@ -80,7 +80,7 @@ describe('github core', () => { 'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname', ); expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, @@ -91,12 +91,12 @@ describe('github core', () => { }); it('happy path for ghe api', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'ghe.mycompany.net', apiBaseUrl: 'https://ghe.mycompany.net/api/v3', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, appCredentials, @@ -105,7 +105,7 @@ describe('github core', () => { 'https://ghe.mycompany.net/api/v3/repos/a/b/contents/path/to/c.yaml?ref=branchname', ); expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, @@ -116,12 +116,12 @@ describe('github core', () => { }); it('happy path for github tree', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'github.com', apiBaseUrl: 'https://api.github.com', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/tree/branchname/path/to/c.yaml', config, tokenCredentials, @@ -132,12 +132,12 @@ describe('github core', () => { }); it('happy path for ghe tree', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'ghe.mycompany.net', apiBaseUrl: 'https://ghe.mycompany.net/api/v3', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/tree/branchname/path/to/c.yaml', config, tokenCredentials, @@ -148,12 +148,12 @@ describe('github core', () => { }); it('happy path for github raw', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'github.com', rawBaseUrl: 'https://raw.githubusercontent.com', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, @@ -164,12 +164,12 @@ describe('github core', () => { }); it('happy path for ghe raw', () => { - const config: GitHubIntegrationConfig = { + const config: GithubIntegrationConfig = { host: 'ghe.mycompany.net', rawBaseUrl: 'https://ghe.mycompany.net/raw', }; expect( - getGitHubFileFetchUrl( + getGithubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, tokenCredentials, diff --git a/packages/integration/src/github/core.ts b/packages/integration/src/github/core.ts index 5b630d5182..a755f3383c 100644 --- a/packages/integration/src/github/core.ts +++ b/packages/integration/src/github/core.ts @@ -15,7 +15,7 @@ */ import parseGitUrl from 'git-url-parse'; -import { GitHubIntegrationConfig } from './config'; +import { GithubIntegrationConfig } from './config'; import { GithubCredentials } from './types'; /** @@ -33,9 +33,9 @@ import { GithubCredentials } from './types'; * @param config - The relevant provider config * @public */ -export function getGitHubFileFetchUrl( +export function getGithubFileFetchUrl( url: string, - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): string { try { @@ -71,7 +71,7 @@ export function getGitHubFileFetchUrl( * @public */ export function getGitHubRequestOptions( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): { headers: Record } { const headers: Record = {}; @@ -88,7 +88,7 @@ export function getGitHubRequestOptions( } export function chooseEndpoint( - config: GitHubIntegrationConfig, + config: GithubIntegrationConfig, credentials: GithubCredentials, ): 'api' | 'raw' { if (config.apiBaseUrl && (credentials.token || !config.rawBaseUrl)) { diff --git a/packages/integration/src/github/deprecated.ts b/packages/integration/src/github/deprecated.ts new file mode 100644 index 0000000000..127040c1a7 --- /dev/null +++ b/packages/integration/src/github/deprecated.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + GithubIntegrationConfig, + readGithubIntegrationConfig, + readGithubIntegrationConfigs, +} from './config'; +import { getGithubFileFetchUrl } from './core'; +import { GithubIntegration, replaceGithubUrlType } from './GithubIntegration'; +import { ScmIntegrationsFactory } from '../types'; + +/** + * @public + * @deprecated Use {@link getGithubFileFetchUrl} instead. + */ +export const getGitHubFileFetchUrl = getGithubFileFetchUrl; + +/** + * @public + * @deprecated Use {@link GithubIntegrationConfig} instead. + */ +export type GitHubIntegrationConfig = GithubIntegrationConfig; + +/** + * @public + * @deprecated Use {@link GithubIntegration} instead. + */ +export class GitHubIntegration extends GithubIntegration { + static factory: ScmIntegrationsFactory = + GithubIntegration.factory; + + constructor(integrationConfig: GitHubIntegrationConfig) { + super(integrationConfig as GithubIntegrationConfig); + } + + get config(): GitHubIntegrationConfig { + return super.config as GitHubIntegrationConfig; + } +} + +/** + * @public + * @deprecated Use {@link readGithubIntegrationConfig} instead. + */ +export const readGitHubIntegrationConfig = readGithubIntegrationConfig; + +/** + * @public + * @deprecated Use {@link readGithubIntegrationConfigs} instead. + */ +export const readGitHubIntegrationConfigs = readGithubIntegrationConfigs; + +/** + * @public + * @deprecated Use {@link replaceGithubUrlType} instead. + */ +export const replaceGitHubUrlType = replaceGithubUrlType; diff --git a/packages/integration/src/github/index.ts b/packages/integration/src/github/index.ts index c6a7799c3d..816b0d2ff9 100644 --- a/packages/integration/src/github/index.ts +++ b/packages/integration/src/github/index.ts @@ -15,11 +15,11 @@ */ export { - readGitHubIntegrationConfig, - readGitHubIntegrationConfigs, + readGithubIntegrationConfig, + readGithubIntegrationConfigs, } from './config'; -export type { GithubAppConfig, GitHubIntegrationConfig } from './config'; -export { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; +export type { GithubAppConfig, GithubIntegrationConfig } from './config'; +export { getGithubFileFetchUrl, getGitHubRequestOptions } from './core'; export { DefaultGithubCredentialsProvider } from './DefaultGithubCredentialsProvider'; export { GithubAppCredentialsMux, @@ -30,4 +30,6 @@ export type { GithubCredentialsProvider, GithubCredentialType, } from './types'; -export { GitHubIntegration, replaceGitHubUrlType } from './GitHubIntegration'; +export { GithubIntegration, replaceGithubUrlType } from './GithubIntegration'; + +export * from './deprecated'; diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index 388b874275..5738da3da0 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -26,6 +26,7 @@ export * from './bitbucket'; export * from './bitbucketCloud'; export * from './bitbucketServer'; export * from './gerrit'; +export * from './gitea'; export * from './github'; export * from './gitlab'; export * from './googleGcs'; diff --git a/packages/integration/src/registry.ts b/packages/integration/src/registry.ts index f4f759cfe9..7058ca1b5c 100644 --- a/packages/integration/src/registry.ts +++ b/packages/integration/src/registry.ts @@ -21,8 +21,9 @@ import { BitbucketCloudIntegration } from './bitbucketCloud/BitbucketCloudIntegr import { BitbucketIntegration } from './bitbucket/BitbucketIntegration'; import { BitbucketServerIntegration } from './bitbucketServer/BitbucketServerIntegration'; import { GerritIntegration } from './gerrit/GerritIntegration'; -import { GitHubIntegration } from './github/GitHubIntegration'; +import { GithubIntegration } from './github/GithubIntegration'; import { GitLabIntegration } from './gitlab/GitLabIntegration'; +import { GiteaIntegration } from './gitea/GiteaIntegration'; /** * Holds all registered SCM integrations, of all types. @@ -40,9 +41,9 @@ export interface ScmIntegrationRegistry bitbucketCloud: ScmIntegrationsGroup; bitbucketServer: ScmIntegrationsGroup; gerrit: ScmIntegrationsGroup; - github: ScmIntegrationsGroup; + github: ScmIntegrationsGroup; gitlab: ScmIntegrationsGroup; - + gitea: ScmIntegrationsGroup; /** * Resolves an absolute or relative URL in relation to a base URL. * diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 49b59771c3..fc7dbe743b 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,62 @@ # techdocs-cli-embedded-app +## 0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/app-defaults@1.0.8-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## 0.2.75 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/cli@0.20.0 + - @backstage/test-utils@1.2.1 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.2.75-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + ## 0.2.75-next.1 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 25eb9f2b27..c34c52e3f2 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.75-next.1", + "version": "0.2.76-next.0", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx index cd8b5d35ed..81e3d12d9d 100644 --- a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx @@ -16,7 +16,7 @@ import React, { PropsWithChildren } from 'react'; -import { Link, makeStyles } from '@material-ui/core'; +import { makeStyles } from '@material-ui/core'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; @@ -28,8 +28,8 @@ import { sidebarConfig, SidebarDivider, useSidebarOpenState, + Link, } from '@backstage/core-components'; -import { NavLink } from 'react-router-dom'; const useSidebarLogoStyles = makeStyles({ root: { @@ -53,7 +53,6 @@ const SidebarLogo = () => { return (
*[MOCDOC]: Mock Documentation diff --git a/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md b/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md index bd8bc3a4ea..47bc1547c6 100644 --- a/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md +++ b/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md @@ -1,4 +1,4 @@ # Plugin A -This is a description of Plugin A. This file exists to prove that glob'd +This is a description of Plugin A. This file exists to prove that glob formed includes using the `*include` syntax work as expected. diff --git a/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md b/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md index 93e545252f..2c9571add6 100644 --- a/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md +++ b/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md @@ -1,4 +1,4 @@ # Plugin B -This is a description of Plugin B. This file exists to prove that glob'd +This is a description of Plugin B. This file exists to prove that glob formed includes using the `*include` syntax work as expected. diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 28a4a92294..c2e2975656 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/test-utils +## 1.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## 1.2.1 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/core-app-api@1.1.1 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-react@0.4.6 + +## 1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 1.2.1-next.1 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index a7bcbab826..70e6dca4cc 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "1.2.1-next.1", + "version": "1.2.2-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/theme/src/pageTheme.ts b/packages/theme/src/pageTheme.ts index 5b5eb24be9..dd36d7641b 100644 --- a/packages/theme/src/pageTheme.ts +++ b/packages/theme/src/pageTheme.ts @@ -23,7 +23,7 @@ import { PageTheme } from './types'; * * How to add a shape: * - * 1. Get the svg shape from figma, should be ~1400 wide, ~400 high + * 1. Get the SVG shape from figma, should be ~1400 wide, ~400 high * and only the white-to-transparent mask, no colors. * 2. Run it through https://jakearchibald.github.io/svgomg/ * 3. Run that through https://github.com/tigt/mini-svg-data-uri @@ -62,7 +62,7 @@ export const colorVariants: Record = { * @remarks * * As the background shapes and colors are decorative, we place them onto the - * page as a css background-image instead of an html element of its own. + * page as a CSS `background-image` instead of an HTML element of its own. */ export function genPageTheme(props: { colors: string[]; diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 4c0b2f6083..9070442f8f 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/types +## 1.0.1-next.0 + +### Patch Changes + +- 30e43717c7: Added the `HumanDuration` type, moved here from `@backstage/backend-tasks`. This type matches the `Duration.fromObject` form of `luxon`. + ## 1.0.0 ### Major Changes diff --git a/packages/types/api-report.md b/packages/types/api-report.md index 43e3cfee0e..4b43f5daa6 100644 --- a/packages/types/api-report.md +++ b/packages/types/api-report.md @@ -3,6 +3,18 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +// @public +export type HumanDuration = { + years?: number; + months?: number; + weeks?: number; + days?: number; + hours?: number; + minutes?: number; + seconds?: number; + milliseconds?: number; +}; + // @public export interface JsonArray extends Array {} diff --git a/packages/types/package.json b/packages/types/package.json index b8ebeffce8..a9c340cec3 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/types", "description": "Common TypeScript types used within Backstage", - "version": "1.0.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,6 +34,7 @@ "devDependencies": { "@backstage/cli": "workspace:^", "@types/zen-observable": "^0.8.0", + "luxon": "^3.0.0", "zen-observable": "^0.8.15" }, "files": [ diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 1f1b13c349..0b5c8689b3 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -22,3 +22,4 @@ export type { JsonArray, JsonObject, JsonPrimitive, JsonValue } from './json'; export type { Observable, Observer, Subscription } from './observable'; +export type { HumanDuration } from './time'; diff --git a/packages/types/src/time.test.ts b/packages/types/src/time.test.ts new file mode 100644 index 0000000000..a1175c9e9b --- /dev/null +++ b/packages/types/src/time.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { HumanDuration } from './time'; +import { Duration } from 'luxon'; + +describe('time', () => { + describe('HumanDuration', () => { + const durations: HumanDuration[] = [ + { years: 1 }, + { months: 1 }, + { weeks: 1 }, + { days: 1 }, + { hours: 1 }, + { minutes: 1 }, + { seconds: 1 }, + { milliseconds: 1 }, + ]; + it.each(durations)('successfully parsed by luxon, %p', d => { + expect(Duration.fromObject(d).toObject()).toEqual(d); + }); + }); +}); diff --git a/packages/types/src/time.ts b/packages/types/src/time.ts new file mode 100644 index 0000000000..7ab8288cea --- /dev/null +++ b/packages/types/src/time.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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. + */ + +/** + * Human friendly durations object. + * + * @public + */ +export type HumanDuration = { + years?: number; + months?: number; + weeks?: number; + days?: number; + hours?: number; + minutes?: number; + seconds?: number; + milliseconds?: number; +}; diff --git a/plugins/adr-backend/CHANGELOG.md b/plugins/adr-backend/CHANGELOG.md index a13f16e340..84143ccf78 100644 --- a/plugins/adr-backend/CHANGELOG.md +++ b/plugins/adr-backend/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-adr-backend +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-adr-common@0.2.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.2.2 + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-adr-common@0.2.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-adr-common@0.2.2-next.2 + ## 0.2.2-next.1 ### Patch Changes diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json index ff232e6b69..ccbd3f119e 100644 --- a/plugins/adr-backend/package.json +++ b/plugins/adr-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr-backend", - "version": "0.2.2-next.1", + "version": "0.2.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts b/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts index 21f0f1945f..78f6e0226e 100644 --- a/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts +++ b/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts @@ -113,7 +113,7 @@ export class DefaultAdrCollatorFactory implements DocumentCollatorFactory { this.catalogClient = options.catalogClient ?? new CatalogClient({ discoveryApi: options.discovery }); - this.logger = options.logger; + this.logger = options.logger.child({ documentType: this.type }); this.parser = options.parser ?? createMadrParser(); this.reader = options.reader; this.scmIntegrations = ScmIntegrations.fromConfig(options.config); diff --git a/plugins/adr-common/CHANGELOG.md b/plugins/adr-common/CHANGELOG.md index d1738d9920..f9093a1dcf 100644 --- a/plugins/adr-common/CHANGELOG.md +++ b/plugins/adr-common/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-adr-common +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/integration@1.3.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.2.2-next.1 ### Patch Changes diff --git a/plugins/adr-common/package.json b/plugins/adr-common/package.json index 8358349631..e9a9edc597 100644 --- a/plugins/adr-common/package.json +++ b/plugins/adr-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-adr-common", "description": "Common functionalities for the adr plugin", - "version": "0.2.2-next.1", + "version": "0.2.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr/CHANGELOG.md b/plugins/adr/CHANGELOG.md index 7ac3655e03..e1f34381e4 100644 --- a/plugins/adr/CHANGELOG.md +++ b/plugins/adr/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-adr +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-adr-common@0.2.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-adr-common@0.2.2 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-adr-common@0.2.2-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + ## 0.2.2-next.1 ### Patch Changes diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 4eb9cf7b03..e6113c7a83 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr", - "version": "0.2.2-next.1", + "version": "0.2.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md index b61f76db54..38ed0acbc7 100644 --- a/plugins/airbrake-backend/CHANGELOG.md +++ b/plugins/airbrake-backend/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-airbrake-backend +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.2.10 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## 0.2.10-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.2.10-next.1 ### Patch Changes diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index 2817861740..f8904563a4 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake-backend", - "version": "0.2.10-next.1", + "version": "0.2.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md index c466c6cb3a..a714e4d69f 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-airbrake +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/dev-utils@1.0.8-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/test-utils@1.2.1 + - @backstage/dev-utils@1.0.7 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/dev-utils@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 28677b7948..3f04501019 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.3.10-next.1", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md index ae170e29c7..0f57d596c5 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-allure +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.26-next.1 ### Patch Changes diff --git a/plugins/allure/package.json b/plugins/allure/package.json index 1691a77ecf..68808cf511 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-allure", "description": "A Backstage plugin that integrates with Allure", - "version": "0.1.26-next.1", + "version": "0.1.27-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md index 13c445f246..149215b223 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-analytics-module-ga +## 0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.21-next.1 ### Patch Changes diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 87d6d4f0fc..ac94ad8979 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-ga", - "version": "0.1.21-next.1", + "version": "0.1.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md index 5cd28f0827..b4dee086eb 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-apache-airflow +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index 5f9c1b4f9d..bba1177b06 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apache-airflow", - "version": "0.2.3-next.1", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 432ff12f76..de93784591 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-api-docs +## 0.8.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/theme@0.2.16 + +## 0.8.10 + +### Patch Changes + +- 50c6e14aee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.43`. +- 3d5bb521ee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.42`. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.8.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.8.10-next.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index bd38fa9c65..ecb6fe3ff4 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-api-docs", "description": "A Backstage plugin that helps represent API entities in the frontend", - "version": "0.8.10-next.1", + "version": "0.8.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/apollo-explorer/CHANGELOG.md b/plugins/apollo-explorer/CHANGELOG.md index a1c6c6c8df..5ef5cb16f9 100644 --- a/plugins/apollo-explorer/CHANGELOG.md +++ b/plugins/apollo-explorer/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-apollo-explorer +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.3-next.1 ### Patch Changes diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json index a5e598ede1..91fe56950e 100644 --- a/plugins/apollo-explorer/package.json +++ b/plugins/apollo-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apollo-explorer", - "version": "0.1.3-next.1", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index b1b112a030..81c2020152 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-app-backend +## 0.3.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/config-loader@1.1.6-next.0 + +## 0.3.37 + +### Patch Changes + +- 11c9e0ad33: Added alpha plugin implementation for the new backend system. Available at `@backstage/plugin-app-backend/alpha`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/types@1.0.0 + +## 0.3.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/types@1.0.0 + ## 0.3.37-next.1 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index db100ea12b..5e72d7b666 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.37-next.1", + "version": "0.3.38-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index afa2d700fd..a65fa417d8 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-auth-backend +## 0.17.1-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.17.0 + +### Minor Changes + +- e2dc42e9f0: Google OAuth refresh tokens will now be revoked on logout by calling Google's API +- 5fa831ce55: CookieConfigurer can optionally return the `SameSite` cookie attribute. + CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute. + defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests. + + OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`. + OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set. + +### Patch Changes + +- b5c126010c: Auth0 provider now supports optional `connection` and `connectionScope` parameters to configure social identity providers. +- 8c6ec175bf: Fix GitLab provider setup so that it supports GitLab installations with a path in the URL. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.17.0-next.1 ### Minor Changes diff --git a/plugins/auth-backend/README.md b/plugins/auth-backend/README.md index fe2cd05c22..d13a5e5a20 100644 --- a/plugins/auth-backend/README.md +++ b/plugins/auth-backend/README.md @@ -91,9 +91,9 @@ export AUTH_GITLAB_CLIENT_SECRET=x Add a new Okta application using the following URI conventions: -Login redirect URI's: `http://localhost:7007/api/auth/okta/handler/frame` -Logout redirect URI's: `http://localhost:7007/api/auth/okta/logout` -Initiate login URI's: `http://localhost:7007/api/auth/okta/start` +Login redirect URIs: `http://localhost:7007/api/auth/okta/handler/frame` +Logout redirect URIs: `http://localhost:7007/api/auth/okta/logout` +Initiate login URIs: `http://localhost:7007/api/auth/okta/start` Then configure the following environment variables to be used in the `app-config.yaml` file: diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index be02a90a71..4b3d7910e5 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend", "description": "A Backstage backend plugin that handles authentication", - "version": "0.17.0-next.1", + "version": "0.17.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 147861383f..aac75b87e9 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-auth-node +## 0.2.7-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.6 + +### Patch Changes + +- f3a3fefb96: Ensure `getIdentity` throws an `AuthenticationError` instead of a `NotAllowed` error when authentication fails +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.6-next.1 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 7c0c3b51e2..79dcc6d6b8 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.2.6-next.1", + "version": "0.2.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-node/src/DefaultIdentityClient.ts b/plugins/auth-node/src/DefaultIdentityClient.ts index a7946c77ff..b8f6b73865 100644 --- a/plugins/auth-node/src/DefaultIdentityClient.ts +++ b/plugins/auth-node/src/DefaultIdentityClient.ts @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { AuthenticationError, NotAllowedError } from '@backstage/errors'; +import { AuthenticationError } from '@backstage/errors'; import { createRemoteJWKSet, decodeJwt, @@ -24,12 +25,12 @@ import { jwtVerify, } from 'jose'; import { GetKeyFunction } from 'jose/dist/types/types'; - import { BackstageIdentityResponse, IdentityApiGetIdentityRequest, } from './types'; -import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '.'; +import { getBearerTokenFromAuthorizationHeader } from './getBearerTokenFromAuthorizationHeader'; +import { IdentityApi } from './IdentityApi'; const CLOCK_MARGIN_S = 10; @@ -86,7 +87,7 @@ export class DefaultIdentityClient implements IdentityApi { getBearerTokenFromAuthorizationHeader(request.headers.authorization), ); } catch (e) { - throw new NotAllowedError(e.message); + throw new AuthenticationError(e.message); } } diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index 00cc426c0e..dfaffa8e35 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-azure-devops-backend +## 0.3.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.3.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-azure-devops-common@0.3.0 + ## 0.3.16-next.1 ### Patch Changes diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 27be6291ed..940b24ed44 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-backend", - "version": "0.3.16-next.1", + "version": "0.3.17-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 4919b1226d..1317547b7f 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-azure-devops +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + ## 0.2.1-next.1 ### Patch Changes diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 14f2c29310..a2ffd77c84 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops", - "version": "0.2.1-next.1", + "version": "0.2.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-sites-backend/.eslintrc.js b/plugins/azure-sites-backend/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/azure-sites-backend/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/azure-sites-backend/CHANGELOG.md b/plugins/azure-sites-backend/CHANGELOG.md new file mode 100644 index 0000000000..b06742648f --- /dev/null +++ b/plugins/azure-sites-backend/CHANGELOG.md @@ -0,0 +1,14 @@ +# @backstage/plugin-azure-sites-backend + +## 0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-azure-sites-common@0.1.0-next.0 + - @backstage/config@1.0.4-next.0 diff --git a/plugins/azure-sites-backend/README.md b/plugins/azure-sites-backend/README.md new file mode 100644 index 0000000000..5f19267499 --- /dev/null +++ b/plugins/azure-sites-backend/README.md @@ -0,0 +1,85 @@ +# Azure Sites Backend + +Simple plugin that proxies requests to the Azure Portal API through Azure SDK JavaScript libraries. + +_Inspired by [roadie.io AWS Lamda plugin](https://roadie.io/backstage/plugins/aws-lambda/)_ + +## Setup + +The following sections will help you get the Azure Sites Backend plugin setup and running. + +### Configuration + +The Azure plugin requires the following YAML to be added to your app-config.yaml: + +```yaml +azureSites: + domain: + tenantId: + clientId: + clientSecret: + allowedSubscriptions: + - id: +``` + +Configuration Details: + +- `domain` can be found by visiting the [Directories + Subscriptions settings page](https://portal.azure.com/#settings/directory). Alternatively you can inspect the [Azure home](https://portal.azure.com/#home) URL - `https://portal.azure.com/#@/`. +- `tenantId` can be found by visiting [Azure Directory Overview page](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade). +- (Optional) `clientId` and `clientSecret` can be the same values you used for [Azure DevOps Backend](https://github.com/backstage/backstage/tree/master/plugins/azure-devops-backend) or [Azure Integration](https://backstage.io/docs/integrations/azure/org#app-registration) as long as this App Registration has permissions to read your function apps. +- (Optional) `allowedSubscriptions` is an array of `id` that will be used to iterate over and look for the specified functions' app. `id` can be found the [Subscriptions page](https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade). + +### Integrating + +Here's how to get the backend plugin up and running: + +1. First we need to add the `@backstage/plugin-azure-sites-backend` package to your backend: + + ```sh + # From the Backstage root directory + yarn add --cwd packages/backend @backstage/plugin-azure-sites-backend + ``` + +2. Then we will create a new file named `packages/backend/src/plugins/azure.ts`, and add the following to it: + + ```ts + import { + createRouter, + AzureSitesApi, + } from '@backstage/plugin-azure-sites-backend'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + azureSitesApi: AzureSitesApi.fromConfig(env.config), + }); + } + ``` + +3. Next we wire this into the overall backend router, edit `packages/backend/src/index.ts`: + + ```ts + import azure from './plugins/azure'; + + // Removed for clairty... + + async function main() { + // ... + // Add this line under the other lines that follow the useHotMemoize pattern + const azureSitesEnv = useHotMemoize(module, () => + createEnv('azure-sites'), + ); + + // ... + // Insert this line under the other lines that add their routers to apiRouter in the same way + apiRouter.use('/azure-sites', await azure(azureSitesEnv)); + } + ``` + +4. Now run `yarn start-backend` from the repo root. + +5. Finally, open `http://localhost:7007/api/azure/health` in a browser, it should return `{"status":"ok"}`. diff --git a/plugins/azure-sites-backend/api-report.md b/plugins/azure-sites-backend/api-report.md new file mode 100644 index 0000000000..751ff3f033 --- /dev/null +++ b/plugins/azure-sites-backend/api-report.md @@ -0,0 +1,61 @@ +## API Report File for "@backstage/plugin-azure-sites-backend" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AzureSiteListRequest } from '@backstage/plugin-azure-sites-common'; +import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common'; +import { AzureSiteStartStopRequest } from '@backstage/plugin-azure-sites-common'; +import { Config } from '@backstage/config'; +import express from 'express'; +import { Logger } from 'winston'; + +// @public (undocumented) +export class AzureSitesApi { + constructor(config: AzureSitesConfig); + // (undocumented) + static fromConfig(config: Config): AzureSitesApi; + // (undocumented) + list(request: AzureSiteListRequest): Promise; + // (undocumented) + start(request: AzureSiteStartStopRequest): Promise; + // (undocumented) + stop(request: AzureSiteStartStopRequest): Promise; +} + +// @public (undocumented) +export class AzureSitesConfig { + constructor( + domain: string, + tenantId: string, + subscriptions?: string[] | undefined, + clientId?: string | undefined, + clientSecret?: string | undefined, + ); + // (undocumented) + readonly clientId?: string | undefined; + // (undocumented) + readonly clientSecret?: string | undefined; + // (undocumented) + readonly domain: string; + // (undocumented) + static fromConfig(config: Config): AzureSitesConfig; + // (undocumented) + readonly subscriptions?: string[] | undefined; + // (undocumented) + readonly tenantId: string; +} + +// @public (undocumented) +export function createRouter(options: RouterOptions): Promise; + +// @public (undocumented) +export interface RouterOptions { + // (undocumented) + azureSitesApi: AzureSitesApi; + // (undocumented) + logger: Logger; +} + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/azure-sites-backend/package.json b/plugins/azure-sites-backend/package.json new file mode 100644 index 0000000000..f6529b7a3d --- /dev/null +++ b/plugins/azure-sites-backend/package.json @@ -0,0 +1,56 @@ +{ + "name": "@backstage/plugin-azure-sites-backend", + "version": "0.1.0-next.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/azure-sites-backend" + }, + "keywords": [ + "backstage", + "azure" + ], + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@azure/arm-appservice": "^13.0.1", + "@azure/arm-resourcegraph": "^4.2.1", + "@azure/identity": "^2.1.0", + "@backstage/backend-common": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/plugin-azure-sites-common": "workspace:^", + "@types/express": "^4.17.6", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "node-fetch": "^2.6.7", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@types/supertest": "^2.0.8", + "msw": "^0.47.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/azure-sites-backend/schema.d.ts b/plugins/azure-sites-backend/schema.d.ts new file mode 100644 index 0000000000..19a12d11a1 --- /dev/null +++ b/plugins/azure-sites-backend/schema.d.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + azureSites: { + /** @visibility backend */ + domain: string; + /** @visibility backend */ + tenantId: string; + /** @visibility backend */ + subscriptions?: [ + { + id: string; + }, + ]; + /** @visibility backend */ + clientId?: string; + /** @visibility secret */ + clientSecret?: string; + }; +} diff --git a/plugins/azure-sites-backend/src/api/AzureSitesApi.ts b/plugins/azure-sites-backend/src/api/AzureSitesApi.ts new file mode 100644 index 0000000000..73e92ea6a0 --- /dev/null +++ b/plugins/azure-sites-backend/src/api/AzureSitesApi.ts @@ -0,0 +1,98 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { + DefaultAzureCredential, + ClientSecretCredential, +} from '@azure/identity'; +import { ResourceGraphClient } from '@azure/arm-resourcegraph'; +import { WebSiteManagementClient } from '@azure/arm-appservice'; +import { + AzureSite, + AzureSiteListRequest, + AzureSiteListResponse, + AzureSiteStartStopRequest, +} from '@backstage/plugin-azure-sites-common'; +import { AzureSitesConfig } from '../config'; + +/** @public */ +export class AzureSitesApi { + private readonly baseHref = (domain: string) => + `https://portal.azure.com/#@${domain}/resource`; + private readonly client: ResourceGraphClient; + + constructor(private readonly config: AzureSitesConfig) { + const creds = this.getCredentials(config); + + this.client = new ResourceGraphClient(creds); + } + + private getCredentials(config: AzureSitesConfig) { + return config.clientId && config.clientSecret + ? new ClientSecretCredential( + config.tenantId, + config.clientId, + config.clientSecret, + ) + : new DefaultAzureCredential({ tenantId: config.tenantId }); + } + + static fromConfig(config: Config): AzureSitesApi { + return new AzureSitesApi(AzureSitesConfig.fromConfig(config)); + } + + async start(request: AzureSiteStartStopRequest): Promise { + const client = new WebSiteManagementClient( + this.getCredentials(this.config), + request.subscription, + ); + await client.webApps.start(request.resourceGroup, request.name); + } + + async stop(request: AzureSiteStartStopRequest): Promise { + const client = new WebSiteManagementClient( + this.getCredentials(this.config), + request.subscription, + ); + await client.webApps.stop(request.resourceGroup, request.name); + } + + async list(request: AzureSiteListRequest): Promise { + const items: AzureSite[] = []; + const result = await this.client.resources({ + query: `resources | where type == 'microsoft.web/sites' | where name contains '${request.name}'`, + subscriptions: this.config.subscriptions, + }); + for (const v of result.data) { + items.push({ + href: `${this.baseHref(this.config.domain)}${v.id!}`, + logstreamHref: `${this.baseHref(this.config.domain)}${v.id!}/logStream`, + name: v.name!, + kind: v.kind!, + resourceGroup: v.resourceGroup!, + subscription: v.id!.match(/\w{8}\-\w{4}\-\w{4}\-\w{4}\-\w{12}/)[0], + location: v.location!, + lastModifiedDate: v.properties.lastModifiedTimeUtc!, + usageState: v.properties.usageState!, + state: v.properties.state!, + containerSize: v.properties.containerSize!, + tags: v.properties.tags!, + }); + } + return { items: items }; + } +} diff --git a/plugins/azure-sites-backend/src/api/index.ts b/plugins/azure-sites-backend/src/api/index.ts new file mode 100644 index 0000000000..187dc1314b --- /dev/null +++ b/plugins/azure-sites-backend/src/api/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { AzureSitesApi } from './AzureSitesApi'; diff --git a/plugins/azure-sites-backend/src/config.ts b/plugins/azure-sites-backend/src/config.ts new file mode 100644 index 0000000000..3475175cb5 --- /dev/null +++ b/plugins/azure-sites-backend/src/config.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; + +/** @public */ +export class AzureSitesConfig { + constructor( + public readonly domain: string, + public readonly tenantId: string, + public readonly subscriptions?: string[], + public readonly clientId?: string, + public readonly clientSecret?: string, + ) {} + + static fromConfig(config: Config): AzureSitesConfig { + const azConfig = config.getConfig('azureSites'); + + return new AzureSitesConfig( + azConfig.getString('domain'), + azConfig.getString('tenantId'), + azConfig + .getOptionalConfigArray('subscriptions') + ?.map(as => as.getString('id')), + azConfig.getOptionalString('clientId'), + azConfig.getOptionalString('clientSecret'), + ); + } +} diff --git a/plugins/azure-sites-backend/src/index.ts b/plugins/azure-sites-backend/src/index.ts new file mode 100644 index 0000000000..97f94e4470 --- /dev/null +++ b/plugins/azure-sites-backend/src/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './service/router'; +export * from './api'; +export * from './config'; diff --git a/plugins/azure-sites-backend/src/run.ts b/plugins/azure-sites-backend/src/run.ts new file mode 100644 index 0000000000..d945aa13f0 --- /dev/null +++ b/plugins/azure-sites-backend/src/run.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { getRootLogger } from '@backstage/backend-common'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; + +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/azure-sites-backend/src/service/router.ts b/plugins/azure-sites-backend/src/service/router.ts new file mode 100644 index 0000000000..bea9a15396 --- /dev/null +++ b/plugins/azure-sites-backend/src/service/router.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { errorHandler } from '@backstage/backend-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; + +import { AzureSitesApi } from '../api'; + +/** @public */ +export interface RouterOptions { + logger: Logger; + azureSitesApi: AzureSitesApi; +} + +/** @public */ +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger, azureSitesApi } = options; + + const router = Router(); + router.use(express.json()); + + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.send({ status: 'ok' }); + }); + router.get('/list/:name', async (request, response) => { + const { name } = request.params; + response.json( + await azureSitesApi.list({ + name: name, + }), + ); + }); + router.post( + '/:subscription/:resourceGroup/:name/start', + async (request, response) => { + const { subscription, resourceGroup, name } = request.params; + console.log('starting...'); + response.json( + await azureSitesApi.start({ + subscription, + resourceGroup, + name, + }), + ); + }, + ); + router.post( + '/:subscription/:resourceGroup/:name/stop', + async (request, response) => { + const { subscription, resourceGroup, name } = request.params; + console.log('stopping...'); + response.json( + await azureSitesApi.stop({ + subscription, + resourceGroup, + name, + }), + ); + }, + ); + router.use(errorHandler()); + return router; +} diff --git a/plugins/azure-sites-backend/src/service/standaloneServer.ts b/plugins/azure-sites-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..1998bee023 --- /dev/null +++ b/plugins/azure-sites-backend/src/service/standaloneServer.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + createServiceBuilder, + loadBackendConfig, +} from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { AzureSitesApi } from '../api'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'azure-backend' }); + const config = await loadBackendConfig({ logger, argv: process.argv }); + logger.debug('Starting application server...'); + const router = await createRouter({ + logger, + azureSitesApi: AzureSitesApi.fromConfig(config), + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/azure-backend', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/azure-sites-backend/src/setupTests.ts b/plugins/azure-sites-backend/src/setupTests.ts new file mode 100644 index 0000000000..813cdeaae3 --- /dev/null +++ b/plugins/azure-sites-backend/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/plugins/azure-sites-common/.eslintrc.js b/plugins/azure-sites-common/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/azure-sites-common/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/azure-sites-common/CHANGELOG.md b/plugins/azure-sites-common/CHANGELOG.md new file mode 100644 index 0000000000..387e8d9ddd --- /dev/null +++ b/plugins/azure-sites-common/CHANGELOG.md @@ -0,0 +1,7 @@ +# @backstage/plugin-azure-sites-common + +## 0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. diff --git a/plugins/azure-sites-common/api-report.md b/plugins/azure-sites-common/api-report.md new file mode 100644 index 0000000000..64b4275085 --- /dev/null +++ b/plugins/azure-sites-common/api-report.md @@ -0,0 +1,40 @@ +## API Report File for "@backstage/plugin-azure-sites-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// @public (undocumented) +export type AzureSite = { + href: string; + logstreamHref: string; + name: string; + kind: string; + location: string; + resourceGroup: string; + subscription: string; + state: string; + usageState: string; + containerSize: number; + lastModifiedDate: string; + tags: {}; +}; + +// @public (undocumented) +export type AzureSiteListRequest = { + name: string; +}; + +// @public (undocumented) +export type AzureSiteListResponse = { + items: AzureSite[]; +}; + +// @public (undocumented) +export type AzureSiteStartStopRequest = { + subscription: string; + resourceGroup: string; + name: string; +}; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/azure-sites-common/package.json b/plugins/azure-sites-common/package.json new file mode 100644 index 0000000000..568edaeff9 --- /dev/null +++ b/plugins/azure-sites-common/package.json @@ -0,0 +1,40 @@ +{ + "name": "@backstage/plugin-azure-sites-common", + "description": "Common functionalities for the azure plugin", + "version": "0.1.0-next.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "common-library" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/azure-sites-common" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "devDependencies": { + "@backstage/cli": "workspace:^" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/github-issues/src/components/GitHubIssues/index.ts b/plugins/azure-sites-common/src/index.ts similarity index 94% rename from plugins/github-issues/src/components/GitHubIssues/index.ts rename to plugins/azure-sites-common/src/index.ts index 6fa55e3358..db229eae34 100644 --- a/plugins/github-issues/src/components/GitHubIssues/index.ts +++ b/plugins/azure-sites-common/src/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './GitHubIssues'; + +export * from './types'; diff --git a/plugins/azure-sites-common/src/types.ts b/plugins/azure-sites-common/src/types.ts new file mode 100644 index 0000000000..7c8be7ce24 --- /dev/null +++ b/plugins/azure-sites-common/src/types.ts @@ -0,0 +1,48 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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. + */ + +/** @public */ +export type AzureSite = { + href: string; + logstreamHref: string; + name: string; + kind: string; + location: string; + resourceGroup: string; + subscription: string; + state: string; + usageState: string; + containerSize: number; + lastModifiedDate: string; + tags: {}; +}; + +/** @public */ +export type AzureSiteListRequest = { + name: string; +}; + +/** @public */ +export type AzureSiteListResponse = { + items: AzureSite[]; +}; + +/** @public */ +export type AzureSiteStartStopRequest = { + subscription: string; + resourceGroup: string; + name: string; +}; diff --git a/plugins/azure-sites/.eslintrc.js b/plugins/azure-sites/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/azure-sites/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/azure-sites/CHANGELOG.md b/plugins/azure-sites/CHANGELOG.md new file mode 100644 index 0000000000..4e1b2bb12b --- /dev/null +++ b/plugins/azure-sites/CHANGELOG.md @@ -0,0 +1,18 @@ +# @backstage/plugin-azure-sites + +## 0.1.0-next.0 + +### Minor Changes + +- 4a75ce761c: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-azure-sites-common@0.1.0-next.0 + - @backstage/theme@0.2.16 diff --git a/plugins/azure-sites/README.md b/plugins/azure-sites/README.md new file mode 100644 index 0000000000..f240dab140 --- /dev/null +++ b/plugins/azure-sites/README.md @@ -0,0 +1,70 @@ +# Azure Sites Plugin + +![preview of Azure table](docs/functions-table.png) + +_Inspired by [roadie.io AWS Lamda plugin](https://roadie.io/backstage/plugins/aws-lambda/)_ + +## Features + +- Azure overview table + +## Plugin Setup + +The following sections will help you get the Azure plugin setup and running + +### Azure Sites Backend + +You need to set up the Azure Sites Backend plugin before you move forward with any of these steps if you haven't already. + +### Entity Annotation + +To be able to use the Azure Sites plugin you need to add the following annotation to any entities you want to use it with: + +```yaml +azure.com/microsoft-web-sites: +``` + +`` supports case-insensitive exact / partial value. + +Example of Partial Matching: + +Let's say you have a number of functions apps, spread out over different regions (and possibly different subscriptions), and they follow a naming convention: + +``` +func-testapp-eu +func-testapp-ca +func-testapp-us +``` + +The annotation you will use to have the three functions' app appear in the overview table would look like this: + +```yaml +azure.com/microsoft-web-sites: func-testapp +``` + +### Install the component + +1. Install the plugin in the `packages/app` directory + +```sh +yarn add @backstage/plugin-azure-sites +``` + +2. Add widget component to your Backstage instance: + +```ts +// In packages/app/src/components/catalog/EntityPage.tsx +import { EntityAzureSitesOverviewWidget, isAzureWebSiteNameAvailable } from '@backstage/plugin-azure-sites'; + +... + +const serviceEntityPage = ( + + //... + Boolean(isAzureWebSiteNameAvailable(e))} path="/azure" title="Azure"> + + + //... + +); +``` diff --git a/plugins/azure-sites/api-report.md b/plugins/azure-sites/api-report.md new file mode 100644 index 0000000000..38ca6a8fe2 --- /dev/null +++ b/plugins/azure-sites/api-report.md @@ -0,0 +1,63 @@ +## API Report File for "@backstage/plugin-azure-sites" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { ApiRef } from '@backstage/core-plugin-api'; +import { AzureSiteListRequest } from '@backstage/plugin-azure-sites-common'; +import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common'; +import { AzureSiteStartStopRequest } from '@backstage/plugin-azure-sites-common'; +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { IdentityApi } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; + +// @public (undocumented) +export const azureSiteApiRef: ApiRef; + +// @public (undocumented) +export type AzureSitesApi = { + list: (request: AzureSiteListRequest) => Promise; + start: (request: AzureSiteStartStopRequest) => Promise; + stop: (request: AzureSiteStartStopRequest) => Promise; +}; + +// @public (undocumented) +export class AzureSitesApiBackendClient implements AzureSitesApi { + constructor(options: { + discoveryApi: DiscoveryApi; + identityApi: IdentityApi; + }); + // (undocumented) + list(request: AzureSiteListRequest): Promise; + // (undocumented) + start(request: AzureSiteStartStopRequest): Promise; + // (undocumented) + stop(request: AzureSiteStartStopRequest): Promise; +} + +// @public (undocumented) +export const AzureSitesOverviewWidget: () => JSX.Element; + +// @public (undocumented) +export const azureSitesPlugin: BackstagePlugin< + { + entityContent: RouteRef; + }, + {}, + {} +>; + +// @public (undocumented) +export const EntityAzureSitesOverviewWidget: () => JSX.Element; + +// @public (undocumented) +export const isAzureWebSiteNameAvailable: ( + entity: Entity, +) => string | undefined; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/azure-sites/dev/index.tsx b/plugins/azure-sites/dev/index.tsx new file mode 100644 index 0000000000..02f8ed99cd --- /dev/null +++ b/plugins/azure-sites/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { createDevApp } from '@backstage/dev-utils'; +import { azureSitesPlugin } from '../src/plugin'; + +createDevApp().registerPlugin(azureSitesPlugin).render(); diff --git a/plugins/azure-sites/docs/functions-table.png b/plugins/azure-sites/docs/functions-table.png new file mode 100644 index 0000000000..d8603e8fa7 Binary files /dev/null and b/plugins/azure-sites/docs/functions-table.png differ diff --git a/plugins/azure-sites/package.json b/plugins/azure-sites/package.json new file mode 100644 index 0000000000..feff8e8645 --- /dev/null +++ b/plugins/azure-sites/package.json @@ -0,0 +1,65 @@ +{ + "name": "@backstage/plugin-azure-sites", + "version": "0.1.0-next.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" + }, + "backstage": { + "role": "frontend-plugin" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/azure-sites" + }, + "keywords": [ + "backstage", + "azure" + ], + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/catalog-model": "workspace:^", + "@backstage/core-components": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/plugin-azure-sites-common": "workspace:^", + "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/theme": "workspace:^", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.57", + "luxon": "^3.0.0", + "react-use": "^17.2.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/core-app-api": "workspace:^", + "@backstage/dev-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.0.0", + "@types/node": "^16.11.26", + "cross-fetch": "^3.1.5", + "msw": "^0.47.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/azure-sites/src/api/AzureSitesApi.ts b/plugins/azure-sites/src/api/AzureSitesApi.ts new file mode 100644 index 0000000000..87a3b46a8d --- /dev/null +++ b/plugins/azure-sites/src/api/AzureSitesApi.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { createApiRef } from '@backstage/core-plugin-api'; +import { + AzureSiteListRequest, + AzureSiteListResponse, + AzureSiteStartStopRequest, +} from '@backstage/plugin-azure-sites-common'; + +/** @public */ +export const azureSiteApiRef = createApiRef({ + id: 'plugin.azure-sites.service', +}); + +/** @public */ +export type AzureSitesApi = { + list: (request: AzureSiteListRequest) => Promise; + start: (request: AzureSiteStartStopRequest) => Promise; + stop: (request: AzureSiteStartStopRequest) => Promise; +}; diff --git a/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts b/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts new file mode 100644 index 0000000000..fc622e786b --- /dev/null +++ b/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { AzureSitesApi } from './AzureSitesApi'; +import { + AzureSiteListRequest, + AzureSiteListResponse, + AzureSiteStartStopRequest, +} from '@backstage/plugin-azure-sites-common'; +import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; + +/** @public */ +export class AzureSitesApiBackendClient implements AzureSitesApi { + private readonly identityApi: IdentityApi; + private readonly discoveryApi: DiscoveryApi; + constructor(options: { + discoveryApi: DiscoveryApi; + identityApi: IdentityApi; + }) { + this.discoveryApi = options.discoveryApi; + this.identityApi = options.identityApi; + } + + async stop(request: AzureSiteStartStopRequest): Promise { + const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/${ + request.subscription + }/${request.resourceGroup}/${request.name}/stop`; + const { token: accessToken } = await this.identityApi.getCredentials(); + await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(accessToken && { Authorization: `Bearer ${accessToken}` }), + }, + }); + } + async start(request: AzureSiteStartStopRequest): Promise { + const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/${ + request.subscription + }/${request.resourceGroup}/${request.name}/start`; + const { token: accessToken } = await this.identityApi.getCredentials(); + await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(accessToken && { Authorization: `Bearer ${accessToken}` }), + }, + }); + } + + async list(request: AzureSiteListRequest): Promise { + const url = `${await this.discoveryApi.getBaseUrl('azure-sites')}/list/${ + request.name + }`; + const { token: accessToken } = await this.identityApi.getCredentials(); + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + ...(accessToken && { Authorization: `Bearer ${accessToken}` }), + }, + }); + return await response.json(); + } +} diff --git a/plugins/azure-sites/src/api/index.ts b/plugins/azure-sites/src/api/index.ts new file mode 100644 index 0000000000..2b6cc64221 --- /dev/null +++ b/plugins/azure-sites/src/api/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './AzureSitesApi'; +export * from './AzureSitesApiBackendClient'; diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx new file mode 100644 index 0000000000..755a42f5da --- /dev/null +++ b/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx @@ -0,0 +1,76 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { Entity } from '@backstage/catalog-model'; +import { useSites } from '../../hooks/useSites'; +import { + AZURE_WEB_SITE_NAME_ANNOTATION, + useServiceEntityAnnotations, +} from '../../hooks/useServiceEntityAnnotations'; +import { + ErrorBoundary, + MissingAnnotationEmptyState, + ResponseErrorPanel, +} from '@backstage/core-components'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { AzureSitesOverviewTable } from '../AzureSitesOverviewTableComponent/AzureSitesOverviewTable'; + +/** @public */ +export const isAzureWebSiteNameAvailable = (entity: Entity) => + entity?.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION]; + +const AzureSitesOverview = ({ entity }: { entity: Entity }) => { + const { webSiteName } = useServiceEntityAnnotations(entity); + + const [sites] = useSites({ + name: webSiteName, + }); + + if (sites.error) { + return ( +
+ +
+ ); + } + + return ( + + ); +}; + +/** @public */ +export const AzureSitesOverviewWidget = () => { + const { entity } = useEntity(); + + if (!isAzureWebSiteNameAvailable(entity)) { + return ( + + ); + } + + return ( + + + + ); +}; diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx new file mode 100644 index 0000000000..8f527a635d --- /dev/null +++ b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { render } from '@testing-library/react'; +import { + AnyApiRef, + configApiRef, + errorApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { rest } from 'msw'; +import { + setupRequestMockHandlers, + TestApiProvider, +} from '@backstage/test-utils'; +import { setupServer } from 'msw/node'; +import { DateTime } from 'luxon'; +import { siteMock } from '../../mocks/mocks'; +import { AzureSitesOverviewTable } from './AzureSitesOverviewTable'; +import { azureSiteApiRef } from '../../api'; + +const errorApiMock = { post: jest.fn(), error$: jest.fn() }; +const identityApiMock = (getCredentials: any) => ({ + signOut: jest.fn(), + getProfileInfo: jest.fn(), + getBackstageIdentity: jest.fn(), + getCredentials, +}); +const azureSitesApiMock = {}; + +const config = { + getString: (_: string) => 'https://test-url', +}; + +const apis: [AnyApiRef, Partial][] = [ + [errorApiRef, errorApiMock], + [configApiRef, config], + [azureSiteApiRef, azureSitesApiMock], + [identityApiRef, identityApiMock], +]; + +describe('AzureSitesOverviewWidget', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + beforeEach(() => { + worker.use( + rest.get('/list/func-mock', (_, res, ctx) => { + res(ctx.json(siteMock)); + }), + ); + }); + + it('should display an overview table with the data from the requests', async () => { + const rendered = render( + + + , + ); + + expect(await rendered.findByText(siteMock.name)).toBeInTheDocument(); + expect(await rendered.findByText(siteMock.location)).toBeInTheDocument(); + expect(await rendered.findByText(siteMock.state)).toBeInTheDocument(); + expect( + await rendered.findByText( + DateTime.fromISO(siteMock.lastModifiedDate).toLocaleString( + DateTime.DATETIME_MED, + ), + ), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx new file mode 100644 index 0000000000..c0558ee05c --- /dev/null +++ b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx @@ -0,0 +1,266 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { Dispatch, useEffect, useState } from 'react'; +import { + Box, + Card, + IconButton, + LinearProgress, + Menu, + MenuItem, + Snackbar, + Tooltip, +} from '@material-ui/core'; +import { default as MuiAlert } from '@material-ui/lab/Alert'; +import { AzureSite } from '@backstage/plugin-azure-sites-common'; +import { Table, TableColumn, Link } from '@backstage/core-components'; +import FlashOnIcon from '@material-ui/icons/FlashOn'; +import PublicIcon from '@material-ui/icons/Public'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import StartIcon from '@material-ui/icons/PlayArrow'; +import StopIcon from '@material-ui/icons/Stop'; +import OpenInNewIcon from '@material-ui/icons/OpenInNew'; +import { DateTime } from 'luxon'; +import { useApi } from '@backstage/core-plugin-api'; +import { azureSiteApiRef } from '../../api'; + +type States = 'Waiting' | 'Running' | 'Paused' | 'Failed' | 'Stopped'; +type Kinds = 'app' | 'functionapp'; + +const State = ({ value }: { value: States }) => { + const colorMap = { + Waiting: '#dcbc21', + Running: 'green', + Paused: 'black', + Failed: 'red', + Stopped: 'black', + }; + return ( + + + {value} + + ); +}; + +const Kind = ({ value }: { value: Kinds }) => { + const iconMap = { + app: , + functionapp: , + }; + return ( + + {iconMap[value]} + + ); +}; + +type TableProps = { + data: AzureSite[]; + loading: boolean; +}; + +const ActionButtons = ({ + value, + onMenuItemClick, +}: { + value: AzureSite; + onMenuItemClick: Dispatch>; +}) => { + const azureApi = useApi(azureSiteApiRef); + + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + + const handleOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + const start = () => { + azureApi.start({ + name: value.name, + resourceGroup: value.resourceGroup, + subscription: value.subscription, + }); + onMenuItemClick('Starting, this may take some time...'); + handleClose(); + }; + const stop = () => { + azureApi.stop({ + name: value.name, + resourceGroup: value.resourceGroup, + subscription: value.subscription, + }); + onMenuItemClick('Stopping, this may take some time...'); + handleClose(); + }; + + return ( +
+ + + + + {value.state !== 'Running' && ( + + +  Start + + )} + {value.state !== 'Stopped' && ( + + +  Stop + + )} + + +  Log Stream + + +
+ ); +}; + +/** @public */ +export const AzureSitesOverviewTable = ({ data, loading }: TableProps) => { + const [snackbarMessage, setSnackbarMessage] = useState(null); + const [isSnackbarOpen, setSnackbarOpen] = useState(false); + + const onSnackbarClose = () => { + setSnackbarMessage(null); + }; + + useEffect(() => { + setSnackbarOpen(!!snackbarMessage); + }, [snackbarMessage]); + + const columns: TableColumn[] = [ + { + width: '25px', + field: 'kind', + render: (func: AzureSite) => , + }, + { + title: 'name', + field: 'name', + highlight: true, + render: (func: AzureSite) => { + return {func.name}; + }, + }, + { + title: 'location', + field: 'location', + render: (func: AzureSite) => func.location ?? 'unknown', + }, + { + title: 'status', + field: 'status', + render: (func: AzureSite) => , + }, + { + title: 'last modified', + field: 'lastModifiedDate', + render: (func: AzureSite) => + DateTime.fromISO(func.lastModifiedDate).toLocaleString( + DateTime.DATETIME_MED, + ), + }, + { + title: 'actions', + align: 'right', + sorting: false, + field: 'actions', + render: (func: AzureSite) => ( + + ), + }, + ]; + + const tableStyle = { + minWidth: '0', + width: '100%', + }; + + return ( + + + + + Azure Sites + + } + options={{ paging: true, search: false, pageSize: 10 }} + data={data} + emptyContent={} + isLoading={loading} + columns={columns} + /> + + + {snackbarMessage} + + + + ); +}; diff --git a/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.ts b/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.ts new file mode 100644 index 0000000000..08070dc8e4 --- /dev/null +++ b/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { Entity } from '@backstage/catalog-model'; + +export const AZURE_WEB_SITE_NAME_ANNOTATION = 'azure.com/microsoft-web-sites'; +export const useServiceEntityAnnotations = (entity: Entity) => { + const webSiteName = + entity?.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION] ?? ''; + + return { + webSiteName, + }; +}; diff --git a/plugins/azure-sites/src/hooks/useSites.ts b/plugins/azure-sites/src/hooks/useSites.ts new file mode 100644 index 0000000000..fb3215c008 --- /dev/null +++ b/plugins/azure-sites/src/hooks/useSites.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 useInterval from 'react-use/lib/useInterval'; +import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common'; +import { azureSiteApiRef } from '../api'; +import { useCallback } from 'react'; + +const POLLING_INTERVAL = 15000; + +export function useSites({ name }: { name: string }) { + const azureApi = useApi(azureSiteApiRef); + const errorApi = useApi(errorApiRef); + + const list = useCallback( + async () => { + return await azureApi.list({ + name: name, + }); + }, + [name], // eslint-disable-line react-hooks/exhaustive-deps + ); + const { + loading, + value: data, + error, + retry, + } = useAsyncRetry(async () => { + try { + const sites = await list(); + return sites; + } catch (e) { + if (e instanceof Error) { + if (e?.message === 'AbortError') { + errorApi.post( + new Error( + 'Timeout reaching backend plugin, please add azure-backend plugin', + ), + ); + } + errorApi.post(e); + } + return null; + } + }, []); + + useInterval(() => retry(), POLLING_INTERVAL); + + return [ + { + loading, + data, + error, + retry, + }, + ] as const; +} diff --git a/plugins/azure-sites/src/index.ts b/plugins/azure-sites/src/index.ts new file mode 100644 index 0000000000..83029c6af5 --- /dev/null +++ b/plugins/azure-sites/src/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './plugin'; +export * from './api'; +export * from './components/AzureSitesOverviewComponent/AzureSitesOverview'; diff --git a/plugins/azure-sites/src/mocks/mocks.ts b/plugins/azure-sites/src/mocks/mocks.ts new file mode 100644 index 0000000000..2bc3b8b9fe --- /dev/null +++ b/plugins/azure-sites/src/mocks/mocks.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { AzureSite } from '@backstage/plugin-azure-sites-common'; + +export const entityMock = { + metadata: { + namespace: 'default', + annotations: { + 'azure.com/microsoft-web-sites': 'func-mock', + }, + name: 'sample-azure-service', + description: 'A service for testing Backstage functionality.', + uid: 'c009b513-d053-4b3f-9429-8433a145e943', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + spec: { + type: 'service', + owner: 'dev_group@example.com', + lifecycle: 'experimental', + }, +}; + +// https://management.azure.com/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Web/sites/{{functionsName}}?api-version=2022-03-01 +export const siteMock: AzureSite = { + name: 'func-mock', + kind: 'functionapp', + resourceGroup: 'mock-resourcegroup', + subscription: '00000000-0000-0000-0000-000000000000', + tags: { + isMock: true, + }, + location: 'West Europe', + state: 'Running', + href: 'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock', + logstreamHref: + 'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock/logStream', + usageState: 'Normal', + lastModifiedDate: '2022-09-02T11:09:58.9033333', + containerSize: 100, +}; diff --git a/plugins/azure-sites/src/plugin.test.ts b/plugins/azure-sites/src/plugin.test.ts new file mode 100644 index 0000000000..fba8e36e6f --- /dev/null +++ b/plugins/azure-sites/src/plugin.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { azureSitesPlugin } from './plugin'; + +describe('azure', () => { + it('should export plugin', () => { + expect(azureSitesPlugin).toBeDefined(); + }); +}); diff --git a/plugins/azure-sites/src/plugin.ts b/plugins/azure-sites/src/plugin.ts new file mode 100644 index 0000000000..f54f121ea1 --- /dev/null +++ b/plugins/azure-sites/src/plugin.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + createApiFactory, + createComponentExtension, + createPlugin, + createRouteRef, + discoveryApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { azureSiteApiRef, AzureSitesApiBackendClient } from './api'; + +const entityContentRouteRef = createRouteRef({ + id: 'Azure Sites Entity Content', +}); + +/** @public */ +export const azureSitesPlugin = createPlugin({ + id: 'azureSites', + apis: [ + createApiFactory({ + api: azureSiteApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi, identityApi }) => + new AzureSitesApiBackendClient({ discoveryApi, identityApi }), + }), + ], + routes: { + entityContent: entityContentRouteRef, + }, +}); + +/** @public */ +export const EntityAzureSitesOverviewWidget = azureSitesPlugin.provide( + createComponentExtension({ + name: 'EntityAzureSitesOverviewWidget', + component: { + lazy: () => + import( + './components/AzureSitesOverviewComponent/AzureSitesOverview' + ).then(m => m.AzureSitesOverviewWidget), + }, + }), +); diff --git a/plugins/azure-sites/src/routes.ts b/plugins/azure-sites/src/routes.ts new file mode 100644 index 0000000000..1b10ad8dde --- /dev/null +++ b/plugins/azure-sites/src/routes.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { createRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + id: 'azure-sites', +}); diff --git a/plugins/azure-sites/src/setupTests.ts b/plugins/azure-sites/src/setupTests.ts new file mode 100644 index 0000000000..2b5b615b19 --- /dev/null +++ b/plugins/azure-sites/src/setupTests.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index 770491ec15..c88b7cf763 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-badges-backend +## 0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.31 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.1.31-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.31-next.1 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 678dc8d56e..a8e465597c 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges-backend", "description": "A Backstage backend plugin that generates README badges for your entities", - "version": "0.1.31-next.1", + "version": "0.1.32-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index 5adc7d112b..f736588933 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-badges +## 0.2.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.2.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.2.34-next.1 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 0f05f6fa25..68d47e08ac 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges", "description": "A Backstage plugin that generates README badges for your entities", - "version": "0.2.34-next.1", + "version": "0.2.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/badges/src/components/EntityBadgesDialog.test.tsx b/plugins/badges/src/components/EntityBadgesDialog.test.tsx index ea44eb20fc..5d0e7fe849 100644 --- a/plugins/badges/src/components/EntityBadgesDialog.test.tsx +++ b/plugins/badges/src/components/EntityBadgesDialog.test.tsx @@ -38,7 +38,10 @@ describe('EntityBadgesDialog', () => { }, ]), }; - const mockEntity = { metadata: { name: 'mock' } } as Entity; + const mockEntity = { + metadata: { name: 'mock' }, + kind: 'MockKind', + } as Entity; const rendered = await renderWithEffects( { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.2.0-next.2 + +### Minor Changes + +- 8554533546: **BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. + + These changes are **required** to `packages/backend/src/plugins/bazaar.ts` + + The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. + + ```diff + import { PluginEnvironment } from '../types'; + import { createRouter } from '@backstage/plugin-bazaar-backend'; + import { Router } from 'express'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.21-next.1 ### Patch Changes diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 31ae4c6cb5..2f0e766917 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar-backend", - "version": "0.1.21-next.1", + "version": "0.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index b2d0e8a8d4..7ff38b63f8 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-bazaar +## 0.2.0-next.0 + +### Minor Changes + +- 28b39e0e0e: The limit prop of BazaarOverviewCard has been removed entirely, and instead replaced with a new optional boolean prop `fullWidth`. The BazaarOverviewCard now always use full height without fixed width. Also fixed problem with link to Bazaar. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/cli@0.21.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.25 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/cli@0.20.0 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + +## 0.1.25-next.2 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.25-next.1 ### Patch Changes diff --git a/plugins/bazaar/README.md b/plugins/bazaar/README.md index 6168b0c4ba..40b2a8afbe 100644 --- a/plugins/bazaar/README.md +++ b/plugins/bazaar/README.md @@ -76,17 +76,17 @@ export const homePage = ( + -+ ++ + -+ -+ ++ ++ + {/* ...other homepage items */} ``` -Specify how many projects you want through the "limit" props. In the example above 4 cards is specified. +The property `fullWidth` is optional and can be used to adjust the card to fit a grid with column width 12. ## How does the Bazaar work? diff --git a/plugins/bazaar/api-report.md b/plugins/bazaar/api-report.md index 2e6ebe6746..c12c95a1f7 100644 --- a/plugins/bazaar/api-report.md +++ b/plugins/bazaar/api-report.md @@ -16,7 +16,7 @@ export const BazaarOverviewCard: ( // @public (undocumented) export type BazaarOverviewCardProps = { order: 'latest' | 'random'; - limit: number; + fullWidth?: boolean; }; // @public (undocumented) diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index e019784cc5..eb0542a504 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.25-next.1", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx b/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx index 83358c0a05..d64e36baeb 100644 --- a/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx +++ b/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx @@ -30,7 +30,7 @@ import { bazaarPlugin } from '../../plugin'; /** @public */ export type BazaarOverviewCardProps = { order: 'latest' | 'random'; - limit: number; + fullWidth?: boolean; }; const getUnlinkedCatalogEntities = ( @@ -48,14 +48,14 @@ const getUnlinkedCatalogEntities = ( /** @public */ export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => { - const { order, limit } = props; + const { order, fullWidth = false } = props; const bazaarApi = useApi(bazaarApiRef); const catalogApi = useApi(catalogApiRef); const root = useRouteRef(bazaarPlugin.routes.root); const bazaarLink = { title: 'Go to Bazaar', - link: root.toString(), + link: `${root()}`, }; const [unlinkedCatalogEntities, setUnlinkedCatalogEntities] = @@ -66,6 +66,7 @@ export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => { }); const [bazaarProjects, fetchBazaarProjects] = useAsyncFn(async () => { + const limit = fullWidth ? 6 : 3; const response = await bazaarApi.getProjects(limit, order); return response.data.map(parseBazaarProject) as BazaarProject[]; }); @@ -133,8 +134,7 @@ export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => { fetchBazaarProjects={fetchBazaarProjects} catalogEntities={unlinkedCatalogEntities || []} useTablePagination={false} - fullHeight={false} - fixedWidth + gridSize={fullWidth ? 2 : 4} /> ); diff --git a/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx index dace9b8185..fcae411519 100644 --- a/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx +++ b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx @@ -54,7 +54,7 @@ const useStyles = makeStyles({ float: 'right', }, content: { - overflow: 'scroll', + height: '13rem', }, header: { whiteSpace: 'nowrap', @@ -67,7 +67,6 @@ export const ProjectCard = ({ project, fetchBazaarProjects, catalogEntities, - fullHeight, }: Props) => { const classes = useStyles(); const [openCard, setOpenCard] = useState(false); @@ -103,10 +102,7 @@ export const ProjectCard = ({ base: DateTime.now(), })}`} /> - + {Number(membersCount) === Number(1) diff --git a/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx index 77c16ef306..7dd82edf94 100644 --- a/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx +++ b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx @@ -17,7 +17,7 @@ import React, { ChangeEvent, useState } from 'react'; import { Content } from '@backstage/core-components'; import { ProjectCard } from '../ProjectCard/ProjectCard'; -import { makeStyles, Grid, TablePagination } from '@material-ui/core'; +import { makeStyles, Grid, TablePagination, GridSize } from '@material-ui/core'; import { BazaarProject } from '../../types'; import { Entity } from '@backstage/catalog-model'; @@ -26,8 +26,7 @@ type Props = { fetchBazaarProjects: () => Promise; catalogEntities: Entity[]; useTablePagination?: boolean; - fullHeight?: boolean; - fixedWidth?: boolean; + gridSize?: GridSize; }; const useStyles = makeStyles({ @@ -55,8 +54,7 @@ export const ProjectPreview = ({ fetchBazaarProjects, catalogEntities, useTablePagination = true, - fullHeight = true, - fixedWidth = false, + gridSize = 2, }: Props) => { const classes = useStyles(); const [page, setPage] = useState(1); @@ -86,18 +84,12 @@ export const ProjectPreview = ({ .slice((page - 1) * rows, rows * page) .map((bazaarProject: BazaarProject, i: number) => { return ( - + ); diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 2dc4bd1ae3..d3ab58bf5a 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.4.0-next.0 + +## 0.2.0 + +### Minor Changes + +- ad74723fbf: Update Bitbucket Cloud models to latest OAS version. + + The latest specification contained some BREAKING CHANGES + due to removed fields. + + All of these fields are not used at other plugins, though. + Therefore, this change has no impact on other modules here. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2 + +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.2 + ## 0.2.0-next.1 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 10d443e2a5..fbc63e4497 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", "description": "Common functionalities for bitbucket-cloud plugins", - "version": "0.2.0-next.1", + "version": "0.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index d8781740ec..2ec214ea00 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-bitrise +## 0.1.38-next.0 + +### Patch Changes + +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.1.37 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.1.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.1.37-next.1 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 0645020104..cb23af3b90 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitrise", "description": "A Backstage plugin that integrates towards Bitrise", - "version": "0.1.37-next.1", + "version": "0.1.38-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,7 +36,7 @@ "luxon": "^3.0.0", "qs": "^6.9.6", "react-use": "^17.2.4", - "recharts": "^1.8.5" + "recharts": "^2.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0" diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 54717bb37d..59ab7970f2 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.1.11-next.0 + +### Patch Changes + +- bae3617be5: `AwsS3EntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/aws-s3/discovery + +- defb389ecd: Add `awsS3EntityProviderCatalogModule` (new backend-plugin-api, alpha). +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/api-report.md b/plugins/catalog-backend-module-aws/api-report.md index b1f6358979..a911fb5ebb 100644 --- a/plugins/catalog-backend-module-aws/api-report.md +++ b/plugins/catalog-backend-module-aws/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorParser } from '@backstage/plugin-catalog-backend'; @@ -12,6 +13,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; import { UrlReader } from '@backstage/backend-common'; @@ -77,7 +79,8 @@ export class AwsS3EntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): AwsS3EntityProvider[]; // (undocumented) @@ -85,4 +88,9 @@ export class AwsS3EntityProvider implements EntityProvider { // (undocumented) refresh(logger: Logger): Promise; } + +// @alpha +export const awsS3EntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; ``` diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts index 6f84ca6daf..b3610ccfc5 100644 --- a/plugins/catalog-backend-module-aws/config.d.ts +++ b/plugins/catalog-backend-module-aws/config.d.ts @@ -14,11 +14,10 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { - /** - * List of processor-specific options and attributes - */ processors?: { /** * AwsOrganizationCloudAccountProcessor configuration @@ -32,9 +31,6 @@ export interface Config { }; }; }; - /** - * List of provider-specific options and attributes - */ providers?: { /** * AwsS3EntityProvider configuration @@ -45,44 +41,46 @@ export interface Config { | { /** * (Required) AWS S3 Bucket Name - * @visibility backend */ bucketName: string; /** * (Optional) AWS S3 Object key prefix * If not set, all keys will be accepted, no filtering will be applied. - * @visibility backend */ prefix?: string; /** * (Optional) AWS Region. * If not set, AWS_REGION environment variable or aws config file will be used. * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html - * @visibility backend */ region?: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } | Record< string, { /** * (Required) AWS S3 Bucket Name - * @visibility backend */ bucketName: string; /** * (Optional) AWS S3 Object key prefix * If not set, all keys will be accepted, no filtering will be applied. - * @visibility backend */ prefix?: string; /** * (Optional) AWS Region. * If not set, AWS_REGION environment variable or aws config file will be used. * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html - * @visibility backend */ region?: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 8be3ca3031..a000ee9d84 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,12 +1,13 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", "description": "A Backstage catalog backend module that helps integrate towards AWS", - "version": "0.1.10-next.1", + "version": "0.1.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,22 +24,24 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "aws-sdk": "^2.840.0", "lodash": "^4.17.21", @@ -47,14 +50,17 @@ "winston": "^3.2.1" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/lodash": "^4.14.151", "aws-sdk-mock": "^5.2.1", + "luxon": "^3.0.0", "yaml": "^2.0.0" }, "files": [ - "dist", - "config.d.ts" + "alpha", + "config.d.ts", + "dist" ], "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-aws/src/index.ts b/plugins/catalog-backend-module-aws/src/index.ts index 212308edaa..804fd97e91 100644 --- a/plugins/catalog-backend-module-aws/src/index.ts +++ b/plugins/catalog-backend-module-aws/src/index.ts @@ -22,4 +22,5 @@ export * from './processors'; export * from './providers'; +export { awsS3EntityProviderCatalogModule } from './service/AwsS3EntityProviderCatalogModule'; export * from './types'; diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts index 3d59d738cf..134a3c6338 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts @@ -15,7 +15,11 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { AwsS3EntityProvider } from './AwsS3EntityProvider'; @@ -83,6 +87,7 @@ describe('AwsS3EntityProvider', () => { expectedBaseUrl: string, names: Record, integrationConfig?: object, + scheduleInConfig?: boolean, ) => { const config = new ConfigReader({ integrations: { @@ -97,15 +102,25 @@ describe('AwsS3EntityProvider', () => { }, }); + const schedulingConfig: Record = {}; + const schedule = new PersistingTaskRunner(); const entityProviderConnection: EntityProviderConnection = { applyMutation: jest.fn(), refresh: jest.fn(), }; + if (scheduleInConfig) { + schedulingConfig.scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + } else { + schedulingConfig.schedule = schedule; + } + const provider = AwsS3EntityProvider.fromConfig(config, { + ...schedulingConfig, logger, - schedule, })[0]; expect(provider.getProviderName()).toEqual(`awsS3-provider:${providerId}`); @@ -224,4 +239,81 @@ describe('AwsS3EntityProvider', () => { }, ); }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + awsS3: { + test: { + bucketName: 'bucket-1', + prefix: 'sub/dir/', + region: 'eu-west-1', + }, + }, + }, + }, + }); + + expect(() => + AwsS3EntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + awsS3: { + test: { + bucketName: 'bucket-1', + prefix: 'sub/dir/', + region: 'eu-west-1', + }, + }, + }, + }, + }); + + expect(() => + AwsS3EntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for awsS3-provider:test', + ); + }); + + // eslint-disable-next-line jest/expect-expect + it('single simple provider config with schedule in config', async () => { + return expectMutation( + 'regionalStatic', + { + bucketName: 'bucket-1', + prefix: 'sub/dir/', + region: 'eu-west-1', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + 'https://s3.eu-west-1.amazonaws.com/bucket-1/sub/dir/', + { + 'key1.yaml': 'generated-7f6d5861b0b3401a38b5fe62e6c7ca11da5fd6d8', + 'key2.yaml': 'generated-a290be145586042af7d80715626399c9d661718d', + 'key3.yaml': 'generated-8d75f78ed9fa618ce433b226dc24eeab441f3a2d', + 'key 4.yaml': 'generated-1e0249dcb5805fc2ce6ac2d3c4d2a3ef4f1270c0', + }, + undefined, + true, + ); + }); }); diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts index cee5eea1a4..80bc4d459b 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AwsS3Integration, ScmIntegrations } from '@backstage/integration'; import { @@ -49,7 +49,8 @@ export class AwsS3EntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): AwsS3EntityProvider[] { const providerConfigs = readAwsS3Configs(configRoot); @@ -67,22 +68,35 @@ export class AwsS3EntityProvider implements EntityProvider { throw new Error('No integration found for awsS3'); } - return providerConfigs.map( - providerConfig => - new AwsS3EntityProvider( - providerConfig, - integration, - options.logger, - options.schedule, - ), - ); + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + + return providerConfigs.map(providerConfig => { + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for awsS3-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + + return new AwsS3EntityProvider( + providerConfig, + integration, + options.logger, + taskRunner, + ); + }); } private constructor( private readonly config: AwsS3Config, integration: AwsS3Integration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.logger = logger.child({ target: this.getProviderName(), @@ -99,13 +113,13 @@ export class AwsS3EntityProvider implements EntityProvider { s3ForcePathStyle: integration.config.s3ForcePathStyle, }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-aws/src/providers/config.test.ts b/plugins/catalog-backend-module-aws/src/providers/config.test.ts index 534d4da35e..fb2f3c0790 100644 --- a/plugins/catalog-backend-module-aws/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-aws/src/providers/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readAwsS3Configs } from './config'; describe('readAwsS3Configs', () => { @@ -54,17 +55,26 @@ describe('readAwsS3Configs', () => { const provider3 = { bucketName: 'bucket-3', }; + const provider4 = { + bucketName: 'bucket-4', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }; const config = { catalog: { providers: { - awsS3: { provider1, provider2, provider3 }, + awsS3: { provider1, provider2, provider3, provider4 }, }, }, }; const actual = readAwsS3Configs(new ConfigReader(config)); - expect(actual).toHaveLength(3); + expect(actual).toHaveLength(4); expect(actual[0]).toEqual({ ...provider1, id: 'provider1', @@ -77,6 +87,14 @@ describe('readAwsS3Configs', () => { ...provider3, id: 'provider3', }); + expect(actual[3]).toEqual({ + ...provider4, + id: 'provider4', + schedule: { + ...provider4.schedule, + frequency: Duration.fromISO(provider4.schedule.frequency), + }, + }); }); it('fails if bucketName is missing', () => { diff --git a/plugins/catalog-backend-module-aws/src/providers/config.ts b/plugins/catalog-backend-module-aws/src/providers/config.ts index 5fdf2351c3..5ef2729bf0 100644 --- a/plugins/catalog-backend-module-aws/src/providers/config.ts +++ b/plugins/catalog-backend-module-aws/src/providers/config.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AwsS3Config } from './types'; @@ -46,10 +47,15 @@ function readAwsS3Config(id: string, config: Config): AwsS3Config { const region = config.getOptionalString('region'); const prefix = config.getOptionalString('prefix'); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, bucketName, region, prefix, + schedule, }; } diff --git a/plugins/catalog-backend-module-aws/src/providers/types.ts b/plugins/catalog-backend-module-aws/src/providers/types.ts index 3508bf919c..204fa154c6 100644 --- a/plugins/catalog-backend-module-aws/src/providers/types.ts +++ b/plugins/catalog-backend-module-aws/src/providers/types.ts @@ -14,9 +14,12 @@ * limitations under the License. */ +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; + export type AwsS3Config = { id: string; bucketName: string; prefix?: string; region?: string; + schedule?: TaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..3167291bca --- /dev/null +++ b/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { awsS3EntityProviderCatalogModule } from './AwsS3EntityProviderCatalogModule'; +import { AwsS3EntityProvider } from '../providers'; + +describe('awsS3EntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + awsS3: { + bucketName: 'module-test', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [awsS3EntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'awsS3-provider:default', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.ts b/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.ts new file mode 100644 index 0000000000..79269ad53a --- /dev/null +++ b/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + createBackendModule, + loggerToWinstonLogger, + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { AwsS3EntityProvider } from '../providers'; + +/** + * Registers the AwsS3EntityProvider with the catalog processing extension point. + * + * @alpha + */ +export const awsS3EntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'awsS3EntityProvider', + register(env) { + env.registerInit({ + deps: { + config: configServiceRef, + catalog: catalogProcessingExtensionPoint, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ config, catalog, logger, scheduler }) { + catalog.addEntityProvider( + AwsS3EntityProvider.fromConfig(config, { + logger: loggerToWinstonLogger(logger), + scheduler, + }), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 4342307cf5..7fed636045 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.9-next.0 + +### Patch Changes + +- 87ff05892d: `AzureDevOpsEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/azure/discovery + +- 0ca399b31b: Add `azureDevOpsEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/api-report.md b/plugins/catalog-backend-module-azure/api-report.md index 22f5015c9b..84ffaf39e9 100644 --- a/plugins/catalog-backend-module-azure/api-report.md +++ b/plugins/catalog-backend-module-azure/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; @@ -10,6 +11,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TaskRunner } from '@backstage/backend-tasks'; @@ -45,7 +47,8 @@ export class AzureDevOpsEntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): AzureDevOpsEntityProvider[]; // (undocumented) @@ -53,4 +56,9 @@ export class AzureDevOpsEntityProvider implements EntityProvider { // (undocumented) refresh(logger: Logger): Promise; } + +// @alpha +export const azureDevOpsEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; ``` diff --git a/plugins/catalog-backend-module-azure/config.d.ts b/plugins/catalog-backend-module-azure/config.d.ts index 6e423c9415..940695c8ea 100644 --- a/plugins/catalog-backend-module-azure/config.d.ts +++ b/plugins/catalog-backend-module-azure/config.d.ts @@ -14,34 +14,35 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + interface AzureDevOpsConfig { /** * (Optional) The DevOps host; leave empty for `dev.azure.com`, otherwise set to your self-hosted instance host. - * @visibility backend */ host: string; /** * (Required) Your organization slug. - * @visibility backend */ organization: string; /** * (Required) Your project slug. - * @visibility backend */ project: string; /** * (Optional) The repository name. Wildcards are supported as show on the examples above. * If not set, all repositories will be searched. - * @visibility backend */ repository?: string; /** * (Optional) Where to find catalog-info.yaml files. Wildcards are supported. * If not set, defaults to /catalog-info.yaml. - * @visibility backend */ path?: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } export interface Config { diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 0385655259..204321f876 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,12 +1,13 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", "description": "A Backstage catalog backend module that helps integrate towards Azure", - "version": "0.1.8-next.1", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,22 +24,24 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "lodash": "^4.17.21", "msw": "^0.47.0", @@ -49,11 +52,13 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/lodash": "^4.14.151" + "@types/lodash": "^4.14.151", + "luxon": "^3.0.0" }, "files": [ - "dist", - "config.d.ts" + "alpha", + "config.d.ts", + "dist" ], "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-azure/src/index.ts b/plugins/catalog-backend-module-azure/src/index.ts index a71c40ee6c..aa3a94fda2 100644 --- a/plugins/catalog-backend-module-azure/src/index.ts +++ b/plugins/catalog-backend-module-azure/src/index.ts @@ -22,3 +22,4 @@ export { AzureDevOpsDiscoveryProcessor } from './processors'; export { AzureDevOpsEntityProvider } from './providers'; +export { azureDevOpsEntityProviderCatalogModule } from './service/AzureDevOpsEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts index c57455d8d6..f7a63f259e 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts @@ -15,7 +15,11 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { CodeSearchResultItem } from '../lib'; @@ -52,6 +56,7 @@ describe('AzureDevOpsEntityProvider', () => { expectedBaseUrl: string, names: Record, integrationConfig?: object, + scheduleInConfig?: boolean, ) => { const config = new ConfigReader({ integrations: { @@ -74,9 +79,18 @@ describe('AzureDevOpsEntityProvider', () => { refresh: jest.fn(), }; + const schedulingConfig: Record = {}; + if (scheduleInConfig) { + schedulingConfig.scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + } else { + schedulingConfig.schedule = schedule; + } + const provider = AzureDevOpsEntityProvider.fromConfig(config, { + ...schedulingConfig, logger, - schedule, })[0]; expect(provider.getProviderName()).toEqual( `AzureDevOpsEntityProvider:${providerId}`, @@ -193,4 +207,92 @@ describe('AzureDevOpsEntityProvider', () => { }, ); }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + azureDevOps: { + test: { + organization: 'myorganization', + project: 'myproject', + }, + }, + }, + }, + }); + + expect(() => + AzureDevOpsEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + azureDevOps: { + test: { + organization: 'myorganization', + project: 'myproject', + }, + }, + }, + }, + }); + + expect(() => + AzureDevOpsEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for AzureDevOpsEntityProvider:test', + ); + }); + + // eslint-disable-next-line jest/expect-expect + it('single simple provider config with schedule in config', async () => { + return expectMutation( + 'allReposMultipleFiles', + { + organization: 'myorganization', + project: 'myproject', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + [ + { + fileName: 'catalog-info.yaml', + path: '/catalog-info.yaml', + repository: { + name: 'myrepo', + }, + }, + { + fileName: 'catalog-info.yaml', + path: '/catalog-info.yaml', + repository: { + name: 'myotherrepo', + }, + }, + ], + 'https://dev.azure.com/myorganization/myproject', + { + 'myrepo?path=/catalog-info.yaml': + 'generated-87865246726bb12a8c4fb4f914443f1fbb91648c', + 'myotherrepo?path=/catalog-info.yaml': + 'generated-2deccac384c34d0dca37be0ebb4b1c8cf6913fe1', + }, + ); + }); }); diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts index aca30f367a..b824d176ee 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AzureIntegration, ScmIntegrations } from '@backstage/integration'; import { @@ -45,11 +45,16 @@ export class AzureDevOpsEntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): AzureDevOpsEntityProvider[] { const providerConfigs = readAzureDevOpsConfigs(configRoot); + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + return providerConfigs.map(providerConfig => { const integration = ScmIntegrations.fromConfig(configRoot).azure.byHost( providerConfig.host, @@ -61,11 +66,21 @@ export class AzureDevOpsEntityProvider implements EntityProvider { ); } + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for AzureDevOpsEntityProvider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + return new AzureDevOpsEntityProvider( providerConfig, integration, options.logger, - options.schedule, + taskRunner, ); }); } @@ -74,19 +89,19 @@ export class AzureDevOpsEntityProvider implements EntityProvider { private readonly config: AzureDevOpsConfig, private readonly integration: AzureIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-azure/src/providers/config.test.ts b/plugins/catalog-backend-module-azure/src/providers/config.test.ts index 236c9a48d4..a09079f1c3 100644 --- a/plugins/catalog-backend-module-azure/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-azure/src/providers/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readAzureDevOpsConfigs } from './config'; describe('readAzureDevOpsConfigs', () => { @@ -33,17 +34,27 @@ describe('readAzureDevOpsConfigs', () => { project: 'myproject', repository: 'service-*', }; + const provider4 = { + organization: 'mycompany', + project: 'myproject', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }; const config = { catalog: { providers: { - azureDevOps: { provider1, provider2, provider3 }, + azureDevOps: { provider1, provider2, provider3, provider4 }, }, }, }; const actual = readAzureDevOpsConfigs(new ConfigReader(config)); - expect(actual).toHaveLength(3); + expect(actual).toHaveLength(4); expect(actual[0]).toEqual({ ...provider1, path: '/catalog-info.yaml', @@ -63,5 +74,16 @@ describe('readAzureDevOpsConfigs', () => { path: '/catalog-info.yaml', id: 'provider3', }); + expect(actual[3]).toEqual({ + ...provider4, + host: 'dev.azure.com', + path: '/catalog-info.yaml', + repository: '*', + id: 'provider4', + schedule: { + ...provider4.schedule, + frequency: Duration.fromISO(provider4.schedule.frequency), + }, + }); }); }); diff --git a/plugins/catalog-backend-module-azure/src/providers/config.ts b/plugins/catalog-backend-module-azure/src/providers/config.ts index 1d143fc882..c84c242488 100644 --- a/plugins/catalog-backend-module-azure/src/providers/config.ts +++ b/plugins/catalog-backend-module-azure/src/providers/config.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AzureDevOpsConfig } from './types'; @@ -42,6 +43,10 @@ function readAzureDevOpsConfig(id: string, config: Config): AzureDevOpsConfig { const repository = config.getOptionalString('repository') || '*'; const path = config.getOptionalString('path') || '/catalog-info.yaml'; + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, host, @@ -49,5 +54,6 @@ function readAzureDevOpsConfig(id: string, config: Config): AzureDevOpsConfig { project, repository, path, + schedule, }; } diff --git a/plugins/catalog-backend-module-azure/src/providers/types.ts b/plugins/catalog-backend-module-azure/src/providers/types.ts index 15ea00ff13..b22a8551a5 100644 --- a/plugins/catalog-backend-module-azure/src/providers/types.ts +++ b/plugins/catalog-backend-module-azure/src/providers/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; + export type AzureDevOpsConfig = { id: string; host: string; @@ -21,4 +23,5 @@ export type AzureDevOpsConfig = { project: string; repository: string; path: string; + schedule?: TaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..e3a0449a8e --- /dev/null +++ b/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { azureDevOpsEntityProviderCatalogModule } from './AzureDevOpsEntityProviderCatalogModule'; +import { AzureDevOpsEntityProvider } from '../providers'; + +describe('azureDevOpsEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + azureDevOps: { + test: { + organization: 'myorganization', + project: 'myproject', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [azureDevOpsEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'AzureDevOpsEntityProvider:test', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..f3621bee23 --- /dev/null +++ b/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + createBackendModule, + loggerToWinstonLogger, + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { AzureDevOpsEntityProvider } from '../providers'; + +/** + * Registers the AzureDevOpsEntityProvider with the catalog processing extension point. + * + * @alpha + */ +export const azureDevOpsEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'azureDevOpsEntityProvider', + register(env) { + env.registerInit({ + deps: { + config: configServiceRef, + catalog: catalogProcessingExtensionPoint, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ config, catalog, logger, scheduler }) { + catalog.addEntityProvider( + AzureDevOpsEntityProvider.fromConfig(config, { + logger: loggerToWinstonLogger(logger), + scheduler, + }), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index bbc999591c..47cc94f257 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.1.4 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/bitbucketCloud/discovery + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/plugin-bitbucket-cloud-common@0.2.0 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + +## 0.1.4-next.2 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/bitbucketCloud/discovery + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 54603b128f..93a9407aa1 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.1.4-next.1", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index ca248ce215..1224da8bdd 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.3-next.0 + +### Patch Changes + +- 68f7f5a857: `BitbucketServerEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/bitbucketServer/discovery + +- cd48ed8370: Add `bitbucketServerEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.1.2-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/api-report.md b/plugins/catalog-backend-module-bitbucket-server/api-report.md index 7d95e148a3..a38a4d8c35 100644 --- a/plugins/catalog-backend-module-bitbucket-server/api-report.md +++ b/plugins/catalog-backend-module-bitbucket-server/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { BitbucketServerIntegrationConfig } from '@backstage/integration'; import { Config } from '@backstage/config'; import { Entity } from '@backstage/catalog-model'; @@ -10,6 +11,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Response as Response_2 } from 'node-fetch'; import { TaskRunner } from '@backstage/backend-tasks'; @@ -55,8 +57,9 @@ export class BitbucketServerEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; parser?: BitbucketServerLocationParser; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): BitbucketServerEntityProvider[]; // (undocumented) @@ -65,6 +68,11 @@ export class BitbucketServerEntityProvider implements EntityProvider { refresh(logger: Logger): Promise; } +// @alpha (undocumented) +export const bitbucketServerEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; + // @public (undocumented) export type BitbucketServerListOptions = { [key: string]: number | undefined; diff --git a/plugins/catalog-backend-module-bitbucket-server/config.d.ts b/plugins/catalog-backend-module-bitbucket-server/config.d.ts index d8a09c92a4..67f694d419 100644 --- a/plugins/catalog-backend-module-bitbucket-server/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-server/config.d.ts @@ -14,11 +14,10 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { - /** - * List of provider-specific options and attributes - */ providers?: { /** * BitbucketServerEntityProvider configuration @@ -48,6 +47,10 @@ export interface Config { */ projectKey?: RegExp; }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } | Record< string, @@ -73,6 +76,10 @@ export interface Config { */ projectKey?: RegExp; }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 2490b79ffa..594b5f0852 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,11 +1,12 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.2-next.1", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,21 +24,23 @@ ], "scripts": { "start": "backstage-cli package start", - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", - "clean": "backstage-cli package clean", "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack" + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@types/node-fetch": "^2.5.12", "node-fetch": "^2.6.7", "uuid": "^8.0.0", @@ -46,11 +49,13 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", + "luxon": "^3.0.0", "msw": "^0.47.0" }, "files": [ - "dist", - "config.d.ts" + "alpha", + "config.d.ts", + "dist" ], "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-bitbucket-server/src/index.ts b/plugins/catalog-backend-module-bitbucket-server/src/index.ts index 34ac3a9966..f139078cfb 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/index.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/index.ts @@ -29,3 +29,4 @@ export type { } from './lib'; export { BitbucketServerEntityProvider } from './providers'; export type { BitbucketServerLocationParser } from './providers'; +export { bitbucketServerEntityProviderCatalogModule } from './service/BitbucketServerEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts index 39689b6c1d..20299e3452 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts @@ -15,14 +15,18 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import { BitbucketServerEntityProvider } from './BitbucketServerEntityProvider'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { BitbucketServerPagedResponse } from '../lib/BitbucketServerClient'; +import { BitbucketServerEntityProvider } from './BitbucketServerEntityProvider'; +import { BitbucketServerPagedResponse } from '../lib'; class PersistingTaskRunner implements TaskRunner { private tasks: TaskInvocationDefinition[] = []; @@ -367,4 +371,163 @@ describe('BitbucketServerEntityProvider', () => { entities: expectedEntities, }); }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketServer: { + host: 'bitbucket.mycompany.com', + }, + }, + }, + integrations: { + bitbucketServer: [ + { + host: 'bitbucket.mycompany.com', + }, + ], + }, + }); + + expect(() => + BitbucketServerEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketServer: { + host: 'bitbucket.mycompany.com', + }, + }, + }, + integrations: { + bitbucketServer: [ + { + host: 'bitbucket.mycompany.com', + }, + ], + }, + }); + + expect(() => + BitbucketServerEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for bitbucketServer-provider:default', + ); + }); + + it('apply full update with schedule in config', async () => { + const host = 'bitbucket.mycompany.com'; + const config = new ConfigReader({ + integrations: { + bitbucketServer: [ + { + host: host, + }, + ], + }, + catalog: { + providers: { + bitbucketServer: { + mainProvider: { + host: host, + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + }, + }, + }, + }); + const schedule = new PersistingTaskRunner(); + const scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + const provider = BitbucketServerEntityProvider.fromConfig(config, { + logger, + scheduler, + })[0]; + expect(provider.getProviderName()).toEqual( + 'bitbucketServer-provider:mainProvider', + ); + + setupStubs( + [ + { key: 'project-test', repos: ['repo-test'] }, + { key: 'other-project', repos: ['other-repo'] }, + ], + `https://${host}`, + ); + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('bitbucketServer-provider:mainProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const expectedEntities = [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:https://${host}/projects/project-test/repos/repo-test/browse/catalog-info.yaml`, + 'backstage.io/managed-by-origin-location': `url:https://${host}/projects/project-test/repos/repo-test/browse/catalog-info.yaml`, + }, + name: 'generated-77f4323822420990f8c3e3c981d38c2dec4ae3a6', + }, + spec: { + presence: 'optional', + target: `https://${host}/projects/project-test/repos/repo-test/browse/catalog-info.yaml`, + type: 'url', + }, + }, + locationKey: 'bitbucketServer-provider:mainProvider', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + 'backstage.io/managed-by-origin-location': `url:https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + }, + name: 'generated-d8d4944c30c2906dfee172ddda9537f9893b2c0f', + }, + spec: { + presence: 'optional', + target: `https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + type: 'url', + }, + }, + locationKey: 'bitbucketServer-provider:mainProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: expectedEntities, + }); + }); }); diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts index 92cecf642a..447b71ee58 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts @@ -14,29 +14,29 @@ * limitations under the License. */ +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { + BitbucketServerIntegration, + ScmIntegrations, +} from '@backstage/integration'; import { EntityProvider, EntityProviderConnection, } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; -import { Config } from '@backstage/config'; -import { TaskRunner } from '@backstage/backend-tasks'; import * as uuid from 'uuid'; -import { - BitbucketServerLocationParser, - defaultBitbucketServerLocationParser, -} from './BitbucketServerLocationParser'; -import { - BitbucketServerIntegration, - ScmIntegrations, -} from '@backstage/integration'; import { BitbucketServerClient, paginated } from '../lib'; -import { InputError } from '@backstage/errors'; import { BitbucketServerEntityProviderConfig, readProviderConfigs, } from './BitbucketServerEntityProviderConfig'; -import { Entity } from '@backstage/catalog-model'; +import { + BitbucketServerLocationParser, + defaultBitbucketServerLocationParser, +} from './BitbucketServerLocationParser'; /** * Discovers catalog files located in Bitbucket Server. @@ -58,12 +58,17 @@ export class BitbucketServerEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; parser?: BitbucketServerLocationParser; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): BitbucketServerEntityProvider[] { const integrations = ScmIntegrations.fromConfig(config); + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + return readProviderConfigs(config).map(providerConfig => { const integration = integrations.bitbucketServer.byHost( providerConfig.host, @@ -73,11 +78,22 @@ export class BitbucketServerEntityProvider implements EntityProvider { `No BitbucketServer integration found that matches host ${providerConfig.host}`, ); } + + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for bitbucketServer-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + return new BitbucketServerEntityProvider( providerConfig, integration, options.logger, - options.schedule, + taskRunner, options.parser, ); }); @@ -87,7 +103,7 @@ export class BitbucketServerEntityProvider implements EntityProvider { config: BitbucketServerEntityProviderConfig, integration: BitbucketServerIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, parser?: BitbucketServerLocationParser, ) { this.integration = integration; @@ -96,13 +112,13 @@ export class BitbucketServerEntityProvider implements EntityProvider { this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts index 712b26cbb0..af8639566f 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readProviderConfigs } from './BitbucketServerEntityProviderConfig'; describe('readProviderConfigs', () => { @@ -63,13 +64,22 @@ describe('readProviderConfigs', () => { host: 'bitbucket2.mycompany.com', catalogPath: 'custom/path/catalog-info.yaml', }, + thirdProvider: { + host: 'bitbucket3.mycompany.com', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, }, }, }, }); const providerConfigs = readProviderConfigs(config); - expect(providerConfigs).toHaveLength(2); + expect(providerConfigs).toHaveLength(3); expect(providerConfigs[0]).toEqual({ id: 'mainProvider', catalogPath: '/catalog-info.yaml', @@ -88,6 +98,21 @@ describe('readProviderConfigs', () => { repoSlug: undefined, }, }); + expect(providerConfigs[2]).toEqual({ + id: 'thirdProvider', + catalogPath: '/catalog-info.yaml', + host: 'bitbucket3.mycompany.com', + filters: { + projectKey: undefined, + repoSlug: undefined, + }, + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + }, + }); }); it('single provider config with filters', () => { diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts index 8a3decab43..b2ed7a8556 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -27,6 +31,7 @@ export type BitbucketServerEntityProviderConfig = { projectKey?: RegExp; repoSlug?: RegExp; }; + schedule?: TaskScheduleDefinition; }; export function readProviderConfigs( @@ -60,6 +65,10 @@ function readProviderConfig( const projectKeyPattern = config.getOptionalString('filters.projectKey'); const repoSlugPattern = config.getOptionalString('filters.repoSlug'); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, host, @@ -68,5 +77,6 @@ function readProviderConfig( projectKey: projectKeyPattern ? new RegExp(projectKeyPattern) : undefined, repoSlug: repoSlugPattern ? new RegExp(repoSlugPattern) : undefined, }, + schedule, }; } diff --git a/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..d2ce4dbd3d --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { bitbucketServerEntityProviderCatalogModule } from './BitbucketServerEntityProviderCatalogModule'; +import { Duration } from 'luxon'; +import { BitbucketServerEntityProvider } from '../providers'; + +describe('bitbucketServerEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketServer: { + host: 'bitbucket.mycompany.com', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + integrations: { + bitbucketServer: [ + { + host: 'bitbucket.mycompany.com', + }, + ], + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [bitbucketServerEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'bitbucketServer-provider:default', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..e89eee7b45 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + configServiceRef, + createBackendModule, + loggerServiceRef, + loggerToWinstonLogger, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { BitbucketServerEntityProvider } from '../providers'; + +/** + * @alpha + */ +export const bitbucketServerEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'bitbucketServerEntityProvider', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: configServiceRef, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ catalog, config, logger, scheduler }) { + const winstonLogger = loggerToWinstonLogger(logger); + const providers = BitbucketServerEntityProvider.fromConfig(config, { + logger: winstonLogger, + scheduler, + }); + + catalog.addEntityProvider(providers); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md index eb9eb97acc..571c18de00 100644 --- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-catalog-backend-module-bitbucket +## 0.2.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.4 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.2.4-next.2 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + ## 0.2.4-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket/api-report.md b/plugins/catalog-backend-module-bitbucket/api-report.md index 34acd6e76d..053c6040a4 100644 --- a/plugins/catalog-backend-module-bitbucket/api-report.md +++ b/plugins/catalog-backend-module-bitbucket/api-report.md @@ -12,7 +12,7 @@ import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import { ScmIntegrationRegistry } from '@backstage/integration'; -// @public (undocumented) +// @public @deprecated (undocumented) export class BitbucketDiscoveryProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrationRegistry; @@ -37,7 +37,7 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { ): Promise; } -// @public +// @public @deprecated export type BitbucketRepositoryParser = (options: { integration: BitbucketIntegration; target: string; diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index ff0594154d..8ee6661b01 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -1,7 +1,8 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket", - "version": "0.2.4-next.1", + "version": "0.2.5-next.0", + "deprecated": true, "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts index 14bf90ee62..6924765483 100644 --- a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts @@ -41,7 +41,10 @@ import { const DEFAULT_BRANCH = 'master'; const DEFAULT_CATALOG_LOCATION = '/catalog-info.yaml'; -/** @public */ +/** + * @public + * @deprecated Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + */ export class BitbucketDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly parser: BitbucketRepositoryParser; @@ -70,6 +73,9 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { this.integrations = options.integrations; this.parser = options.parser || defaultRepositoryParser; this.logger = options.logger; + this.logger.warn( + 'Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead.', + ); } getProcessorName(): string { diff --git a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts b/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts index 57a162e1e4..97225e2579 100644 --- a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts +++ b/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts @@ -26,6 +26,7 @@ import { Logger } from 'winston'; * results. * * @public + * @deprecated Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. */ export type BitbucketRepositoryParser = (options: { integration: BitbucketIntegration; diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 8e1b11c1f7..a6babaf4fc 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.6-next.0 + +### Patch Changes + +- 4fba50f5d4: Add `gerritEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- 134b69f478: `GerritEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/gerrit/discovery + +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.1.5-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/api-report.md b/plugins/catalog-backend-module-gerrit/api-report.md index 7eae94c2a8..45847ea93d 100644 --- a/plugins/catalog-backend-module-gerrit/api-report.md +++ b/plugins/catalog-backend-module-gerrit/api-report.md @@ -3,10 +3,12 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; // @public (undocumented) @@ -18,7 +20,8 @@ export class GerritEntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GerritEntityProvider[]; // (undocumented) @@ -27,5 +30,10 @@ export class GerritEntityProvider implements EntityProvider { refresh(logger: Logger): Promise; } +// @alpha (undocumented) +export const gerritEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/catalog-backend-module-gerrit/config.d.ts b/plugins/catalog-backend-module-gerrit/config.d.ts index 744917a198..d8b48d2967 100644 --- a/plugins/catalog-backend-module-gerrit/config.d.ts +++ b/plugins/catalog-backend-module-gerrit/config.d.ts @@ -30,19 +30,16 @@ export interface Config { { /** * (Required) The host of the Gerrit integration to use. - * @visibility backend */ host: string; /** * (Required) The query to use for the "List Projects" API call. Used to limit the * scope of the projects that the provider tries to ingest. - * @visibility backend */ query: string; /** * (Optional) Branch. * The branch where the provider will try to find entities. Defaults to "master". - * @visibility backend */ branch?: string; } diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index e3665a96ae..c6d342640f 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,11 +1,12 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.5-next.1", + "version": "0.1.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -20,21 +21,23 @@ }, "scripts": { "start": "backstage-cli package start", - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", - "clean": "backstage-cli package clean", "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack" + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "fs-extra": "10.1.0", "msw": "^0.47.0", "node-fetch": "^2.6.7", @@ -44,7 +47,8 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/fs-extra": "^9.0.1" + "@types/fs-extra": "^9.0.1", + "luxon": "^3.0.0" }, "files": [ "dist", diff --git a/plugins/catalog-backend-module-gerrit/src/index.ts b/plugins/catalog-backend-module-gerrit/src/index.ts index 133772d4b1..38ede69926 100644 --- a/plugins/catalog-backend-module-gerrit/src/index.ts +++ b/plugins/catalog-backend-module-gerrit/src/index.ts @@ -15,3 +15,4 @@ */ export { GerritEntityProvider } from './providers/GerritEntityProvider'; +export { gerritEntityProviderCatalogModule } from './service/GerritEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts index d6ad0ff668..5d258ee94f 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts @@ -15,14 +15,18 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; -import { rest } from 'msw'; import fs from 'fs-extra'; -import path from 'path'; +import { rest } from 'msw'; import { setupServer } from 'msw/node'; +import path from 'path'; import { GerritEntityProvider } from './GerritEntityProvider'; const server = setupServer(); @@ -207,4 +211,93 @@ describe('GerritEntityProvider', () => { }), ).toThrow(/No gerrit integration/); }); + + it('fail without schedule and scheduler', () => { + expect(() => + GerritEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + expect(() => + GerritEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for gerrit-provider:active-training', + ); + }); + + it('discovers projects from the api with schedule in config', async () => { + const configWithSchedule = new ConfigReader({ + catalog: { + providers: { + gerrit: { + 'active-training': { + host: 'g.com', + query: 'state=ACTIVE&prefix=training', + branch: 'main', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + }, + }, + }, + integrations: { + gerrit: [ + { + host: 'g.com', + baseUrl: 'https://g.com/gerrit', + gitilesBaseUrl: 'https:/g.com/gitiles', + }, + ], + }, + }); + const scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + + const repoBuffer = fs.readFileSync( + path.resolve(__dirname, '__fixtures__/listProjectsBody.txt'), + ); + const expected = getJsonFixture('expectedProviderEntities.json'); + + server.use( + rest.get('https://g.com/gerrit/projects/', (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.body(repoBuffer), + ), + ), + ); + + const provider = GerritEntityProvider.fromConfig(configWithSchedule, { + logger, + scheduler, + })[0]; + expect(provider.getProviderName()).toEqual( + 'gerrit-provider:active-training', + ); + + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('gerrit-provider:active-training:refresh'); + await (taskDef.fn as () => Promise)(); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith( + expected, + ); + }); }); diff --git a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts index beb4052073..ec08dbe4be 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { @@ -49,9 +49,14 @@ export class GerritEntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GerritEntityProvider[] { + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + const providerConfigs = readGerritConfigs(configRoot); const integrations = ScmIntegrations.fromConfig(configRoot).gerrit; const providers: GerritEntityProvider[] = []; @@ -63,12 +68,23 @@ export class GerritEntityProvider implements EntityProvider { `No gerrit integration found that matches host ${providerConfig.host}`, ); } + + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for gerrit-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + providers.push( new GerritEntityProvider( providerConfig, integration, options.logger, - options.schedule, + taskRunner, ), ); }); @@ -79,14 +95,14 @@ export class GerritEntityProvider implements EntityProvider { config: GerritProviderConfig, integration: GerritIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.config = config; this.integration = integration; this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); } getProviderName(): string { @@ -98,10 +114,10 @@ export class GerritEntityProvider implements EntityProvider { await this.scheduleFn(); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-gerrit/src/providers/config.test.ts b/plugins/catalog-backend-module-gerrit/src/providers/config.test.ts index 0422b4e4ea..ecf68d9f3c 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readGerritConfigs } from './config'; describe('readGerritConfigs', () => { @@ -29,12 +30,24 @@ describe('readGerritConfigs', () => { query: 'state=ACTIVE', branch: 'main', }; + const provider3 = { + host: 'gerrit1.com', + query: 'state=ACTIVE', + branch: 'main', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }; const config = { catalog: { providers: { gerrit: { 'active-g1': provider1, 'active-g2': provider2, + 'active-g3': provider3, }, }, }, @@ -42,10 +55,19 @@ describe('readGerritConfigs', () => { const actual = readGerritConfigs(new ConfigReader(config)); - expect(actual).toHaveLength(2); + expect(actual).toHaveLength(3); expect(actual[0]).toEqual({ ...provider1, id: 'active-g1' }); expect(actual[1]).toEqual({ ...provider2, id: 'active-g2' }); + expect(actual[2]).toEqual({ + ...provider3, + id: 'active-g3', + schedule: { + ...provider3.schedule, + frequency: Duration.fromISO(provider3.schedule.frequency), + }, + }); }); + it('provides default values', () => { const provider = { host: 'gerrit1.com', diff --git a/plugins/catalog-backend-module-gerrit/src/providers/config.ts b/plugins/catalog-backend-module-gerrit/src/providers/config.ts index 7a28ddd8ea..dd7ee9ec43 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/config.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/config.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GerritProviderConfig } from './types'; @@ -22,11 +23,16 @@ function readGerritConfig(id: string, config: Config): GerritProviderConfig { const host = config.getString('host'); const query = config.getString('query'); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { branch, host, id, query, + schedule, }; } diff --git a/plugins/catalog-backend-module-gerrit/src/providers/types.ts b/plugins/catalog-backend-module-gerrit/src/providers/types.ts index 1e96bd1ee4..4a8329df86 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/types.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; + export type GerritProjectInfo = { id: string; name: string; @@ -28,4 +30,5 @@ export type GerritProviderConfig = { query: string; id: string; branch?: string; + schedule?: TaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..53e012209b --- /dev/null +++ b/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { gerritEntityProviderCatalogModule } from './GerritEntityProviderCatalogModule'; +import { GerritEntityProvider } from '../providers/GerritEntityProvider'; + +describe('gerritEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + gerrit: { + test: { + host: 'g.com', + query: 'state=ACTIVE&prefix=training', + branch: 'main', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + integrations: { + gerrit: [ + { + host: 'g.com', + baseUrl: 'https://g.com/gerrit', + gitilesBaseUrl: 'https:/g.com/gitiles', + }, + ], + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [gerritEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'gerrit-provider:test', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..32d582e397 --- /dev/null +++ b/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.ts @@ -0,0 +1,52 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + configServiceRef, + createBackendModule, + loggerServiceRef, + loggerToWinstonLogger, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { GerritEntityProvider } from '../providers/GerritEntityProvider'; + +/** + * @alpha + */ +export const gerritEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'gerritEntityProvider', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: configServiceRef, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ catalog, config, logger, scheduler }) { + const winstonLogger = loggerToWinstonLogger(logger); + const providers = GerritEntityProvider.fromConfig(config, { + logger: winstonLogger, + scheduler, + }); + + catalog.addEntityProvider(providers); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 1a7a4a512c..b89231a422 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,116 @@ # @backstage/plugin-catalog-backend-module-github +## 0.2.0-next.0 + +### Minor Changes + +- 67fe5bc9a9: BREAKING: Support authenticated backends by including a server token for catalog requests. The constructor of `GithubLocationAnalyzer` now requires an instance of `TokenManager` to be supplied: + + ```diff + ... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + + tokenManager: env.tokenManager, + }), + ); + ... + ``` + +- f64d66a45c: Added the ability for the GitHub discovery provider to validate that catalog files exist before emitting them. + + Users can now set the `validateLocationsExist` property to `true` in their GitHub discovery configuration to opt in to this feature. + This feature only works with `catalogPath`s that do not contain wildcards. + + When `validateLocationsExist` is set to `true`, the GitHub discovery provider will retrieve the object from the + repository at the provided `catalogPath`. + If this file exists and is non-empty, then it will be emitted as a location for further processing. + If this file does not exist or is empty, then it will not be emitted. + Not emitting locations that do not exist allows for far fewer calls to the GitHub API to validate locations that do not exist. + +### Patch Changes + +- 67fe5bc9a9: Properly derive Github credentials when making requests in `GithubLocationAnalyzer` to support Github App authentication +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.8 + +### Patch Changes + +- 8749df3d02: `GitHubEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/github/discovery + +- 7022aebf35: Added `GithubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +- 51046b58b0: Use schedule from config at backend module. + + Also, it removes `GithubEntityProviderCatalogModuleOptions` + in favor of config-only for the backend module setup + like at other similar modules. + +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. +- be9474b103: Replaces in-code uses of `GitHub` by `Github` and deprecates old versions. + + Deprecates + + - `GitHubEntityProvider` replaced by `GithubEntityProvider` + - `GitHubLocationAnalyzer` replaced by `GithubLocationAnalyzer` + - `GitHubLocationAnalyzerOptions` replaced by `GithubLocationAnalyzerOptions` + - `GitHubOrgEntityProvider` replaced by `GithubOrgEntityProvider` + - `GitHubOrgEntityProviderOptions` replaced by `GithubOrgEntityProviderOptions` + + Renames + + - `GitHubLocationAnalyzer` to `GithubLocationAnalyzer` + - `GitHubLocationAnalyzerOptions` to `GithubLocationAnalyzerOptions` + +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.8-next.2 + +### Patch Changes + +- 7022aebf35: Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index c89a34c259..a9587e172b 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -12,14 +12,15 @@ import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { GithubCredentialsProvider } from '@backstage/integration'; -import { GitHubIntegrationConfig } from '@backstage/integration'; +import { GithubIntegrationConfig } from '@backstage/integration'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend'; import { TaskRunner } from '@backstage/backend-tasks'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; +import { TokenManager } from '@backstage/backend-common'; // @public export class GithubDiscoveryProcessor implements CatalogProcessor { @@ -46,7 +47,7 @@ export class GithubDiscoveryProcessor implements CatalogProcessor { ): Promise; } -// @public +// @public @deprecated (undocumented) export class GitHubEntityProvider implements EntityProvider { // (undocumented) connect(connection: EntityProviderConnection): Promise; @@ -55,7 +56,8 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GitHubEntityProvider[]; // (undocumented) @@ -64,19 +66,33 @@ export class GitHubEntityProvider implements EntityProvider { refresh(logger: Logger): Promise; } +// @public +export class GithubEntityProvider implements EntityProvider { + // (undocumented) + connect(connection: EntityProviderConnection): Promise; + // (undocumented) + static fromConfig( + config: Config, + options: { + logger: Logger; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; + }, + ): GithubEntityProvider[]; + // (undocumented) + getProviderName(): string; + // (undocumented) + refresh(logger: Logger): Promise; +} + // @alpha export const githubEntityProviderCatalogModule: ( - options?: GithubEntityProviderCatalogModuleOptions | undefined, + options?: undefined, ) => BackendFeature; -// @alpha -export type GithubEntityProviderCatalogModuleOptions = { - schedule?: TaskScheduleDefinition; -}; - // @public (undocumented) -export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { - constructor(options: GitHubLocationAnalyzerOptions); +export class GithubLocationAnalyzer implements ScmLocationAnalyzer { + constructor(options: GithubLocationAnalyzerOptions); // (undocumented) analyze({ url, catalogFilename }: AnalyzeOptions): Promise<{ existing: { @@ -93,9 +109,10 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { } // @public (undocumented) -export type GitHubLocationAnalyzerOptions = { +export type GithubLocationAnalyzerOptions = { config: Config; discovery: PluginEndpointDiscovery; + tokenManager: TokenManager; }; // @public @@ -131,12 +148,21 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { ): Promise; } +// @public @deprecated (undocumented) +export class GitHubOrgEntityProvider extends GithubOrgEntityProvider { + // (undocumented) + static fromConfig( + config: Config, + options: GitHubOrgEntityProviderOptions, + ): GitHubOrgEntityProvider; +} + // @public -export class GitHubOrgEntityProvider implements EntityProvider { +export class GithubOrgEntityProvider implements EntityProvider { constructor(options: { id: string; orgUrl: string; - gitHubConfig: GitHubIntegrationConfig; + gitHubConfig: GithubIntegrationConfig; logger: Logger; githubCredentialsProvider?: GithubCredentialsProvider; }); @@ -145,15 +171,18 @@ export class GitHubOrgEntityProvider implements EntityProvider { // (undocumented) static fromConfig( config: Config, - options: GitHubOrgEntityProviderOptions, - ): GitHubOrgEntityProvider; + options: GithubOrgEntityProviderOptions, + ): GithubOrgEntityProvider; // (undocumented) getProviderName(): string; read(options?: { logger?: Logger }): Promise; } +// @public @deprecated (undocumented) +export type GitHubOrgEntityProviderOptions = GithubOrgEntityProviderOptions; + // @public -export interface GitHubOrgEntityProviderOptions { +export interface GithubOrgEntityProviderOptions { githubCredentialsProvider?: GithubCredentialsProvider; id: string; logger: Logger; diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index 1dcd49ea4e..691caedc46 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { processors?: { @@ -48,7 +50,7 @@ export interface Config { providers?: { /** - * GitHubEntityProvider configuration + * GithubEntityProvider configuration * * Uses "default" as default id for the single config variant. */ @@ -103,6 +105,10 @@ export interface Config { exclude?: string[]; }; }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } | Record< string, @@ -156,6 +162,10 @@ export interface Config { exclude?: string[]; }; }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 1fb82a648a..60185636ea 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-github", "description": "A Backstage catalog backend module that helps integrate towards GitHub", - "version": "0.1.8-next.1", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -56,7 +56,8 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/lodash": "^4.14.151" + "@types/lodash": "^4.14.151", + "luxon": "^3.0.0" }, "files": [ "dist", diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts similarity index 88% rename from plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts rename to plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts index 0dcc131502..549ca385e8 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.test.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts @@ -32,8 +32,11 @@ jest.mock('@octokit/rest', () => { return { Octokit }; }); -import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer'; +import { + PluginEndpointDiscovery, + TokenManager, +} from '@backstage/backend-common'; +import { GithubLocationAnalyzer } from './GithubLocationAnalyzer'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; @@ -41,11 +44,15 @@ import { ConfigReader } from '@backstage/config'; const server = setupServer(); -describe('GitHubLocationAnalyzer', () => { +describe('GithubLocationAnalyzer', () => { const mockDiscoveryApi: jest.Mocked = { getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), getExternalBaseUrl: jest.fn(), }; + const mockTokenManager: jest.Mocked = { + authenticate: jest.fn(), + getToken: jest.fn().mockResolvedValue('abc123'), + }; const config = new ConfigReader({ integrations: { github: [ @@ -114,9 +121,10 @@ describe('GitHubLocationAnalyzer', () => { return Promise.reject(); }); - const analyzer = new GitHubLocationAnalyzer({ + const analyzer = new GithubLocationAnalyzer({ discovery: mockDiscoveryApi, config, + tokenManager: mockTokenManager, }); const result = await analyzer.analyze({ url: 'https://github.com/foo/bar', @@ -139,9 +147,10 @@ describe('GitHubLocationAnalyzer', () => { return Promise.reject(); }); - const analyzer = new GitHubLocationAnalyzer({ + const analyzer = new GithubLocationAnalyzer({ discovery: mockDiscoveryApi, config, + tokenManager: mockTokenManager, }); const result = await analyzer.analyze({ url: 'https://github.com/foo/bar', diff --git a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts similarity index 66% rename from plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts rename to plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts index d1112d2cc0..a740fd7706 100644 --- a/plugins/catalog-backend-module-github/src/analyzers/GitHubLocationAnalyzer.ts +++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts @@ -15,7 +15,12 @@ */ import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; -import { ScmIntegrations } from '@backstage/integration'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrationRegistry, + ScmIntegrations, +} from '@backstage/integration'; import { Octokit } from '@octokit/rest'; import { trimEnd } from 'lodash'; import parseGitUrl from 'git-url-parse'; @@ -23,28 +28,39 @@ import { AnalyzeOptions, ScmLocationAnalyzer, } from '@backstage/plugin-catalog-backend'; -import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { + PluginEndpointDiscovery, + TokenManager, +} from '@backstage/backend-common'; import { Config } from '@backstage/config'; /** @public */ -export type GitHubLocationAnalyzerOptions = { +export type GithubLocationAnalyzerOptions = { config: Config; discovery: PluginEndpointDiscovery; + tokenManager: TokenManager; }; -/** @public */ -export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { - private readonly catalogClient: CatalogApi; - private readonly config: Config; - constructor(options: GitHubLocationAnalyzerOptions) { - this.config = options.config; +/** @public */ +export class GithubLocationAnalyzer implements ScmLocationAnalyzer { + private readonly catalogClient: CatalogApi; + private readonly githubCredentialsProvider: GithubCredentialsProvider; + private readonly integrations: ScmIntegrationRegistry; + private readonly tokenManager: TokenManager; + + constructor(options: GithubLocationAnalyzerOptions) { this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); + this.integrations = ScmIntegrations.fromConfig(options.config); + this.githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(this.integrations); + this.tokenManager = options.tokenManager; } + supports(url: string) { - const integrations = ScmIntegrations.fromConfig(this.config); - const integration = integrations.byUrl(url); + const integration = this.integrations.byUrl(url); return integration?.type === 'github'; } + async analyze({ url, catalogFilename }: AnalyzeOptions) { const { owner, name: repo } = parseGitUrl(url); @@ -52,15 +68,18 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { const query = `filename:${catalogFile} repo:${owner}/${repo}`; - const integration = ScmIntegrations.fromConfig(this.config).github.byUrl( - url, - ); + const integration = this.integrations.github.byUrl(url); if (!integration) { throw new Error('Make sure you have a GitHub integration configured'); } + const { token: githubToken } = + await this.githubCredentialsProvider.getCredentials({ + url, + }); + const octokitClient = new Octokit({ - auth: integration.config.token, + auth: githubToken, baseUrl: integration.config.apiBaseUrl, }); @@ -79,15 +98,20 @@ export class GitHubLocationAnalyzer implements ScmLocationAnalyzer { }); const defaultBranch = repoInformation.data.default_branch; + const { token: serviceToken } = await this.tokenManager.getToken(); + const result = await Promise.all( searchResult.data.items .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) .map(async target => { - const addLocationResult = await this.catalogClient.addLocation({ - type: 'url', - target, - dryRun: true, - }); + const addLocationResult = await this.catalogClient.addLocation( + { + type: 'url', + target, + dryRun: true, + }, + { token: serviceToken }, + ); return addLocationResult.entities.map(e => ({ location: { type: 'url', target }, isRegistered: !!addLocationResult.exists, diff --git a/plugins/catalog-backend-module-github/src/deprecated.ts b/plugins/catalog-backend-module-github/src/deprecated.ts new file mode 100644 index 0000000000..d094bb4031 --- /dev/null +++ b/plugins/catalog-backend-module-github/src/deprecated.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { Config } from '@backstage/config'; +import { + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-backend'; +import { Logger } from 'winston'; +import { GithubEntityProvider } from './providers/GithubEntityProvider'; +import { + GithubOrgEntityProvider, + GithubOrgEntityProviderOptions, +} from './providers/GithubOrgEntityProvider'; + +/** + * @public + * @deprecated Use {@link GithubOrgEntityProvider} instead. + */ +export class GitHubOrgEntityProvider extends GithubOrgEntityProvider { + static fromConfig(config: Config, options: GitHubOrgEntityProviderOptions) { + options.logger.warn( + '[Deprecated] Use GithubOrgEntityProvider instead of GitHubOrgEntityProvider.', + ); + return GithubOrgEntityProvider.fromConfig( + config, + options as GithubOrgEntityProviderOptions, + ) as GitHubOrgEntityProvider; + } +} + +/** + * @public + * @deprecated Use {@link GithubOrgEntityProviderOptions} instead. + */ +export type GitHubOrgEntityProviderOptions = GithubOrgEntityProviderOptions; + +/** + * @public + * @deprecated Use {@link GithubEntityProvider} instead. + */ +export class GitHubEntityProvider implements EntityProvider { + static fromConfig( + config: Config, + options: { + logger: Logger; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; + }, + ): GitHubEntityProvider[] { + options.logger.warn( + '[Deprecated] Please use GithubEntityProvider instead of GitHubEntityProvider.', + ); + return GithubEntityProvider.fromConfig(config, options).map( + delegate => new GitHubEntityProvider(delegate), + ); + } + + private constructor(private readonly delegate: GithubEntityProvider) {} + + connect(connection: EntityProviderConnection): Promise { + return this.delegate.connect(connection); + } + + getProviderName(): string { + return this.delegate.getProviderName(); + } + + refresh(logger: Logger): Promise { + return this.delegate.refresh(logger); + } +} diff --git a/plugins/catalog-backend-module-github/src/index.ts b/plugins/catalog-backend-module-github/src/index.ts index ca7c72f11b..f38035db15 100644 --- a/plugins/catalog-backend-module-github/src/index.ts +++ b/plugins/catalog-backend-module-github/src/index.ts @@ -20,14 +20,15 @@ * @packageDocumentation */ +export { GithubLocationAnalyzer } from './analyzers/GithubLocationAnalyzer'; +export type { GithubLocationAnalyzerOptions } from './analyzers/GithubLocationAnalyzer'; +export type { GithubMultiOrgConfig } from './lib'; export { GithubDiscoveryProcessor } from './processors/GithubDiscoveryProcessor'; export { GithubMultiOrgReaderProcessor } from './processors/GithubMultiOrgReaderProcessor'; export { GithubOrgReaderProcessor } from './processors/GithubOrgReaderProcessor'; -export { GitHubEntityProvider } from './providers/GitHubEntityProvider'; -export { GitHubOrgEntityProvider } from './providers/GitHubOrgEntityProvider'; -export type { GitHubOrgEntityProviderOptions } from './providers/GitHubOrgEntityProvider'; -export type { GithubMultiOrgConfig } from './lib'; -export { githubEntityProviderCatalogModule } from './module'; -export type { GithubEntityProviderCatalogModuleOptions } from './module'; -export { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer'; -export type { GitHubLocationAnalyzerOptions } from './analyzers/GitHubLocationAnalyzer'; +export { GithubEntityProvider } from './providers/GithubEntityProvider'; +export { GithubOrgEntityProvider } from './providers/GithubOrgEntityProvider'; +export type { GithubOrgEntityProviderOptions } from './providers/GithubOrgEntityProvider'; +export { githubEntityProviderCatalogModule } from './service/GithubEntityProviderCatalogModule'; + +export * from './deprecated'; diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts index e2303c16e6..25878c2005 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts @@ -213,6 +213,7 @@ describe('github', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'demo', @@ -222,6 +223,11 @@ describe('github', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'acb123', + text: 'some yaml', + }, }, ], pageInfo: { @@ -243,6 +249,7 @@ describe('github', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'demo', @@ -252,6 +259,11 @@ describe('github', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'acb123', + text: 'some yaml', + }, }, ], }; @@ -262,9 +274,9 @@ describe('github', () => { ), ); - await expect(getOrganizationRepositories(graphql, 'a')).resolves.toEqual( - output, - ); + await expect( + getOrganizationRepositories(graphql, 'a', 'catalog-info.yaml'), + ).resolves.toEqual(output); }); }); }); diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index 6a944af330..ef6a7f817a 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -65,6 +65,11 @@ export type Repository = { defaultBranchRef: { name: string; } | null; + catalogInfoFile: { + __typename: string; + id: string; + text: string; + } | null; }; type RepositoryTopics = { @@ -266,14 +271,30 @@ export async function getOrganizationTeams( export async function getOrganizationRepositories( client: typeof graphql, org: string, + catalogPath: string, ): Promise<{ repositories: Repository[] }> { + let relativeCatalogPathRef: string; + // We must strip the leading slash or the query for objects does not work + if (catalogPath.startsWith('/')) { + relativeCatalogPathRef = catalogPath.substring(1); + } else { + relativeCatalogPathRef = catalogPath; + } + const catalogPathRef = `HEAD:${relativeCatalogPathRef}`; const query = ` - query repositories($org: String!, $cursor: String) { + query repositories($org: String!, $catalogPathRef: String!, $cursor: String) { repositoryOwner(login: $org) { login repositories(first: 100, after: $cursor) { nodes { name + catalogInfoFile: object(expression: $catalogPathRef) { + __typename + ... on Blob { + id + text + } + } url isArchived repositoryTopics(first: 100) { @@ -302,7 +323,7 @@ export async function getOrganizationRepositories( query, r => r.repositoryOwner?.repositories, x => x, - { org }, + { org, catalogPathRef }, ); return { repositories }; diff --git a/plugins/catalog-backend-module-github/src/lib/index.ts b/plugins/catalog-backend-module-github/src/lib/index.ts index 26ec3e8516..6402b69121 100644 --- a/plugins/catalog-backend-module-github/src/lib/index.ts +++ b/plugins/catalog-backend-module-github/src/lib/index.ts @@ -22,4 +22,4 @@ export { getOrganizationUsers, } from './github'; export { assignGroupsToUsers, buildOrgHierarchy } from './org'; -export { parseGitHubOrgUrl } from './util'; +export { parseGithubOrgUrl } from './util'; diff --git a/plugins/catalog-backend-module-github/src/lib/util.test.ts b/plugins/catalog-backend-module-github/src/lib/util.test.ts index c530d2bcc9..7aca6839ea 100644 --- a/plugins/catalog-backend-module-github/src/lib/util.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/util.test.ts @@ -14,17 +14,17 @@ * limitations under the License. */ -import { GithubTopicFilters } from '../providers/GitHubEntityProviderConfig'; -import { parseGitHubOrgUrl, satisfiesTopicFilter } from './util'; +import { GithubTopicFilters } from '../providers/GithubEntityProviderConfig'; +import { parseGithubOrgUrl, satisfiesTopicFilter } from './util'; -describe('parseGitHubOrgUrl', () => { +describe('parseGithubOrgUrl', () => { it('only supports clean org urls, and decodes them', () => { - expect(() => parseGitHubOrgUrl('https://github.com')).toThrow(); - expect(() => parseGitHubOrgUrl('https://github.com/org/foo')).toThrow(); + expect(() => parseGithubOrgUrl('https://github.com')).toThrow(); + expect(() => parseGithubOrgUrl('https://github.com/org/foo')).toThrow(); expect(() => - parseGitHubOrgUrl('https://github.com/org/foo/teams'), + parseGithubOrgUrl('https://github.com/org/foo/teams'), ).toThrow(); - expect(parseGitHubOrgUrl('https://github.com/foo%32')).toEqual({ + expect(parseGithubOrgUrl('https://github.com/foo%32')).toEqual({ org: 'foo2', }); }); diff --git a/plugins/catalog-backend-module-github/src/lib/util.ts b/plugins/catalog-backend-module-github/src/lib/util.ts index 238ed9868a..ece463bb40 100644 --- a/plugins/catalog-backend-module-github/src/lib/util.ts +++ b/plugins/catalog-backend-module-github/src/lib/util.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { GithubTopicFilters } from '../providers/GitHubEntityProviderConfig'; +import { GithubTopicFilters } from '../providers/GithubEntityProviderConfig'; -export function parseGitHubOrgUrl(urlString: string): { org: string } { +export function parseGithubOrgUrl(urlString: string): { org: string } { const path = new URL(urlString).pathname.substr(1).split('/'); // /backstage diff --git a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts index c915e8b136..b69728cc62 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.test.ts @@ -153,6 +153,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'master', }, + catalogInfoFile: null, }, { name: 'demo', @@ -162,6 +163,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, ], }); @@ -203,6 +205,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, ], }); @@ -234,6 +237,7 @@ describe('GithubDiscoveryProcessor', () => { repositoryTopics: { nodes: [] }, isArchived: false, defaultBranchRef: null, + catalogInfoFile: null, }, ], }); @@ -259,6 +263,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'master', }, + catalogInfoFile: null, }, ], }); @@ -293,6 +298,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'techdocs-cli', @@ -302,6 +308,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'techdocs-container', @@ -311,6 +318,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'techdocs-durp', @@ -318,6 +326,7 @@ describe('GithubDiscoveryProcessor', () => { repositoryTopics: { nodes: [] }, isArchived: false, defaultBranchRef: null, + catalogInfoFile: null, }, ], }); @@ -360,6 +369,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'test', @@ -369,6 +379,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'test-archived', @@ -378,6 +389,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, { name: 'testxyz', @@ -387,6 +399,7 @@ describe('GithubDiscoveryProcessor', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: null, }, ], }); diff --git a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts index 97c77bf033..593f5f72f1 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubDiscoveryProcessor.ts @@ -121,7 +121,11 @@ export class GithubDiscoveryProcessor implements CatalogProcessor { const startTimestamp = Date.now(); this.logger.info(`Reading GitHub repositories from ${location.target}`); - const { repositories } = await getOrganizationRepositories(client, org); + const { repositories } = await getOrganizationRepositories( + client, + org, + catalogPath, + ); const matching = repositories.filter( r => !r.isArchived && repoSearchPath.test(r.name), ); diff --git a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts index c5315ad166..7bb73990b3 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubMultiOrgReaderProcessor.ts @@ -19,7 +19,7 @@ import { DefaultGithubCredentialsProvider, GithubAppCredentialsMux, GithubCredentialsProvider, - GitHubIntegrationConfig, + GithubIntegrationConfig, ScmIntegrationRegistry, ScmIntegrations, } from '@backstage/integration'; @@ -182,7 +182,7 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { // Note: Does not support usage of PATs private async getAllOrgs( - gitHubConfig: GitHubIntegrationConfig, + gitHubConfig: GithubIntegrationConfig, ): Promise { const githubAppMux = new GithubAppCredentialsMux(gitHubConfig); const installs = await githubAppMux.getAllInstallations(); diff --git a/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts index c6f2f5d0e2..0ef81ac907 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts @@ -35,7 +35,7 @@ import { buildOrgHierarchy, getOrganizationTeams, getOrganizationUsers, - parseGitHubOrgUrl, + parseGithubOrgUrl, } from '../lib'; type GraphQL = typeof graphql; @@ -45,7 +45,7 @@ type GraphQL = typeof graphql; * * @remarks * - * Consider using {@link GitHubOrgEntityProvider} instead. + * Consider using {@link GithubOrgEntityProvider} instead. * * @public */ @@ -94,7 +94,7 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { } const { client, tokenType } = await this.createClient(location.target); - const { org } = parseGitHubOrgUrl(location.target); + const { org } = parseGithubOrgUrl(location.target); // Read out all of the raw data const startTimestamp = Date.now(); diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts similarity index 52% rename from plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts rename to plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index 63fb77288b..cc9a9bfee3 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -15,10 +15,14 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; -import { GitHubEntityProvider } from './GitHubEntityProvider'; +import { GithubEntityProvider } from './GithubEntityProvider'; import * as helpers from '../lib/github'; jest.mock('../lib/github', () => { @@ -41,13 +45,13 @@ class PersistingTaskRunner implements TaskRunner { const logger = getVoidLogger(); -describe('GitHubEntityProvider', () => { +describe('GithubEntityProvider', () => { afterEach(() => jest.resetAllMocks()); it('no provider config', () => { const schedule = new PersistingTaskRunner(); const config = new ConfigReader({}); - const providers = GitHubEntityProvider.fromConfig(config, { + const providers = GithubEntityProvider.fromConfig(config, { logger, schedule, }); @@ -66,7 +70,7 @@ describe('GitHubEntityProvider', () => { }, }, }); - const providers = GitHubEntityProvider.fromConfig(config, { + const providers = GithubEntityProvider.fromConfig(config, { logger, schedule, }); @@ -89,7 +93,7 @@ describe('GitHubEntityProvider', () => { }); expect(() => - GitHubEntityProvider.fromConfig(config, { + GithubEntityProvider.fromConfig(config, { logger, schedule, }), @@ -112,7 +116,7 @@ describe('GitHubEntityProvider', () => { }, }, }); - const providers = GitHubEntityProvider.fromConfig(config, { + const providers = GithubEntityProvider.fromConfig(config, { logger, schedule, }); @@ -149,7 +153,7 @@ describe('GitHubEntityProvider', () => { refresh: jest.fn(), }; - const provider = GitHubEntityProvider.fromConfig(config, { + const provider = GithubEntityProvider.fromConfig(config, { logger, schedule, })[0]; @@ -170,6 +174,11 @@ describe('GitHubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), @@ -237,7 +246,7 @@ describe('GitHubEntityProvider', () => { refresh: jest.fn(), }; - const provider = GitHubEntityProvider.fromConfig(config, { + const provider = GithubEntityProvider.fromConfig(config, { logger, schedule, })[0]; @@ -267,6 +276,11 @@ describe('GitHubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), @@ -311,7 +325,7 @@ describe('GitHubEntityProvider', () => { refresh: jest.fn(), }; - const provider = GitHubEntityProvider.fromConfig(config, { + const provider = GithubEntityProvider.fromConfig(config, { logger, schedule, })[0]; @@ -341,6 +355,11 @@ describe('GitHubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), @@ -381,132 +400,320 @@ describe('GitHubEntityProvider', () => { entities: expectedEntities, }); }); -}); -it('apply full update on scheduled execution with topic exclusion taking priority over topic inclusion', async () => { - const config = new ConfigReader({ - catalog: { - providers: { - github: { - myProvider: { - organization: 'test-org', - catalogPath: 'custom/path/catalog-custom.yaml', - filters: { - branch: 'main', - repository: 'test-.*', - topic: { - exclude: ['backstage-exclude'], - include: ['backstage-include'], + it('should filter out invalid locations when validateLocationsExist is set to true', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + myProvider: { + organization: 'test-org', + catalogPath: 'catalog-custom.yaml', + filters: { + branch: 'main', + }, + validateLocationsExist: true, + }, + }, + }, + }, + }); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + const provider = GithubEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + const mockGetOrganizationRepositories = jest.spyOn( + helpers, + 'getOrganizationRepositories', + ); + + mockGetOrganizationRepositories.mockReturnValue( + Promise.resolve({ + repositories: [ + { + name: 'test-repo', + url: 'https://github.com/test-org/test-repo', + repositoryTopics: { + nodes: [], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + catalogInfoFile: null, + }, + { + name: 'another-repo', + url: 'https://github.com/test-org/another-repo', + repositoryTopics: { + nodes: [], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, + }, + ], + }), + ); + + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('github-provider:myProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const url = `https://github.com/test-org/another-repo/blob/main/catalog-custom.yaml`; + const expectedEntities = [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${url}`, + 'backstage.io/managed-by-origin-location': `url:${url}`, + }, + name: 'generated-934f500db2ba2e8ea3524567926f45a73bb0b532', + }, + spec: { + presence: 'optional', + target: `${url}`, + type: 'url', + }, + }, + locationKey: 'github-provider:myProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: expectedEntities, + }); + }); + + it('apply full update on scheduled execution with topic exclusion taking priority over topic inclusion', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + myProvider: { + organization: 'test-org', + catalogPath: 'custom/path/catalog-custom.yaml', + filters: { + branch: 'main', + repository: 'test-.*', + topic: { + exclude: ['backstage-exclude'], + include: ['backstage-include'], + }, }, }, }, }, }, - }, + }); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + const provider = GithubEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + const mockGetOrganizationRepositories = jest.spyOn( + helpers, + 'getOrganizationRepositories', + ); + + mockGetOrganizationRepositories.mockReturnValue( + Promise.resolve({ + repositories: [ + { + name: 'test-repo', + url: 'https://github.com/test-org/test-repo', + repositoryTopics: { + nodes: [ + { + topic: { name: 'backstage-include' }, + }, + ], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, + }, + { + name: 'test-repo-2', + url: 'https://github.com/test-org/test-repo-2', + repositoryTopics: { + nodes: [ + { + topic: { name: 'backstage-include' }, + }, + { + topic: { name: 'backstage-exclude' }, + }, + ], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, + }, + { + name: 'test-repo-3', + url: 'https://github.com/test-org/test-repo-3', + repositoryTopics: { + nodes: [ + { + topic: { name: 'backstage-exclude' }, + }, + ], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, + }, + ], + }), + ); + + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('github-provider:myProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`; + const expectedEntities = [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${url}`, + 'backstage.io/managed-by-origin-location': `url:${url}`, + }, + name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f', + }, + spec: { + presence: 'optional', + target: `${url}`, + type: 'url', + }, + }, + locationKey: 'github-provider:myProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: expectedEntities, + }); }); - const schedule = new PersistingTaskRunner(); - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - const provider = GitHubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; - - const mockGetOrganizationRepositories = jest.spyOn( - helpers, - 'getOrganizationRepositories', - ); - - mockGetOrganizationRepositories.mockReturnValue( - Promise.resolve({ - repositories: [ - { - name: 'test-repo', - url: 'https://github.com/test-org/test-repo', - repositoryTopics: { - nodes: [ - { - topic: { name: 'backstage-include' }, - }, - ], + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', }, - isArchived: false, - defaultBranchRef: { - name: 'main', - }, - }, - { - name: 'test-repo-2', - url: 'https://github.com/test-org/test-repo-2', - repositoryTopics: { - nodes: [ - { - topic: { name: 'backstage-include' }, - }, - { - topic: { name: 'backstage-exclude' }, - }, - ], - }, - isArchived: false, - defaultBranchRef: { - name: 'main', - }, - }, - { - name: 'test-repo-3', - url: 'https://github.com/test-org/test-repo-3', - repositoryTopics: { - nodes: [ - { - topic: { name: 'backstage-exclude' }, - }, - ], - }, - isArchived: false, - defaultBranchRef: { - name: 'main', - }, - }, - ], - }), - ); - - await provider.connect(entityProviderConnection); - - const taskDef = schedule.getTasks()[0]; - expect(taskDef.id).toEqual('github-provider:myProvider:refresh'); - await (taskDef.fn as () => Promise)(); - - const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, - }, - name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f', - }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', }, }, - locationKey: 'github-provider:myProvider', - }, - ]; + }); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ - type: 'full', - entities: expectedEntities, + expect(() => + GithubEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + }, + }, + }, + }); + + expect(() => + GithubEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for github-provider:default', + ); + }); + + it('single simple provider config with schedule in config', async () => { + const schedule = new PersistingTaskRunner(); + const scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }); + const providers = GithubEntityProvider.fromConfig(config, { + logger, + scheduler, + }); + + expect(providers).toHaveLength(1); + expect(providers[0].getProviderName()).toEqual('github-provider:default'); }); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts similarity index 80% rename from plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts rename to plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 3909b5a3c2..6d3badf865 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GithubCredentialsProvider, ScmIntegrations, - GitHubIntegrationConfig, - GitHubIntegration, + GithubIntegrationConfig, + GithubIntegration, SingleInstanceGithubCredentialsProvider, } from '@backstage/integration'; import { @@ -35,8 +35,8 @@ import * as uuid from 'uuid'; import { Logger } from 'winston'; import { readProviderConfigs, - GitHubEntityProviderConfig, -} from './GitHubEntityProviderConfig'; + GithubEntityProviderConfig, +} from './GithubEntityProviderConfig'; import { getOrganizationRepositories, Repository } from '../lib/github'; import { satisfiesTopicFilter } from '../lib/util'; @@ -48,10 +48,10 @@ import { satisfiesTopicFilter } from '../lib/util'; * * @public */ -export class GitHubEntityProvider implements EntityProvider { - private readonly config: GitHubEntityProviderConfig; +export class GithubEntityProvider implements EntityProvider { + private readonly config: GithubEntityProviderConfig; private readonly logger: Logger; - private readonly integration: GitHubIntegrationConfig; + private readonly integration: GithubIntegrationConfig; private readonly scheduleFn: () => Promise; private connection?: EntityProviderConnection; private readonly githubCredentialsProvider: GithubCredentialsProvider; @@ -60,9 +60,14 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, - ): GitHubEntityProvider[] { + ): GithubEntityProvider[] { + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + const integrations = ScmIntegrations.fromConfig(config); return readProviderConfigs(config).map(providerConfig => { @@ -75,27 +80,37 @@ export class GitHubEntityProvider implements EntityProvider { ); } - return new GitHubEntityProvider( + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for github-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + + return new GithubEntityProvider( providerConfig, integration, options.logger, - options.schedule, + taskRunner, ); }); } private constructor( - config: GitHubEntityProviderConfig, - integration: GitHubIntegration, + config: GithubEntityProviderConfig, + integration: GithubIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.config = config; this.integration = integration.config; this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); this.githubCredentialsProvider = SingleInstanceGithubCredentialsProvider.create(integration.config); } @@ -111,14 +126,14 @@ export class GitHubEntityProvider implements EntityProvider { return await this.scheduleFn(); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ - class: GitHubEntityProvider.prototype.constructor.name, + class: GithubEntityProvider.prototype.constructor.name, taskId, taskInstanceId: uuid.v4(), }); @@ -141,7 +156,7 @@ export class GitHubEntityProvider implements EntityProvider { const matchingTargets = this.matchesFilters(targets); const entities = matchingTargets .map(repository => this.createLocationUrl(repository)) - .map(GitHubEntityProvider.toLocationSpec) + .map(GithubEntityProvider.toLocationSpec) .map(location => { return { locationKey: this.getProviderName(), @@ -163,6 +178,7 @@ export class GitHubEntityProvider implements EntityProvider { private async findCatalogFiles(): Promise { const organization = this.config.organization; const host = this.integration.host; + const catalogPath = this.config.catalogPath; const orgUrl = `https://${host}/${organization}`; const { headers } = await this.githubCredentialsProvider.getCredentials({ @@ -177,8 +193,18 @@ export class GitHubEntityProvider implements EntityProvider { const { repositories } = await getOrganizationRepositories( client, organization, + catalogPath, ); + if (this.config.validateLocationsExist) { + return repositories.filter(repository => { + return ( + repository.catalogInfoFile?.__typename === 'Blob' && + repository.catalogInfoFile.text !== '' + ); + }); + } + return repositories; } diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts similarity index 70% rename from plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts rename to plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts index 66a5463bf5..0d9b5ce65b 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts @@ -15,7 +15,8 @@ */ import { ConfigReader } from '@backstage/config'; -import { readProviderConfigs } from './GitHubEntityProviderConfig'; +import { Duration } from 'luxon'; +import { readProviderConfigs } from './GithubEntityProviderConfig'; describe('readProviderConfigs', () => { afterEach(() => jest.resetAllMocks()); @@ -81,13 +82,22 @@ describe('readProviderConfigs', () => { organization: 'test-org1', host: 'ghe.internal.com', }, + providerWithSchedule: { + organization: 'test-org1', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, }, }, }, }); const providerConfigs = readProviderConfigs(config); - expect(providerConfigs).toHaveLength(6); + expect(providerConfigs).toHaveLength(7); expect(providerConfigs[0]).toEqual({ id: 'providerOrganizationOnly', organization: 'test-org1', @@ -101,6 +111,8 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[1]).toEqual({ id: 'providerCustomCatalogPath', @@ -115,6 +127,8 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[2]).toEqual({ id: 'providerWithRepositoryFilter', @@ -129,6 +143,8 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[3]).toEqual({ id: 'providerWithBranchFilter', @@ -143,6 +159,8 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[4]).toEqual({ id: 'providerWithTopicFilter', @@ -157,6 +175,8 @@ describe('readProviderConfigs', () => { exclude: ['backstage-exclude'], }, }, + schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[5]).toEqual({ id: 'providerWithHost', @@ -171,6 +191,60 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + validateLocationsExist: false, + schedule: undefined, + }); + expect(providerConfigs[6]).toEqual({ + id: 'providerWithSchedule', + organization: 'test-org1', + catalogPath: '/catalog-info.yaml', + host: 'github.com', + filters: { + repository: undefined, + branch: undefined, + topic: { + include: undefined, + exclude: undefined, + }, + }, + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + }, + validateLocationsExist: false, }); }); + + it('defaults validateLocationsExist to false', () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + }, + }, + }, + }); + const providerConfigs = readProviderConfigs(config); + + expect(providerConfigs[0].validateLocationsExist).toEqual(false); + }); + + it('throws an error when a wildcard catalog path is configured with validation of locations', () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + validateLocationsExist: true, + catalogPath: '/*/catalog-info.yaml', + }, + }, + }, + }); + + expect(() => readProviderConfigs(config)).toThrow(); + }); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts similarity index 74% rename from plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts rename to plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts index d40b206751..e888743258 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts @@ -14,12 +14,16 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; const DEFAULT_PROVIDER_ID = 'default'; -export type GitHubEntityProviderConfig = { +export type GithubEntityProviderConfig = { id: string; catalogPath: string; organization: string; @@ -29,6 +33,8 @@ export type GitHubEntityProviderConfig = { branch?: string; topic?: GithubTopicFilters; }; + validateLocationsExist: boolean; + schedule?: TaskScheduleDefinition; }; export type GithubTopicFilters = { @@ -38,7 +44,7 @@ export type GithubTopicFilters = { export function readProviderConfigs( config: Config, -): GitHubEntityProviderConfig[] { +): GithubEntityProviderConfig[] { const providersConfig = config.getOptionalConfig('catalog.providers.github'); if (!providersConfig) { return []; @@ -59,7 +65,7 @@ export function readProviderConfigs( function readProviderConfig( id: string, config: Config, -): GitHubEntityProviderConfig { +): GithubEntityProviderConfig { const organization = config.getString('organization'); const catalogPath = config.getOptionalString('catalogPath') ?? DEFAULT_CATALOG_PATH; @@ -72,6 +78,20 @@ function readProviderConfig( const topicFilterExclude = config?.getOptionalStringArray( 'filters.topic.exclude', ); + const validateLocationsExist = + config?.getOptionalBoolean('validateLocationsExist') ?? false; + + const catalogPathContainsWildcard = catalogPath.includes('*'); + + if (validateLocationsExist && catalogPathContainsWildcard) { + throw Error( + `Error while processing GitHub provider config. The catalog path ${catalogPath} contains a wildcard, which is incompatible with validation of locations existing before emitting them. Ensure that validateLocationsExist is set to false.`, + ); + } + + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; return { id, @@ -88,8 +108,11 @@ function readProviderConfig( exclude: topicFilterExclude, }, }, + schedule, + validateLocationsExist, }; } + /** * Compiles a RegExp while enforcing the pattern to contain * the start-of-line and end-of-line anchors. diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts similarity index 96% rename from plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.test.ts rename to plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index 2d05346f01..b3fc5022ec 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -18,18 +18,18 @@ import { getVoidLogger } from '@backstage/backend-common'; import { GroupEntity, UserEntity } from '@backstage/catalog-model'; import { GithubCredentialsProvider, - GitHubIntegrationConfig, + GithubIntegrationConfig, } from '@backstage/integration'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { graphql } from '@octokit/graphql'; import { - GitHubOrgEntityProvider, + GithubOrgEntityProvider, withLocations, -} from './GitHubOrgEntityProvider'; +} from './GithubOrgEntityProvider'; jest.mock('@octokit/graphql'); -describe('GitHubOrgEntityProvider', () => { +describe('GithubOrgEntityProvider', () => { describe('read', () => { afterEach(() => jest.resetAllMocks()); @@ -87,7 +87,7 @@ describe('GitHubOrgEntityProvider', () => { }; const logger = getVoidLogger(); - const gitHubConfig: GitHubIntegrationConfig = { + const gitHubConfig: GithubIntegrationConfig = { host: 'https://github.com', }; @@ -100,7 +100,7 @@ describe('GitHubOrgEntityProvider', () => { getCredentials: mockGetCredentials, }; - const entityProvider = new GitHubOrgEntityProvider({ + const entityProvider = new GithubOrgEntityProvider({ id: 'my-id', githubCredentialsProvider, orgUrl: 'https://github.com/backstage', diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts similarity index 92% rename from plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.ts rename to plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index bb994c193c..2fc8ce7300 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -24,7 +24,7 @@ import { Config } from '@backstage/config'; import { DefaultGithubCredentialsProvider, GithubCredentialsProvider, - GitHubIntegrationConfig, + GithubIntegrationConfig, ScmIntegrations, SingleInstanceGithubCredentialsProvider, } from '@backstage/integration'; @@ -41,15 +41,15 @@ import { buildOrgHierarchy, getOrganizationTeams, getOrganizationUsers, - parseGitHubOrgUrl, + parseGithubOrgUrl, } from '../lib'; /** - * Options for {@link GitHubOrgEntityProvider}. + * Options for {@link GithubOrgEntityProvider}. * * @public */ -export interface GitHubOrgEntityProviderOptions { +export interface GithubOrgEntityProviderOptions { /** * A unique, stable identifier for this provider. * @@ -96,12 +96,12 @@ export interface GitHubOrgEntityProviderOptions { * * @public */ -export class GitHubOrgEntityProvider implements EntityProvider { +export class GithubOrgEntityProvider implements EntityProvider { private readonly credentialsProvider: GithubCredentialsProvider; private connection?: EntityProviderConnection; private scheduleFn?: () => Promise; - static fromConfig(config: Config, options: GitHubOrgEntityProviderOptions) { + static fromConfig(config: Config, options: GithubOrgEntityProviderOptions) { const integrations = ScmIntegrations.fromConfig(config); const gitHubConfig = integrations.github.byUrl(options.orgUrl)?.config; @@ -115,7 +115,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { target: options.orgUrl, }); - const provider = new GitHubOrgEntityProvider({ + const provider = new GithubOrgEntityProvider({ id: options.id, orgUrl: options.orgUrl, logger, @@ -134,7 +134,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { private options: { id: string; orgUrl: string; - gitHubConfig: GitHubIntegrationConfig; + gitHubConfig: GithubIntegrationConfig; logger: Logger; githubCredentialsProvider?: GithubCredentialsProvider; }, @@ -146,7 +146,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ getProviderName() { - return `GitHubOrgEntityProvider:${this.options.id}`; + return `GithubOrgEntityProvider:${this.options.id}`; } /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ @@ -176,7 +176,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { headers, }); - const { org } = parseGitHubOrgUrl(this.options.orgUrl); + const { org } = parseGithubOrgUrl(this.options.orgUrl); const { users } = await getOrganizationUsers(client, org, tokenType); const { groups, groupMemberUsers } = await getOrganizationTeams( client, @@ -202,7 +202,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { markCommitComplete(); } - private schedule(schedule: GitHubOrgEntityProviderOptions['schedule']) { + private schedule(schedule: GithubOrgEntityProviderOptions['schedule']) { if (!schedule || schedule === 'manual') { return; } @@ -213,7 +213,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { id, fn: async () => { const logger = this.options.logger.child({ - class: GitHubOrgEntityProvider.prototype.constructor.name, + class: GithubOrgEntityProvider.prototype.constructor.name, taskId: id, taskInstanceId: uuid.v4(), }); diff --git a/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..b555927d2c --- /dev/null +++ b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts @@ -0,0 +1,84 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { githubEntityProviderCatalogModule } from './GithubEntityProviderCatalogModule'; +import { GithubEntityProvider } from '../providers/GithubEntityProvider'; + +describe('githubEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'module-test', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [githubEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'github-provider:default', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-github/src/module.ts b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.ts similarity index 61% rename from plugins/catalog-backend-module-github/src/module.ts rename to plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.ts index 0391855618..8fdee886a3 100644 --- a/plugins/catalog-backend-module-github/src/module.ts +++ b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.ts @@ -21,46 +21,30 @@ import { loggerServiceRef, schedulerServiceRef, } from '@backstage/backend-plugin-api'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; -import { GitHubEntityProvider } from './providers/GitHubEntityProvider'; +import { GithubEntityProvider } from '../providers/GithubEntityProvider'; /** - * Options for {@link githubEntityProviderCatalogModule}. - * - * @alpha - */ -export type GithubEntityProviderCatalogModuleOptions = { - schedule?: TaskScheduleDefinition; -}; - -/** - * Registers the GitHubEntityProvider with the catalog processing extension point. + * Registers the {@link GithubEntityProvider} with the catalog processing extension point. * * @alpha */ export const githubEntityProviderCatalogModule = createBackendModule({ pluginId: 'catalog', moduleId: 'githubEntityProvider', - register(env, options?: GithubEntityProviderCatalogModuleOptions) { + register(env) { env.registerInit({ deps: { - config: configServiceRef, catalog: catalogProcessingExtensionPoint, + config: configServiceRef, logger: loggerServiceRef, scheduler: schedulerServiceRef, }, - async init({ config, catalog, logger, scheduler }) { - const scheduleDef = options?.schedule ?? { - frequency: { seconds: 600 }, - timeout: { seconds: 900 }, - initialDelay: { seconds: 3 }, - }; - + async init({ catalog, config, logger, scheduler }) { catalog.addEntityProvider( - GitHubEntityProvider.fromConfig(config, { + GithubEntityProvider.fromConfig(config, { logger: loggerToWinstonLogger(logger), - schedule: scheduler.createScheduledTaskRunner(scheduleDef), + scheduler, }), ); }, diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index add7b604d3..18cce9e01e 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.1.9-next.0 + +### Patch Changes + +- 6bb046bcbe: Add `gitlabDiscoveryEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- 81cedb5033: `GitlabDiscoveryEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/gitlab/discovery + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md index 1009da8209..9ed90a2c42 100644 --- a/plugins/catalog-backend-module-gitlab/api-report.md +++ b/plugins/catalog-backend-module-gitlab/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; @@ -10,6 +11,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; // @public @@ -21,7 +23,8 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GitlabDiscoveryEntityProvider[]; // (undocumented) @@ -30,6 +33,11 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { refresh(logger: Logger): Promise; } +// @alpha +export const gitlabDiscoveryEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; + // @public export class GitLabDiscoveryProcessor implements CatalogProcessor { // (undocumented) diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index 82f1a5db7c..ac75a04c2a 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -14,11 +14,10 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { - /** - * List of provider-specific options and attributes - */ providers?: { /** * GitlabDiscoveryEntityProvider configuration @@ -28,27 +27,27 @@ export interface Config { { /** * (Required) Gitlab's host name. - * @visibility backend */ host: string; /** * (Optional) Gitlab's group[/subgroup] where the discovery is done. * If not defined the whole project will be scanned. - * @visibility backend */ group?: string; /** * (Optional) Default branch to read the catalog-info.yaml file. * If not set, 'master' will be used. - * @visibility backend */ branch?: string; /** * (Optional) The name used for the catalog file. * If not set, 'catalog-info.yaml' will be used. - * @visibility backend */ entityFilename?: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 510a4eb3be..e556d4b2de 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,12 +1,13 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", "description": "A Backstage catalog backend module that helps integrate towards GitLab", - "version": "0.1.8-next.1", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,22 +24,24 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "lodash": "^4.17.21", "msw": "^0.47.0", @@ -50,9 +53,13 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/lodash": "^4.14.151", - "@types/uuid": "^8.0.0" + "@types/uuid": "^8.0.0", + "luxon": "^3.0.0" }, "files": [ + "alpha", + "config.d.ts", "dist" - ] + ], + "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-gitlab/src/index.ts b/plugins/catalog-backend-module-gitlab/src/index.ts index 3182210c81..27aac9e78f 100644 --- a/plugins/catalog-backend-module-gitlab/src/index.ts +++ b/plugins/catalog-backend-module-gitlab/src/index.ts @@ -22,3 +22,4 @@ export { GitLabDiscoveryProcessor } from './GitLabDiscoveryProcessor'; export { GitlabDiscoveryEntityProvider } from './providers'; +export { gitlabDiscoveryEntityProviderCatalogModule } from './service/GitlabDiscoveryEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts index 8a3c33c8f2..86fe1aa385 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; + export type GitlabGroupDescription = { id: number; web_url: string; @@ -36,4 +38,5 @@ export type GitlabProviderConfig = { branch: string; catalogFile: string; projectPattern: RegExp; + schedule?: TaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index 3f3c4e7f91..ae19edc48c 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -15,13 +15,17 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import { GitlabDiscoveryEntityProvider } from './GitlabDiscoveryEntityProvider'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; +import { GitlabDiscoveryEntityProvider } from './GitlabDiscoveryEntityProvider'; class PersistingTaskRunner implements TaskRunner { private tasks: TaskInvocationDefinition[] = []; @@ -338,4 +342,111 @@ describe('GitlabDiscoveryEntityProvider', () => { ], }); }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'test-gitlab', + apiBaseUrl: 'https://api.gitlab.example/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'test-gitlab', + group: 'test-group', + }, + }, + }, + }, + }); + + expect(() => + GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'test-gitlab', + apiBaseUrl: 'https://api.gitlab.example/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'test-gitlab', + group: 'test-group', + }, + }, + }, + }, + }); + + expect(() => + GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for GitlabDiscoveryEntityProvider:test-id', + ); + }); + + it('single simple provider config with schedule in config', async () => { + const schedule = new PersistingTaskRunner(); + const scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'test-gitlab', + apiBaseUrl: 'https://api.gitlab.example/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'test-gitlab', + group: 'test-group', + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + const providers = GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + scheduler, + }); + + expect(providers).toHaveLength(1); + expect(providers[0].getProviderName()).toEqual( + 'GitlabDiscoveryEntityProvider:test-id', + ); + }); }); diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index ffb123edf7..cb4629b820 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts @@ -14,14 +14,16 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GitLabIntegration, ScmIntegrations } from '@backstage/integration'; import { EntityProvider, EntityProviderConnection, + LocationSpec, + locationSpecToLocationEntity, } from '@backstage/plugin-catalog-backend'; -import { LocationSpec } from '@backstage/plugin-catalog-backend'; +import * as uuid from 'uuid'; import { Logger } from 'winston'; import { GitLabClient, @@ -30,8 +32,6 @@ import { paginated, readGitlabConfigs, } from '../lib'; -import * as uuid from 'uuid'; -import { locationSpecToLocationEntity } from '@backstage/plugin-catalog-backend'; type Result = { scanned: number; @@ -51,8 +51,16 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { static fromConfig( config: Config, - options: { logger: Logger; schedule: TaskRunner }, + options: { + logger: Logger; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; + }, ): GitlabDiscoveryEntityProvider[] { + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + const providerConfigs = readGitlabConfigs(config); const integrations = ScmIntegrations.fromConfig(config).gitlab; const providers: GitlabDiscoveryEntityProvider[] = []; @@ -64,11 +72,23 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { `No gitlab integration found that matches host ${providerConfig.host}`, ); } + + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for GitlabDiscoveryEntityProvider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + providers.push( new GitlabDiscoveryEntityProvider({ ...options, config: providerConfig, integration, + taskRunner, }), ); }); @@ -79,14 +99,14 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { config: GitlabProviderConfig; integration: GitLabIntegration; logger: Logger; - schedule: TaskRunner; + taskRunner: TaskRunner; }) { this.config = options.config; this.integration = options.integration; this.logger = options.logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(options.schedule); + this.scheduleFn = this.createScheduleFn(options.taskRunner); } getProviderName(): string { @@ -98,10 +118,10 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { await this.scheduleFn(); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts index cb36e5166c..6b337bf7cd 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readGitlabConfigs } from './config'; describe('config', () => { @@ -53,6 +54,7 @@ describe('config', () => { host: 'host', catalogFile: 'catalog-info.yaml', projectPattern: /[\s\S]*/, + schedule: undefined, }), ); }); @@ -83,6 +85,49 @@ describe('config', () => { host: 'host', catalogFile: 'custom-file.yaml', projectPattern: /[\s\S]*/, + schedule: undefined, + }), + ); + }); + + it('valid config with schedule', () => { + const config = new ConfigReader({ + catalog: { + providers: { + gitlab: { + test: { + group: 'group', + host: 'host', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + }, + }, + }, + }); + + const result = readGitlabConfigs(config); + expect(result).toHaveLength(1); + result.forEach(r => + expect(r).toStrictEqual({ + id: 'test', + group: 'group', + branch: 'master', + host: 'host', + catalogFile: 'catalog-info.yaml', + projectPattern: /[\s\S]*/, + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + initialDelay: undefined, + scope: undefined, + }, }), ); }); diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts index 392922860f..3dfc00745e 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GitlabProviderConfig } from '../lib'; @@ -35,6 +36,10 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { config.getOptionalString('projectPattern') ?? /[\s\S]*/, ); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, group, @@ -42,6 +47,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { host, catalogFile, projectPattern, + schedule, }; } diff --git a/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..0b803734b1 --- /dev/null +++ b/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts @@ -0,0 +1,96 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { gitlabDiscoveryEntityProviderCatalogModule } from './GitlabDiscoveryEntityProviderCatalogModule'; +import { GitlabDiscoveryEntityProvider } from '../providers'; + +describe('gitlabDiscoveryEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'test-gitlab', + apiBaseUrl: 'https://api.gitlab.example/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'test-gitlab', + group: 'test-group', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [gitlabDiscoveryEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'GitlabDiscoveryEntityProvider:test-id', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..dc11841e20 --- /dev/null +++ b/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.ts @@ -0,0 +1,53 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + createBackendModule, + loggerToWinstonLogger, + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { GitlabDiscoveryEntityProvider } from '../providers'; + +/** + * Registers the GitlabDiscoveryEntityProvider with the catalog processing extension point. + * + * @alpha + */ +export const gitlabDiscoveryEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'gitlabDiscoveryEntityProvider', + register(env) { + env.registerInit({ + deps: { + config: configServiceRef, + catalog: catalogProcessingExtensionPoint, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ config, catalog, logger, scheduler }) { + catalog.addEntityProvider( + GitlabDiscoveryEntityProvider.fromConfig(config, { + logger: loggerToWinstonLogger(logger), + scheduler, + }), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 305eed8686..8a58fce1f5 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.5.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.5.4-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index eaa3929ade..d095be6b5c 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", "description": "A Backstage catalog backend module that helps integrate towards LDAP", - "version": "0.5.4-next.1", + "version": "0.5.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 2da4607869..8b8d4479d3 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.4.4-next.0 + +### Patch Changes + +- 8d1a5e08ca: `MicrosoftGraphOrgEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://github.com/backstage/backstage/tree/master/plugins/catalog-backend-module-msgraph#readme + +- 384f99c276: Add `microsoftGraphOrgEntityProviderCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/README.md b/plugins/catalog-backend-module-msgraph/README.md index 9bf979f6a6..6f8c95b667 100644 --- a/plugins/catalog-backend-module-msgraph/README.md +++ b/plugins/catalog-backend-module-msgraph/README.md @@ -85,6 +85,13 @@ catalog: # in order to add extra information to your groups that can be used on your custom groupTransformers # See https://docs.microsoft.com/en-us/graph/api/resources/schemaextension?view=graph-rest-1.0 select: ['id', 'displayName', 'description'] + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { hours: 1 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 50 } + # supports ISO duration, "human duration" as used in code + initialDelay: { seconds: 15}, ``` `user.filter` and `userGroupMember.filter` are mutually exclusive, only one can be provided. If both are provided, an error will be thrown. @@ -116,13 +123,21 @@ yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-msgraph + builder.addEntityProvider( + MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ scheduler, ++ }), ++ ); +``` + +Instead of configuring the refresh schedule inside the config (per provider instance), +you can define it in code (for all of them): + +```diff +- scheduler, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { hours: 1 }, + timeout: { minutes: 50 }, -+ initialDelay: { seconds: 15} ++ initialDelay: { seconds: 15}, + }), -+ }), -+ ); ``` ## Customize the Processor or Entity Provider @@ -161,10 +176,7 @@ export async function myGroupTransformer( builder.addEntityProvider( MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 5 }, - timeout: { minutes: 3 }, - }), + scheduler, + groupTransformer: myGroupTransformer, }), ); diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index 8b77ddf7c3..921d682b89 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; @@ -12,8 +13,10 @@ import { GroupEntity } from '@backstage/catalog-model'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Response as Response_2 } from 'node-fetch'; import { TaskRunner } from '@backstage/backend-tasks'; +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; import { TokenCredential } from '@azure/identity'; import { UserEntity } from '@backstage/catalog-model'; @@ -131,6 +134,20 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { read(options?: { logger?: Logger }): Promise; } +// @alpha +export const microsoftGraphOrgEntityProviderCatalogModule: ( + options?: MicrosoftGraphOrgEntityProviderCatalogModuleOptions | undefined, +) => BackendFeature; + +// @alpha +export interface MicrosoftGraphOrgEntityProviderCatalogModuleOptions { + groupTransformer?: GroupTransformer | Record; + organizationTransformer?: + | OrganizationTransformer + | Record; + userTransformer?: UserTransformer | Record; +} + // @public @deprecated export interface MicrosoftGraphOrgEntityProviderLegacyOptions { groupTransformer?: GroupTransformer; @@ -147,7 +164,8 @@ export type MicrosoftGraphOrgEntityProviderOptions = | MicrosoftGraphOrgEntityProviderLegacyOptions | { logger: Logger; - schedule: 'manual' | TaskRunner; + schedule?: 'manual' | TaskRunner; + scheduler?: PluginTaskScheduler; userTransformer?: UserTransformer | Record; groupTransformer?: GroupTransformer | Record; organizationTransformer?: @@ -202,6 +220,7 @@ export type MicrosoftGraphProviderConfig = { groupSearch?: string; groupSelect?: string[]; queryMode?: 'basic' | 'advanced'; + schedule?: TaskScheduleDefinition; }; // @public diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index 9fe1cb81c9..904ce1f646 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -14,14 +14,10 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { - /** - * Configuration options for the catalog plugin. - */ catalog?: { - /** - * List of processor-specific options and attributes - */ processors?: { /** * MicrosoftGraphOrgReaderProcessor configuration @@ -109,9 +105,7 @@ export interface Config { }>; }; }; - /** - * List of provider-specific options and attributes - */ + providers?: { /** * MicrosoftGraphOrgEntityProvider configuration. @@ -209,6 +203,11 @@ export interface Config { */ search?: string; }; + + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } | Record< string, @@ -292,6 +291,11 @@ export interface Config { */ search?: string; }; + + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index d3ac35f743..cf301c71a0 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,12 +1,13 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", - "version": "0.4.3-next.1", + "version": "0.4.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,20 +24,22 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "clean": "backstage-cli package clean" }, "dependencies": { "@azure/identity": "^2.1.0", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@microsoft/microsoft-graph-types": "^2.6.0", "@types/node-fetch": "^2.5.12", "lodash": "^4.17.21", @@ -51,11 +54,13 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/lodash": "^4.14.151", + "luxon": "^3.0.0", "msw": "^0.47.0" }, "files": [ - "dist", - "config.d.ts" + "alpha", + "config.d.ts", + "dist" ], "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-msgraph/src/index.ts b/plugins/catalog-backend-module-msgraph/src/index.ts index 6a7adedc58..b80a0b3863 100644 --- a/plugins/catalog-backend-module-msgraph/src/index.ts +++ b/plugins/catalog-backend-module-msgraph/src/index.ts @@ -20,5 +20,7 @@ * @packageDocumentation */ -export * from './processors'; export * from './microsoftGraph'; +export * from './processors'; +export { microsoftGraphOrgEntityProviderCatalogModule } from './service/MicrosoftGraphOrgEntityProviderCatalogModule'; +export type { MicrosoftGraphOrgEntityProviderCatalogModuleOptions } from './service/MicrosoftGraphOrgEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts index 3331e3609d..68c174665d 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readMicrosoftGraphConfig, readProviderConfigs } from './config'; describe('readMicrosoftGraphConfig', () => { @@ -172,6 +173,12 @@ describe('readProviderConfigs', () => { filter: 'securityEnabled eq false', select: ['id', 'displayName', 'description'], }, + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, }, }, }, @@ -192,6 +199,12 @@ describe('readProviderConfigs', () => { groupExpand: 'member', groupSelect: ['id', 'displayName', 'description'], groupFilter: 'securityEnabled eq false', + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + }, }, ]; expect(actual).toEqual(expected); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index b8565e0d72..506951ff10 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { trimEnd } from 'lodash'; @@ -121,6 +125,11 @@ export type MicrosoftGraphProviderConfig = { * Some features like `$expand` are not available for advanced queries, though. */ queryMode?: 'basic' | 'advanced'; + + /** + * Schedule configuration for refresh tasks. + */ + schedule?: TaskScheduleDefinition; }; /** @@ -296,6 +305,10 @@ export function readProviderConfig( throw new Error(`clientId must be provided when clientSecret is defined.`); } + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, target, @@ -312,5 +325,6 @@ export function readProviderConfig( queryMode, userGroupMemberFilter, userGroupMemberSearch, + schedule, }; } diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts index ff68235c4c..8af327cf27 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts @@ -14,6 +14,11 @@ * limitations under the License. */ import { getVoidLogger } from '@backstage/backend-common'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { ANNOTATION_LOCATION, @@ -43,10 +48,21 @@ const readMicrosoftGraphOrgMocked = readMicrosoftGraphOrg as jest.Mock< Promise<{ users: UserEntity[]; groups: GroupEntity[] }> >; -describe('MicrosoftGraphOrgEntityProvider', () => { - afterEach(() => jest.resetAllMocks()); +class PersistingTaskRunner implements TaskRunner { + private tasks: TaskInvocationDefinition[] = []; - it('should apply mutation', async () => { + getTasks() { + return this.tasks; + } + + run(task: TaskInvocationDefinition): Promise { + this.tasks.push(task); + return Promise.resolve(undefined); + } +} + +describe('MicrosoftGraphOrgEntityProvider', () => { + beforeEach(() => { jest .spyOn(MicrosoftGraphClient, 'create') .mockReturnValue({} as unknown as MicrosoftGraphClient); @@ -78,8 +94,65 @@ describe('MicrosoftGraphOrgEntityProvider', () => { }, ], }); + }); - const config = { + afterEach(() => jest.resetAllMocks()); + + const logger = getVoidLogger(); + const taskRunner = new PersistingTaskRunner(); + const scheduler = { + createScheduledTaskRunner: (_: any) => taskRunner, + } as unknown as PluginTaskScheduler; + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + const expectedMutation = { + entities: [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'msgraph:customProviderId/u1', + 'backstage.io/managed-by-origin-location': + 'msgraph:customProviderId/u1', + }, + name: 'u1', + }, + spec: { + memberOf: [], + }, + }, + locationKey: 'msgraph-org-provider:customProviderId', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'msgraph:customProviderId/g1', + 'backstage.io/managed-by-origin-location': + 'msgraph:customProviderId/g1', + }, + name: 'g1', + }, + spec: { + children: [], + type: 'team', + }, + }, + locationKey: 'msgraph-org-provider:customProviderId', + }, + ], + type: 'full', + }; + + it('should apply mutation - manual', async () => { + const config = new ConfigReader({ catalog: { providers: { microsoftGraphOrg: { @@ -92,67 +165,143 @@ describe('MicrosoftGraphOrgEntityProvider', () => { }, }, }, - }; - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - const provider = MicrosoftGraphOrgEntityProvider.fromConfig( - new ConfigReader(config), - { - logger: getVoidLogger(), - schedule: 'manual', - }, - )[0]; - - provider.connect(entityProviderConnection); + }); + const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + schedule: 'manual', + })[0]; + await provider.connect(entityProviderConnection); await provider.read(); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ - entities: [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'User', - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'msgraph:customProviderId/u1', - 'backstage.io/managed-by-origin-location': - 'msgraph:customProviderId/u1', - }, - name: 'u1', - }, - spec: { - memberOf: [], + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith( + expectedMutation, + ); + }); + + it('should apply mutation - schedule', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', }, }, - locationKey: 'msgraph-org-provider:customProviderId', }, - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - annotations: { - 'backstage.io/managed-by-location': - 'msgraph:customProviderId/g1', - 'backstage.io/managed-by-origin-location': - 'msgraph:customProviderId/g1', - }, - name: 'g1', - }, - spec: { - children: [], - type: 'team', - }, - }, - locationKey: 'msgraph-org-provider:customProviderId', - }, - ], - type: 'full', + }, }); + const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + schedule: taskRunner, + })[0]; + expect(provider.getProviderName()).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId', + ); + + await provider.connect(entityProviderConnection); + + const taskDef = taskRunner.getTasks()[0]; + expect(taskDef.id).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId:refresh', + ); + await (taskDef.fn as () => Promise)(); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith( + expectedMutation, + ); + }); + + it('should apply mutation - scheduler', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + const provider = MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + scheduler, + })[0]; + expect(provider.getProviderName()).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId', + ); + + await provider.connect(entityProviderConnection); + + const taskDef = taskRunner.getTasks()[0]; + expect(taskDef.id).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId:refresh', + ); + await (taskDef.fn as () => Promise)(); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith( + expectedMutation, + ); + }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + }, + }, + }, + }, + }); + + expect(() => + MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + }, + }, + }, + }, + }); + + expect(() => + MicrosoftGraphOrgEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for MicrosoftGraphOrgEntityProvider:customProviderId', + ); }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index 67f3039873..32892e7201 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, @@ -67,7 +67,13 @@ export type MicrosoftGraphOrgEntityProviderOptions = * {@link @backstage/backend-tasks#PluginTaskScheduler.createScheduledTaskRunner} * to enable automatic scheduling of tasks. */ - schedule: 'manual' | TaskRunner; + schedule?: 'manual' | TaskRunner; + + /** + * Scheduler used to schedule refreshes based on + * the schedule config. + */ + scheduler?: PluginTaskScheduler; /** * The function that transforms a user entry in msgraph to an entity. @@ -168,6 +174,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { ]; } + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + function getTransformer( id: string, transformers?: T | Record, @@ -180,6 +190,16 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { } return readProviderConfigs(configRoot).map(providerConfig => { + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for MicrosoftGraphOrgEntityProvider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + const provider = new MicrosoftGraphOrgEntityProvider({ id: providerConfig.id, provider: providerConfig, @@ -197,7 +217,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { options.organizationTransformer, ), }); - provider.schedule(options.schedule); + + if (taskRunner !== 'manual') { + provider.schedule(taskRunner); + } return provider; }); @@ -238,7 +261,9 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { provider, }); - result.schedule(options.schedule); + if (options.schedule !== 'manual') { + result.schedule(options.schedule); + } return result; } @@ -311,16 +336,10 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { markCommitComplete(); } - private schedule( - schedule: MicrosoftGraphOrgEntityProviderOptions['schedule'], - ) { - if (schedule === 'manual') { - return; - } - + private schedule(taskRunner: TaskRunner) { this.scheduleFn = async () => { const id = `${this.getProviderName()}:refresh`; - await schedule.run({ + await taskRunner.run({ id, fn: async () => { const logger = this.options.logger.child({ diff --git a/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..764f504898 --- /dev/null +++ b/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.test.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { microsoftGraphOrgEntityProviderCatalogModule } from './MicrosoftGraphOrgEntityProviderCatalogModule'; +import { MicrosoftGraphOrgEntityProvider } from '../processors'; + +describe('awsS3EntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + microsoftGraphOrg: { + customProviderId: { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [microsoftGraphOrgEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('PT30M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'MicrosoftGraphOrgEntityProvider:customProviderId', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..9e817636fa --- /dev/null +++ b/plugins/catalog-backend-module-msgraph/src/service/MicrosoftGraphOrgEntityProviderCatalogModule.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { + configServiceRef, + createBackendModule, + loggerServiceRef, + loggerToWinstonLogger, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { + GroupTransformer, + OrganizationTransformer, + UserTransformer, +} from '../microsoftGraph'; +import { MicrosoftGraphOrgEntityProvider } from '../processors'; + +/** + * Options for {@link microsoftGraphOrgEntityProviderCatalogModule}. + * + * @alpha + */ +export interface MicrosoftGraphOrgEntityProviderCatalogModuleOptions { + /** + * The function that transforms a user entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + userTransformer?: UserTransformer | Record; + + /** + * The function that transforms a group entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + groupTransformer?: GroupTransformer | Record; + + /** + * The function that transforms an organization entry in msgraph to an entity. + * Optionally, you can pass separate transformers per provider ID. + */ + organizationTransformer?: + | OrganizationTransformer + | Record; +} + +/** + * Registers the MicrosoftGraphOrgEntityProvider with the catalog processing extension point. + * + * @alpha + */ +export const microsoftGraphOrgEntityProviderCatalogModule = createBackendModule( + { + pluginId: 'catalog', + moduleId: 'microsoftGraphOrgEntityProvider', + register( + env, + options?: MicrosoftGraphOrgEntityProviderCatalogModuleOptions, + ) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: configServiceRef, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ catalog, config, logger, scheduler }) { + catalog.addEntityProvider( + MicrosoftGraphOrgEntityProvider.fromConfig(config, { + groupTransformer: options?.groupTransformer, + logger: loggerToWinstonLogger(logger), + organizationTransformer: options?.organizationTransformer, + scheduler, + userTransformer: options?.userTransformer, + }), + ); + }, + }); + }, + }, +); diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index d098727b3e..0dc46b07a7 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.4-next.0 + +### Patch Changes + +- 4ce887400d: Added support to use the `UrlReaders` when `$ref` pointing to a URL. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.3-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index dded553b09..6f21b8b2e0 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", - "version": "0.1.3-next.1", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts index 21080202b5..9f97e621c2 100644 --- a/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts +++ b/plugins/catalog-backend-module-openapi/src/OpenApiRefProcessor.test.ts @@ -48,6 +48,7 @@ describe('OpenApiRefProcessor', () => { const config = new ConfigReader({}); const reader = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), }; diff --git a/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts b/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts index 0ecd9f7fd1..57ecd54f26 100644 --- a/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts +++ b/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts @@ -88,6 +88,33 @@ describe('bundleOpenApiSpecification', () => { resolveUrl, ); + expect(result).toEqual(expectedResult.trimStart()); + }); + it('should use the urlreaders to fetch $refs', async () => { + const spec = ` + openapi: "3.0.0" + info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT + servers: + - url: http://petstore.swagger.io/v1 + paths: + /pets: + get: + $ref: "https://foo.com/paths/pets/list.yaml" + `; + + read.mockResolvedValue(list); + + const result = await bundleOpenApiSpecification( + spec, + 'https://github.com/owner/repo/blob/main/catalog-info.yaml', + read, + resolveUrl, + ); + expect(result).toEqual(expectedResult.trimStart()); }); }); diff --git a/plugins/catalog-backend-module-openapi/src/lib/bundle.ts b/plugins/catalog-backend-module-openapi/src/lib/bundle.ts index 6ac8d211e2..8dd880c234 100644 --- a/plugins/catalog-backend-module-openapi/src/lib/bundle.ts +++ b/plugins/catalog-backend-module-openapi/src/lib/bundle.ts @@ -47,11 +47,21 @@ export async function bundleOpenApiSpecification( return await read(url); }, }; + const httpUrlReaderResolver: SwaggerParser.ResolverOptions = { + canRead: ref => { + const protocol = getProtocol(ref.url); + return protocol === 'http' || protocol === 'https'; + }, + read: async ref => { + const url = resolveUrl(ref.url, baseUrl); + return await read(url); + }, + }; const options: SwaggerParser.Options = { resolve: { file: fileUrlReaderResolver, - http: true, + http: httpUrlReaderResolver, }, }; const specObject = parse(specification); diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index d3b24efb6f..494f332c12 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,99 @@ # @backstage/plugin-catalog-backend +## 1.5.1-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.5.0 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 8cb6e10105: Fixed a bug where entities provided without a location key would always replace existing entities, rather than updating them. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 63296ebcd4: Allow Placeholder value to be any value, not only string. +- 74022e0163: Make sure to stitch entities correctly after deletion, to ensure that their relations are updated. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 1.5.0-next.2 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + ## 1.4.1-next.1 ### Patch Changes diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index d23c6f7519..10b9afc291 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -45,6 +45,7 @@ import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { processingResult } from '@backstage/plugin-catalog-node'; @@ -171,37 +172,52 @@ export const catalogConditions: Conditions<{ Entity, EntitiesSearchFilter, 'catalog-entity', - [annotation: string, value?: string | undefined] + { + value?: string | undefined; + annotation: string; + } >; hasLabel: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [label: string] + { + label: string; + } >; hasMetadata: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + value?: string | undefined; + key: string; + } >; hasSpec: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + value?: string | undefined; + key: string; + } >; isEntityKind: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [kinds: string[]] + { + kinds: string[]; + } >; isEntityOwner: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [claims: string[]] + { + claims: string[]; + } >; }>; @@ -215,8 +231,9 @@ export type CatalogEnvironment = { }; // @alpha -export type CatalogPermissionRule = - PermissionRule; +export type CatalogPermissionRule< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule; // @alpha export const catalogPlugin: (options?: undefined) => BackendFeature; @@ -274,12 +291,14 @@ export class CodeOwnersProcessor implements CatalogProcessor { export const createCatalogConditionalDecision: ( permission: ResourcePermission<'catalog-entity'>, conditions: PermissionCriteria< - PermissionCondition<'catalog-entity', unknown[]> + PermissionCondition<'catalog-entity', PermissionRuleParams> >, ) => ConditionalPolicyDecision; // @alpha -export const createCatalogPermissionRule: ( +export const createCatalogPermissionRule: < + TParams extends PermissionRuleParams = undefined, +>( rule: PermissionRule, ) => PermissionRule; @@ -442,37 +461,52 @@ export const permissionRules: { Entity, EntitiesSearchFilter, 'catalog-entity', - [annotation: string, value?: string | undefined] + { + value?: string | undefined; + annotation: string; + } >; hasLabel: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [label: string] + { + label: string; + } >; hasMetadata: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + value?: string | undefined; + key: string; + } >; hasSpec: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + value?: string | undefined; + key: string; + } >; isEntityKind: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [kinds: string[]] + { + kinds: string[]; + } >; isEntityOwner: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [claims: string[]] + { + claims: string[]; + } >; }; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 4d127a4d43..b18a133505 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "1.4.1-next.1", + "version": "1.5.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts index 71154c5473..9cb4d414c0 100644 --- a/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/codeowners/CodeOwnersProcessor.test.ts @@ -33,34 +33,24 @@ describe('CodeOwnersProcessor', () => { target: `https://github.com/backstage/backstage/blob/master/${basePath}catalog-info.yaml`, }); - const mockReadResult = ({ - error = undefined, - data = undefined, - }: { - error?: string; - data?: string; - } = {}) => { - if (error) { - throw Error(error); - } - return data; - }; - describe('preProcessEntity', () => { const setupTest = ({ kind = 'Component', spec = {} } = {}) => { const entity = { kind, spec }; - const read = jest - .fn() - .mockResolvedValue(mockReadResult({ data: mockCodeOwnersText() })); - const config = new ConfigReader({}); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(mockCodeOwnersText()), + }), + }; const processor = CodeOwnersProcessor.fromConfig(config, { logger: getVoidLogger(), reader, }); - return { entity, processor, read }; + return { entity, processor }; }; it('should not modify existing owner', async () => { diff --git a/plugins/catalog-backend/src/modules/codeowners/lib/read.test.ts b/plugins/catalog-backend/src/modules/codeowners/lib/read.test.ts index 49189b20de..c1d2301536 100644 --- a/plugins/catalog-backend/src/modules/codeowners/lib/read.test.ts +++ b/plugins/catalog-backend/src/modules/codeowners/lib/read.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { NotFoundError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { findCodeOwnerByTarget, readCodeOwners } from './read'; @@ -25,35 +26,32 @@ const mockCodeowners = ` /docs @acme/team-bar `; -const mockReadResult = ({ - error = undefined, - data = undefined, -}: { - error?: string; - data?: string; -} = {}) => { - if (error) { - throw Error(error); - } - return data; -}; - describe('readCodeOwners', () => { it('should return found codeowners file', async () => { - const ownersText = mockCodeowners; - const read = jest - .fn() - .mockResolvedValue(mockReadResult({ data: ownersText })); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from(mockCodeowners)), + }), + readTree: jest.fn(), + search: jest.fn(), + }; + const result = await readCodeOwners(reader, sourceUrl, [ '.github/CODEOWNERS', ]); - expect(result).toEqual(ownersText); + expect(result).toEqual(mockCodeowners); }); it('should return undefined when no codeowner', async () => { - const read = jest.fn().mockRejectedValue(mockReadResult()); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest.fn().mockRejectedValue(undefined), + }), + readTree: jest.fn(), + search: jest.fn(), + }; await expect( readCodeOwners(reader, sourceUrl, ['.github/CODEOWNERS']), @@ -61,22 +59,31 @@ describe('readCodeOwners', () => { }); it('should look at multiple locations', async () => { - const ownersText = mockCodeowners; - const read = jest - .fn() - .mockImplementationOnce(() => mockReadResult({ error: 'not found' })) - .mockResolvedValue(mockReadResult({ data: ownersText })); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest + .fn() + .mockRejectedValue(new NotFoundError('not found')) + .mockResolvedValue(mockCodeowners), + }), + readTree: jest.fn(), + search: jest.fn(), + }; const result = await readCodeOwners(reader, sourceUrl, [ '.github/CODEOWNERS', 'docs/CODEOWNERS', ]); - expect(read.mock.calls.length).toBe(2); - expect(read.mock.calls[0]).toEqual([`${sourceUrl}.github/CODEOWNERS`]); - expect(read.mock.calls[1]).toEqual([`${sourceUrl}docs/CODEOWNERS`]); - expect(result).toEqual(ownersText); + expect(reader.readUrl.mock.calls.length).toBe(2); + expect(reader.readUrl.mock.calls[0]).toEqual([ + `${sourceUrl}.github/CODEOWNERS`, + ]); + expect(reader.readUrl.mock.calls[1]).toEqual([ + `${sourceUrl}docs/CODEOWNERS`, + ]); + expect(result).toEqual(mockCodeowners); }); }); @@ -85,15 +92,18 @@ describe('findCodeOwnerByLocation', () => { target = 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', codeownersContents: codeOwnersContents = mockCodeowners, }: { target?: string; codeownersContents?: string } = {}) => { - const read = jest - .fn() - .mockResolvedValue(mockReadResult({ data: codeOwnersContents })); - const scmIntegration = ScmIntegrations.fromConfig( new ConfigReader({}), ).byUrl(target); - const reader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader = { + read: jest.fn(), + readUrl: jest.fn().mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(codeOwnersContents), + }), + readTree: jest.fn(), + search: jest.fn(), + }; return { target, reader, scmIntegration, codeOwnersContents }; }; diff --git a/plugins/catalog-backend/src/modules/codeowners/lib/read.ts b/plugins/catalog-backend/src/modules/codeowners/lib/read.ts index c6b89efcc9..2987e162cc 100644 --- a/plugins/catalog-backend/src/modules/codeowners/lib/read.ts +++ b/plugins/catalog-backend/src/modules/codeowners/lib/read.ts @@ -28,14 +28,9 @@ export async function readCodeOwners( ): Promise { const readOwnerLocation = async (path: string): Promise => { const url = `${sourceUrl}${path}`; - - if (reader.readUrl) { - const data = await reader.readUrl(url); - const buffer = await data.buffer(); - return buffer.toString(); - } - const data = await reader.read(url); - return data.toString(); + const data = await reader.readUrl(url); + const buffer = await data.buffer(); + return buffer.toString(); }; const candidates = codeownersPaths.map(readOwnerLocation); diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts index 105ab03d90..903ac1ac6a 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.test.ts @@ -31,8 +31,12 @@ import { const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); describe('PlaceholderProcessor', () => { - const read: jest.MockedFunction = jest.fn(); - const reader: UrlReader = { read, readTree: jest.fn(), search: jest.fn() }; + const reader: jest.Mocked = { + read: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + readUrl: jest.fn(), + }; beforeEach(() => { jest.resetAllMocks(); @@ -86,7 +90,7 @@ describe('PlaceholderProcessor', () => { spec: { a: [{ b: 'TEXT' }] }, }); - expect(read).not.toHaveBeenCalled(); + expect(reader.readUrl).not.toHaveBeenCalled(); expect(upperResolver).toHaveBeenCalledWith( expect.objectContaining({ key: 'upper', @@ -115,7 +119,7 @@ describe('PlaceholderProcessor', () => { processor.preProcessEntity(entity, { type: 'a', target: 'b' }, () => {}), ).resolves.toEqual(entity); - expect(read).not.toHaveBeenCalled(); + expect(reader.readUrl).not.toHaveBeenCalled(); }); it('ignores unknown placeholders', async () => { @@ -136,11 +140,13 @@ describe('PlaceholderProcessor', () => { processor.preProcessEntity(entity, { type: 'a', target: 'b' }, () => {}), ).resolves.toEqual(entity); - expect(read).not.toHaveBeenCalled(); + expect(reader.readUrl).not.toHaveBeenCalled(); }); it('works with the text resolver', async () => { - read.mockResolvedValue(Buffer.from('TEXT', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from('TEXT', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { text: textPlaceholderResolver }, reader, @@ -169,15 +175,19 @@ describe('PlaceholderProcessor', () => { spec: { data: 'TEXT' }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/a/file.txt', ); }); it('works with the json resolver', async () => { - read.mockResolvedValue( - Buffer.from(JSON.stringify({ a: ['b', 7] }), 'utf-8'), - ); + reader.readUrl.mockResolvedValue({ + buffer: jest + .fn() + .mockResolvedValue( + Buffer.from(JSON.stringify({ a: ['b', 7] }), 'utf-8'), + ), + }); const processor = new PlaceholderProcessor({ resolvers: { json: jsonPlaceholderResolver }, reader, @@ -206,13 +216,17 @@ describe('PlaceholderProcessor', () => { spec: { data: { a: ['b', 7] } }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/a/b/file.json', ); }); it('works with the yaml resolver', async () => { - read.mockResolvedValue(Buffer.from('foo:\n - bar: 7', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest + .fn() + .mockResolvedValue(Buffer.from('foo:\n - bar: 7', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { yaml: yamlPlaceholderResolver }, reader, @@ -241,13 +255,15 @@ describe('PlaceholderProcessor', () => { spec: { data: { foo: [{ bar: 7 }] } }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/a/file.yaml', ); }); it('resolves absolute path for absolute location', async () => { - read.mockResolvedValue(Buffer.from('TEXT', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from('TEXT', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { text: textPlaceholderResolver }, reader, @@ -280,13 +296,15 @@ describe('PlaceholderProcessor', () => { spec: { data: 'TEXT' }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/catalog-info.yaml', ); }); it('resolves absolute path for relative file location', async () => { - read.mockResolvedValue(Buffer.from('TEXT', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from('TEXT', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { text: textPlaceholderResolver }, reader, @@ -318,7 +336,7 @@ describe('PlaceholderProcessor', () => { spec: { data: 'TEXT' }, }); - expect(read).toHaveBeenCalledWith( + expect(reader.readUrl).toHaveBeenCalledWith( 'https://github.com/backstage/backstage/catalog-info.yaml', ); }); @@ -327,7 +345,9 @@ describe('PlaceholderProcessor', () => { // We explicitly don't support this case, as it would allow for file system // traversal attacks. If we want to implement this, we need to have additional // security measures in place! - read.mockResolvedValue(Buffer.from('TEXT', 'utf-8')); + reader.readUrl.mockResolvedValue({ + buffer: jest.fn().mockResolvedValue(Buffer.from('TEXT', 'utf-8')), + }); const processor = new PlaceholderProcessor({ resolvers: { text: textPlaceholderResolver }, reader, @@ -356,12 +376,16 @@ describe('PlaceholderProcessor', () => { /^Placeholder \$text could not form a URL out of \.\/a\/b\/catalog-info\.yaml and \.\.\/c\/catalog-info\.yaml, TypeError \[ERR_INVALID_URL\]/, ); - expect(read).not.toHaveBeenCalled(); + expect(reader.readUrl).not.toHaveBeenCalled(); }); it('should emit the resolverValue as a refreshKey', async () => { - read.mockResolvedValue( - Buffer.from(JSON.stringify({ a: ['b', 7] }), 'utf-8'), - ); + reader.readUrl.mockResolvedValue({ + buffer: jest + .fn() + .mockResolvedValue( + Buffer.from(JSON.stringify({ a: ['b', 7] }), 'utf-8'), + ), + }); const processor = new PlaceholderProcessor({ resolvers: { diff --git a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts index 5e49c67915..e6b5ee279d 100644 --- a/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/PlaceholderProcessor.ts @@ -120,12 +120,9 @@ export class PlaceholderProcessor implements CatalogProcessor { } const read = async (url: string): Promise => { - if (this.options.reader.readUrl) { - const response = await this.options.reader.readUrl(url); - const buffer = await response.buffer(); - return buffer; - } - return this.options.reader.read(url); + const response = await this.options.reader.readUrl(url); + const buffer = await response.buffer(); + return buffer; }; const resolveUrl = (url: string, base: string): string => diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts index cf14ea22e0..14c5710afc 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.test.ts @@ -195,6 +195,7 @@ describe('UrlReaderProcessor', () => { const reader: jest.Mocked = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn().mockImplementation(async () => []), }; diff --git a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts index 491f643637..fe9f21ebb4 100644 --- a/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/modules/core/UrlReaderProcessor.ts @@ -135,16 +135,10 @@ export class UrlReaderProcessor implements CatalogProcessor { return { response: await Promise.all(output), etag: response.etag }; } - // Otherwise do a plain read, prioritizing readUrl if available - if (this.options.reader.readUrl) { - const data = await this.options.reader.readUrl(location, { etag }); - return { - response: [{ url: location, data: await data.buffer() }], - etag: data.etag, - }; - } - - const data = await this.options.reader.read(location); - return { response: [{ url: location, data }] }; + const data = await this.options.reader.readUrl(location, { etag }); + return { + response: [{ url: location, data: await data.buffer() }], + etag: data.etag, + }; } } diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts index cb11add9ed..5b34c98ca6 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts @@ -41,7 +41,9 @@ describe('createPropertyRule', () => { name: 'test-component', }, }, - 'org.name', + { + key: 'org.name', + }, ), ).toBe(false); }); @@ -57,7 +59,9 @@ describe('createPropertyRule', () => { tags: [], }, }, - 'tags', + { + key: 'tags', + }, ), ).toBe(false); }); @@ -75,7 +79,9 @@ describe('createPropertyRule', () => { }, }, }, - 'org.name', + { + key: 'org.name', + }, ), ).toBe(true); }); @@ -91,7 +97,9 @@ describe('createPropertyRule', () => { tags: ['java'], }, }, - 'tags', + { + key: 'tags', + }, ), ).toBe(true); }); @@ -108,8 +116,10 @@ describe('createPropertyRule', () => { name: 'test-component', }, }, - 'org.name', - 'test-org', + { + key: 'org.name', + value: 'test-org', + }, ), ).toBe(false); }); @@ -127,8 +137,10 @@ describe('createPropertyRule', () => { }, }, }, - 'org.name', - 'test-org', + { + key: 'org.name', + value: 'test-org', + }, ), ).toBe(false); }); @@ -144,8 +156,10 @@ describe('createPropertyRule', () => { tags: ['java'], }, }, - 'tags', - 'python', + { + key: 'tags', + value: 'python', + }, ), ).toBe(false); }); @@ -163,8 +177,10 @@ describe('createPropertyRule', () => { }, }, }, - 'org.name', - 'test-org', + { + key: 'org.name', + value: 'test-org', + }, ), ).toBe(true); }); @@ -180,8 +196,10 @@ describe('createPropertyRule', () => { tags: ['java', 'java11'], }, }, - 'tags', - 'java', + { + key: 'tags', + value: 'java', + }, ), ).toBe(true); }); @@ -190,7 +208,11 @@ describe('createPropertyRule', () => { describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(toQuery('backstage.io/test-component')).toEqual({ + expect( + toQuery({ + key: 'backstage.io/test-component', + }), + ).toEqual({ key: 'metadata.backstage.io/test-component', }); }); diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 57de5eb922..19a4423846 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -15,16 +15,25 @@ */ import { get } from 'lodash'; -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { createCatalogPermissionRule } from './util'; +import { z } from 'zod'; export const createPropertyRule = (propertyType: 'metadata' | 'spec') => createCatalogPermissionRule({ name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply: (resource: Entity, key: string, value?: string) => { + paramsSchema: z.object({ + key: z + .string() + .describe(`Property within the entities ${propertyType} to match on`), + value: z + .string() + .optional() + .describe(`Value of the given property to match on`), + }), + apply: (resource, { key, value }) => { const foundValue = get(resource[propertyType], key); if (Array.isArray(foundValue)) { @@ -38,7 +47,7 @@ export const createPropertyRule = (propertyType: 'metadata' | 'spec') => } return !!foundValue; }, - toQuery: (key: string, value?: string) => ({ + toQuery: ({ key, value }) => ({ key: `${propertyType}.${key}`, ...(value !== undefined && { values: [value] }), }), diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts index 609114be3d..73fce20a71 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts @@ -31,7 +31,9 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', + { + annotation: 'backstage.io/test-annotation', + }, ), ).toEqual(false); }); @@ -46,7 +48,9 @@ describe('hasAnnotation permission rule', () => { name: 'test-component', }, }, - 'backstage.io/test-annotation', + { + annotation: 'backstage.io/test-annotation', + }, ), ).toEqual(false); expect( @@ -58,8 +62,10 @@ describe('hasAnnotation permission rule', () => { name: 'test-component', }, }, - 'backstage.io/test-annotation', - 'some value', + { + annotation: 'backstage.io/test-annotation', + value: 'some value', + }, ), ).toEqual(false); }); @@ -78,7 +84,9 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', + { + annotation: 'backstage.io/test-annotation', + }, ), ).toEqual(true); }); @@ -97,8 +105,10 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', - 'baz', + { + annotation: 'backstage.io/test-annotation', + value: 'baz', + }, ), ).toEqual(false); }); @@ -117,8 +127,10 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', - 'bar', + { + annotation: 'backstage.io/test-annotation', + value: 'bar', + }, ), ).toEqual(true); }); @@ -126,7 +138,11 @@ describe('hasAnnotation permission rule', () => { describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(hasAnnotation.toQuery('backstage.io/test-annotation')).toEqual({ + expect( + hasAnnotation.toQuery({ + annotation: 'backstage.io/test-annotation', + }), + ).toEqual({ key: 'metadata.annotations.backstage.io/test-annotation', }); }); @@ -134,7 +150,10 @@ describe('hasAnnotation permission rule', () => { it('returns an appropriate catalog-backend filter with values', () => { expect( - hasAnnotation.toQuery('backstage.io/test-annotation', 'foo'), + hasAnnotation.toQuery({ + annotation: 'backstage.io/test-annotation', + value: 'foo', + }), ).toEqual({ key: 'metadata.annotations.backstage.io/test-annotation', values: ['foo'], diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index 22dbd307a6..c24620f41c 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; /** @@ -31,12 +31,19 @@ export const hasAnnotation = createCatalogPermissionRule({ description: 'Allow entities which are annotated with the specified annotation', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply: (resource: Entity, annotation: string, value?: string) => + paramsSchema: z.object({ + annotation: z.string().describe('Name of the annotation to match on'), + value: z + .string() + .optional() + .describe('Value of the annotation to match on'), + }), + apply: (resource, { annotation, value }) => !!resource.metadata.annotations?.hasOwnProperty(annotation) && (value === undefined ? true : resource.metadata.annotations?.[annotation] === value), - toQuery: (annotation: string, value?: string) => + toQuery: ({ annotation, value }) => value === undefined ? { key: `metadata.annotations.${annotation}`, diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts index a1b9cb5ad0..8aa43402c5 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts @@ -31,7 +31,9 @@ describe('hasLabel permission rule', () => { }, }, }, - 'backstage.io/testlabel', + { + label: 'backstage.io/testlabel', + }, ), ).toEqual(false); }); @@ -46,7 +48,9 @@ describe('hasLabel permission rule', () => { name: 'test-component', }, }, - 'backstage.io/testlabel', + { + label: 'backstage.io/testlabel', + }, ), ).toEqual(false); }); @@ -65,7 +69,7 @@ describe('hasLabel permission rule', () => { }, }, }, - 'backstage.io/testlabel', + { label: 'backstage.io/testlabel' }, ), ).toEqual(true); }); @@ -73,7 +77,11 @@ describe('hasLabel permission rule', () => { describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(hasLabel.toQuery('backstage.io/testlabel')).toEqual({ + expect( + hasLabel.toQuery({ + label: 'backstage.io/testlabel', + }), + ).toEqual({ key: 'metadata.labels.backstage.io/testlabel', }); }); diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index 9f37cbfb59..376534ca6f 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; /** @@ -27,9 +27,12 @@ export const hasLabel = createCatalogPermissionRule({ name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply: (resource: Entity, label: string) => + paramsSchema: z.object({ + label: z.string().describe('Name of the label to match one'), + }), + apply: (resource, { label }) => !!resource.metadata.labels?.hasOwnProperty(label), - toQuery: (label: string) => ({ + toQuery: ({ label }) => ({ key: `metadata.labels.${label}`, }), }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts index e11e102728..28983f6bec 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts @@ -27,7 +27,11 @@ describe('isEntityKind', () => { name: 'some-component', }, }; - expect(isEntityKind.apply(component, ['b'])).toBe(true); + expect( + isEntityKind.apply(component, { + kinds: ['b'], + }), + ).toBe(true); }); it('returns false when entity is not the correct kind', () => { @@ -38,13 +42,21 @@ describe('isEntityKind', () => { name: 'some-component', }, }; - expect(isEntityKind.apply(component, ['c'])).toBe(false); + expect( + isEntityKind.apply(component, { + kinds: ['c'], + }), + ).toBe(false); }); }); describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(isEntityKind.toQuery(['b'])).toEqual({ + expect( + isEntityKind.toQuery({ + kinds: ['b'], + }), + ).toEqual({ key: 'kind', values: ['b'], }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 3bd600e8fe..eee7a790b2 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { EntitiesSearchFilter } from '../../catalog/types'; import { createCatalogPermissionRule } from './util'; @@ -27,11 +27,16 @@ export const isEntityKind = createCatalogPermissionRule({ name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply(resource: Entity, kinds: string[]) { + paramsSchema: z.object({ + kinds: z + .array(z.string()) + .describe('List of kinds to match at least one of'), + }), + apply(resource, { kinds }) { const resourceKind = resource.kind.toLocaleLowerCase('en-US'); return kinds.some(kind => kind.toLocaleLowerCase('en-US') === resourceKind); }, - toQuery(kinds: string[]): EntitiesSearchFilter { + toQuery({ kinds }): EntitiesSearchFilter { return { key: 'kind', values: kinds.map(kind => kind.toLocaleLowerCase('en-US')), diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts index 11304c768c..f09e484c32 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts @@ -33,9 +33,11 @@ describe('isEntityOwner', () => { }, ], }; - expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( - true, - ); + expect( + isEntityOwner.apply(component, { + claims: ['user:default/spiderman'], + }), + ).toBe(true); }); it('returns false when entity is not owned by the given user', () => { @@ -52,9 +54,11 @@ describe('isEntityOwner', () => { }, ], }; - expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( - false, - ); + expect( + isEntityOwner.apply(component, { + claims: ['user:default/spiderman'], + }), + ).toBe(false); }); it('returns false when entity does not have an owner', () => { @@ -65,15 +69,21 @@ describe('isEntityOwner', () => { name: 'some-component', }, }; - expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( - false, - ); + expect( + isEntityOwner.apply(component, { + claims: ['user:default/spiderman'], + }), + ).toBe(false); }); }); describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(isEntityOwner.toQuery(['user:default/spiderman'])).toEqual({ + expect( + isEntityOwner.toQuery({ + claims: ['user:default/spiderman'], + }), + ).toEqual({ key: 'relations.ownedBy', values: ['user:default/spiderman'], }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index 5403da2310..c3bfff5c2e 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { RELATION_OWNED_BY } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; /** @@ -28,7 +29,14 @@ export const isEntityOwner = createCatalogPermissionRule({ name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply: (resource: Entity, claims: string[]) => { + paramsSchema: z.object({ + claims: z + .array(z.string()) + .describe( + `List of claims to match at least one on within ${RELATION_OWNED_BY}`, + ), + }), + apply: (resource, { claims }) => { if (!resource.relations) { return false; } @@ -37,7 +45,7 @@ export const isEntityOwner = createCatalogPermissionRule({ .filter(relation => relation.type === RELATION_OWNED_BY) .some(relation => claims.includes(relation.targetRef)); }, - toQuery: (claims: string[]) => ({ + toQuery: ({ claims }) => ({ key: 'relations.ownedBy', values: claims, }), diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index 36b351595d..ea25e115d2 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { makeCreatePermissionRule, PermissionRule, @@ -29,8 +30,9 @@ import { EntitiesSearchFilter } from '../../catalog/types'; * * @alpha */ -export type CatalogPermissionRule = - PermissionRule; +export type CatalogPermissionRule< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule; /** * Helper function for creating correctly-typed diff --git a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts index b15d1df4bb..66764dc89f 100644 --- a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts @@ -65,7 +65,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); const catalog = createCatalog(isEntityKind); @@ -117,7 +117,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); fakeCatalog.entities.mockResolvedValue({ entities: [] }); @@ -136,7 +136,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); fakeCatalog.entities.mockResolvedValue({ @@ -272,7 +272,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); const catalog = createCatalog(isEntityKind); diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 16a36eb8c4..f383df7364 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -36,6 +36,7 @@ import { } from '@backstage/plugin-permission-node'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { CatalogProcessingOrchestrator } from '../processing/types'; +import { z } from 'zod'; describe('createRouter readonly disabled', () => { let entitiesCatalog: jest.Mocked; @@ -695,6 +696,9 @@ describe('NextRouter permissioning', () => { name: 'FAKE_RULE', description: 'fake rule', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + paramsSchema: z.object({ + foo: z.string(), + }), apply: () => true, toQuery: () => ({ key: '', values: [] }), }); @@ -758,7 +762,9 @@ describe('NextRouter permissioning', () => { conditions: { rule: 'FAKE_RULE', resourceType: 'catalog-entity', - params: ['user:default/spiderman'], + params: { + foo: 'user:default/spiderman', + }, }, }, ], diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 04fb82c760..d1378e9418 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-catalog-common +## 1.0.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.0.7 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + +## 1.0.7-next.2 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + ## 1.0.7-next.1 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 12dcbc4ccf..8af6d06c5a 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-common", "description": "Common functionalities for the catalog plugin", - "version": "1.0.7-next.1", + "version": "1.0.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-customized/CHANGELOG.md b/plugins/catalog-customized/CHANGELOG.md index 8ba2f88dce..04be429ab6 100644 --- a/plugins/catalog-customized/CHANGELOG.md +++ b/plugins/catalog-customized/CHANGELOG.md @@ -1,5 +1,29 @@ # @internal/plugin-catalog-customized +## 0.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + +## 0.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + +## 0.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + ## 0.0.3-next.1 ### Patch Changes diff --git a/plugins/catalog-customized/package.json b/plugins/catalog-customized/package.json index ddd80bee43..537180f05e 100644 --- a/plugins/catalog-customized/package.json +++ b/plugins/catalog-customized/package.json @@ -1,7 +1,7 @@ { "name": "@internal/plugin-catalog-customized", "description": "The internal Backstage Customizable plugin for browsing the Backstage catalog", - "version": "0.0.3-next.1", + "version": "0.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 724a4bf1a2..7f8e1e0277 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-catalog-graph +## 0.2.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## 0.2.22 + +### Patch Changes + +- bde1e8c8e2: Added `curve` prop to the `DependencyGraph` component to select the type of layout +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.2.22-next.1 ### Patch Changes diff --git a/plugins/catalog-graph/api-report.md b/plugins/catalog-graph/api-report.md index ed4ca2a2c9..95f497fe95 100644 --- a/plugins/catalog-graph/api-report.md +++ b/plugins/catalog-graph/api-report.md @@ -30,6 +30,7 @@ export const CatalogGraphPage: (props: { mergeRelations?: boolean | undefined; direction?: Direction | undefined; showFilters?: boolean | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; } | undefined; }) => JSX.Element; @@ -120,6 +121,7 @@ export const EntityRelationsGraph: (props: { renderLabel?: | DependencyGraphTypes.RenderLabelFunction | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; }) => JSX.Element; // @public diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 8fd71845c2..3b78d5d7ba 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.2.22-next.1", + "version": "0.2.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index 8e59c127d9..a6a6c2a711 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -40,6 +40,7 @@ import { EntityRelationsGraph, RelationPairs, } from '../EntityRelationsGraph'; +import { CurveFilter } from './CurveFilter'; import { DirectionFilter } from './DirectionFilter'; import { MaxDepthFilter } from './MaxDepthFilter'; import { SelectedKindsFilter } from './SelectedKindsFilter'; @@ -110,6 +111,7 @@ export const CatalogGraphPage = (props: { mergeRelations?: boolean; direction?: Direction; showFilters?: boolean; + curve?: 'curveStepBefore' | 'curveMonotoneX'; }; }) => { const { relationPairs = ALL_RELATION_PAIRS, initialState } = props; @@ -130,6 +132,8 @@ export const CatalogGraphPage = (props: { setMergeRelations, direction, setDirection, + curve, + setCurve, rootEntityNames, setRootEntityNames, showFilters, @@ -201,6 +205,7 @@ export const CatalogGraphPage = (props: { relationPairs={relationPairs} /> + diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx new file mode 100644 index 0000000000..0be0fca66b --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { CurveFilter } from './CurveFilter'; + +describe('', () => { + test('should display current curve label', () => { + const onChange = jest.fn(); + const { getByText } = render( + , + ); + + expect(getByText('Monotone X')).toBeInTheDocument(); + }); + + test('should select an alternative curve factory', async () => { + const onChange = jest.fn(); + const { getByText, getByTestId } = render( + , + ); + + expect(getByText('Step Before')).toBeInTheDocument(); + + await userEvent.click(getByTestId('select')); + await userEvent.click(getByText('Monotone X')); + + await waitFor(() => { + expect(getByText('Monotone X')).toBeInTheDocument(); + expect(onChange).toHaveBeenCalledWith('curveMonotoneX'); + }); + }); +}); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx new file mode 100644 index 0000000000..6cda334cf1 --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Select } from '@backstage/core-components'; +import { Box } from '@material-ui/core'; +import React, { useCallback } from 'react'; + +type Curve = 'curveStepBefore' | 'curveMonotoneX'; +const CURVE_DISPLAY_NAMES: Record = { + curveMonotoneX: 'Monotone X', + curveStepBefore: 'Step Before', +}; + +export type Props = { + value: Curve; + onChange: (value: 'curveStepBefore' | 'curveMonotoneX') => void; +}; + +const curves: Array = ['curveMonotoneX', 'curveStepBefore']; + +export const CurveFilter = ({ value, onChange }: Props) => { + const handleChange = useCallback(v => onChange(v as Curve), [onChange]); + + return ( + +
- No incidents right now + No alerts right now } title={ !compact ? ( ) : ( - INCIDENTS + ALERTS ) } page={tableState.page} - totalCount={incidentsCount} + totalCount={alertsCount} onPageChange={onChangePage} onRowsPerPageChange={onChangeRowsPerPage} - // localization={{ header: { actions: undefined } }} columns={columns} - data={incidents} + data={alerts} isLoading={isLoading} /> ); diff --git a/plugins/ilert/src/components/IncidentsPage/StatusChip.tsx b/plugins/ilert/src/components/AlertsPage/StatusChip.tsx similarity index 79% rename from plugins/ilert/src/components/IncidentsPage/StatusChip.tsx rename to plugins/ilert/src/components/AlertsPage/StatusChip.tsx index 98521234de..55fe46d810 100644 --- a/plugins/ilert/src/components/IncidentsPage/StatusChip.tsx +++ b/plugins/ilert/src/components/AlertsPage/StatusChip.tsx @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { Chip, withStyles } from '@material-ui/core'; -import { Incident, PENDING, ACCEPTED, RESOLVED } from '../../types'; -import { incidentStatusLabels } from '../Incident/IncidentStatus'; +import React from 'react'; +import { ACCEPTED, Alert, PENDING, RESOLVED } from '../../types'; const ResolvedChip = withStyles({ root: { @@ -41,10 +40,16 @@ const PendingChip = withStyles({ }, })(Chip); -export const StatusChip = ({ incident }: { incident: Incident }) => { - const label = `${incidentStatusLabels[incident.status]}`; +export const alertStatusLabels = { + [RESOLVED]: 'Resolved', + [ACCEPTED]: 'Accepted', + [PENDING]: 'Pending', +} as Record; - switch (incident.status) { +export const StatusChip = ({ alert }: { alert: Alert }) => { + const label = `${alertStatusLabels[alert.status]}`; + + switch (alert.status) { case RESOLVED: return ; case ACCEPTED: diff --git a/plugins/ilert/src/components/IncidentsPage/TableTitle.tsx b/plugins/ilert/src/components/AlertsPage/TableTitle.tsx similarity index 76% rename from plugins/ilert/src/components/IncidentsPage/TableTitle.tsx rename to plugins/ilert/src/components/AlertsPage/TableTitle.tsx index 8b8fde87f7..9aab95c6f3 100644 --- a/plugins/ilert/src/components/IncidentsPage/TableTitle.tsx +++ b/plugins/ilert/src/components/AlertsPage/TableTitle.tsx @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { PENDING, ACCEPTED, RESOLVED, IncidentStatus } from '../../types'; -import { incidentStatusLabels } from '../Incident/IncidentStatus'; +import Checkbox from '@material-ui/core/Checkbox'; import FormControl from '@material-ui/core/FormControl'; import ListItemText from '@material-ui/core/ListItemText'; -import Select from '@material-ui/core/Select'; -import Typography from '@material-ui/core/Typography'; import MenuItem from '@material-ui/core/MenuItem'; -import Checkbox from '@material-ui/core/Checkbox'; +import Select from '@material-ui/core/Select'; import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { ACCEPTED, AlertStatus, PENDING, RESOLVED } from '../../types'; +import { alertStatusLabels } from './StatusChip'; const ITEM_HEIGHT = 48; const ITEM_PADDING_TOP = 8; @@ -53,15 +53,15 @@ const useStyles = makeStyles({ }); export const TableTitle = ({ - incidentStates, - onIncidentStatesChange, + alertStates, + onAlertStatesChange, }: { - incidentStates: IncidentStatus[]; - onIncidentStatesChange: (states: IncidentStatus[]) => void; + alertStates: AlertStatus[]; + onAlertStatesChange: (states: AlertStatus[]) => void; }) => { const classes = useStyles(); - const handleIncidentStatusSelectChange = (event: any) => { - onIncidentStatesChange(event.target.value); + const handleAlertStatusSelectChange = (event: any) => { + onAlertStatesChange(event.target.value); }; return ( @@ -75,19 +75,19 @@ export const TableTitle = ({ size="small" > diff --git a/plugins/ilert/src/components/IncidentsPage/index.ts b/plugins/ilert/src/components/AlertsPage/index.ts similarity index 90% rename from plugins/ilert/src/components/IncidentsPage/index.ts rename to plugins/ilert/src/components/AlertsPage/index.ts index a5ad4e65e6..c42d558dca 100644 --- a/plugins/ilert/src/components/IncidentsPage/index.ts +++ b/plugins/ilert/src/components/AlertsPage/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './IncidentsPage'; -export * from './IncidentsTable'; +export * from './AlertsPage'; +export * from './AlertsTable'; diff --git a/plugins/ilert/src/components/ILertCard/ILertCard.tsx b/plugins/ilert/src/components/ILertCard/ILertCard.tsx index 6330189a01..204c32369c 100644 --- a/plugins/ilert/src/components/ILertCard/ILertCard.tsx +++ b/plugins/ilert/src/components/ILertCard/ILertCard.tsx @@ -13,27 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { Entity } from '@backstage/catalog-model'; +import { ResponseErrorPanel } from '@backstage/core-components'; import { AuthenticationError } from '@backstage/errors'; import Card from '@material-ui/core/Card'; import CardContent from '@material-ui/core/CardContent'; import CardHeader from '@material-ui/core/CardHeader'; import Divider from '@material-ui/core/Divider'; import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; import { ILERT_INTEGRATION_KEY_ANNOTATION } from '../../constants'; -import { MissingAuthorizationHeaderError } from '../Errors'; -import { useIncidents } from '../../hooks/useIncidents'; -import { IncidentsTable } from '../IncidentsPage'; -import { IncidentNewModal } from '../Incident/IncidentNewModal'; -import { ILertCardActionsHeader } from './ILertCardActionsHeader'; -import { useAlertSource } from '../../hooks/useAlertSource'; import { useILertEntity } from '../../hooks'; +import { useAlerts } from '../../hooks/useAlerts'; +import { useAlertSource } from '../../hooks/useAlertSource'; +import { AlertNewModal } from '../Alert/AlertNewModal'; +import { AlertsTable } from '../AlertsPage'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { ILertCardActionsHeader } from './ILertCardActionsHeader'; +import { ILertCardEmptyState } from './ILertCardEmptyState'; import { ILertCardHeaderStatus } from './ILertCardHeaderStatus'; import { ILertCardMaintenanceModal } from './ILertCardMaintenanceModal'; -import { ILertCardEmptyState } from './ILertCardEmptyState'; import { ILertCardOnCall } from './ILertCardOnCall'; -import { ResponseErrorPanel } from '@backstage/core-components'; /** @public */ export const isPluginApplicableToEntity = (entity: Entity) => @@ -55,23 +55,21 @@ const useStyles = makeStyles({ export const ILertCard = () => { const classes = useStyles(); const { integrationKey, name } = useILertEntity(); + const [{ alertSource }, { setAlertSource, refetchAlertSource }] = + useAlertSource(integrationKey); const [ - { alertSource, uptimeMonitor }, - { setAlertSource, refetchAlertSource }, - ] = useAlertSource(integrationKey); - const [ - { tableState, states, incidents, incidentsCount, isLoading, error }, + { tableState, states, alerts, alertsCount, isLoading, error }, { - onIncidentStatesChange, + onAlertStatesChange, onChangePage, onChangeRowsPerPage, - onIncidentChanged, - refetchIncidents, + onAlertChanged, + refetchAlerts, setIsLoading, }, - ] = useIncidents(false, true, alertSource); + ] = useAlerts(false, true, alertSource); - const [isNewIncidentModalOpened, setIsNewIncidentModalOpened] = + const [isNewAlertModalOpened, setIsNewAlertModalOpened] = React.useState(false); const [isMaintenanceModalOpened, setIsMaintenanceModalOpened] = React.useState(false); @@ -97,9 +95,8 @@ export const ILertCard = () => { } action={} @@ -107,13 +104,13 @@ export const ILertCard = () => { - { /> - diff --git a/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx index 4e0b562369..85118b29b9 100644 --- a/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx +++ b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx @@ -13,49 +13,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import Alert from '@material-ui/lab/Alert'; import Button from '@material-ui/core/Button'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; import DialogTitle from '@material-ui/core/DialogTitle'; +import Typography from '@material-ui/core/Typography'; import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; import BuildIcon from '@material-ui/icons/Build'; import PauseIcon from '@material-ui/icons/Pause'; import PlayArrowIcon from '@material-ui/icons/PlayArrow'; -import TimelineIcon from '@material-ui/icons/Timeline'; import WebIcon from '@material-ui/icons/Web'; -import Typography from '@material-ui/core/Typography'; +import Alert from '@material-ui/lab/Alert'; +import React from 'react'; import { ilertApiRef } from '../../api'; -import { AlertSource, UptimeMonitor } from '../../types'; +import { AlertSource } from '../../types'; import { HeaderIconLinkRow, IconLinkVerticalProps, } from '@backstage/core-components'; -import { useApi, alertApiRef } from '@backstage/core-plugin-api'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; export const ILertCardActionsHeader = ({ alertSource, setAlertSource, - setIsNewIncidentModalOpened, + setIsNewAlertModalOpened, setIsMaintenanceModalOpened, - uptimeMonitor, }: { alertSource: AlertSource | null; setAlertSource: (alertSource: AlertSource) => void; - setIsNewIncidentModalOpened: (isOpen: boolean) => void; + setIsNewAlertModalOpened: (isOpen: boolean) => void; setIsMaintenanceModalOpened: (isOpen: boolean) => void; - uptimeMonitor: UptimeMonitor | null; }) => { const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const [isLoading, setIsLoading] = React.useState(false); const [isDisableModalOpened, setIsDisableModalOpened] = React.useState(false); - const handleCreateNewIncident = () => { - setIsNewIncidentModalOpened(true); + const handleCreateNewAlert = () => { + setIsNewAlertModalOpened(true); }; const handleEnableAlertSource = async () => { @@ -108,9 +105,9 @@ export const ILertCardActionsHeader = ({ icon: , }; - const createIncidentLink: IconLinkVerticalProps = { - label: 'Create Incident', - onClick: handleCreateNewIncident, + const createAlertLink: IconLinkVerticalProps = { + label: 'Create Alert', + onClick: handleCreateNewAlert, icon: , color: 'secondary', disabled: @@ -140,25 +137,14 @@ export const ILertCardActionsHeader = ({ disabled: !alertSource || isLoading, }; - const uptimeMonitorReportLink: IconLinkVerticalProps = { - label: 'Uptime Report', - href: uptimeMonitor ? uptimeMonitor.shareUrl : '', - icon: , - disabled: !alertSource || !uptimeMonitor || isLoading, - }; - const links: IconLinkVerticalProps[] = [ alertSourceLink, - createIncidentLink, + createAlertLink, alertSource && alertSource.active ? disableAlertSourceLink : enableAlertSourceLink, ]; - if (alertSource && alertSource.integrationType === 'MONITOR') { - links.push(uptimeMonitorReportLink); - } - if (alertSource && alertSource.status !== 'IN_MAINTENANCE') { links.push(maintenanceAlertSourceLink); } @@ -178,7 +164,7 @@ export const ILertCardActionsHeader = ({ Do you really want to disable this alert source? A disabled alert - source cannot create new incidents. + source cannot create new alerts. diff --git a/plugins/ilert/src/components/ILertPage/ILertPage.tsx b/plugins/ilert/src/components/ILertPage/ILertPage.tsx index 89dae215e4..5b95677ce8 100644 --- a/plugins/ilert/src/components/ILertPage/ILertPage.tsx +++ b/plugins/ilert/src/components/ILertPage/ILertPage.tsx @@ -13,34 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { IncidentsPage } from '../IncidentsPage'; -import { UptimeMonitorsPage } from '../UptimeMonitorsPage'; -import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; import { - Page, - Header, - HeaderTabs, - HeaderLabel, Content, + Header, + HeaderLabel, + HeaderTabs, + Page, } from '@backstage/core-components'; +import React from 'react'; +import { AlertsPage } from '../AlertsPage'; +import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; +import { ServicesPage } from '../ServicesPage'; +import { StatusPagesPage } from '../StatusPagePage'; /** @public */ export const ILertPage = () => { const [selectedTab, setSelectedTab] = React.useState(0); const tabs = [ { label: 'Who is on call?' }, - { label: 'Incidents' }, - { label: 'Uptime Monitors' }, + { label: 'Alerts' }, + { label: 'Services' }, + { label: 'Status pages' }, ]; const renderTab = () => { switch (selectedTab) { case 0: return ; case 1: - return ; + return ; case 2: - return ; + return ; + case 3: + return ; default: return null; } diff --git a/plugins/ilert/src/components/Incident/IncidentStatus.tsx b/plugins/ilert/src/components/Incident/IncidentStatus.tsx deleted file mode 100644 index e6eb1525f7..0000000000 --- a/plugins/ilert/src/components/Incident/IncidentStatus.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import Tooltip from '@material-ui/core/Tooltip'; -import { ACCEPTED, Incident, PENDING, RESOLVED } from '../../types'; -import { StatusError, StatusOK } from '@backstage/core-components'; - -const useStyles = makeStyles({ - denseListIcon: { - marginRight: 0, - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', - }, -}); - -export const incidentStatusLabels = { - [RESOLVED]: 'Resolved', - [ACCEPTED]: 'Accepted', - [PENDING]: 'Pending', -} as Record; - -export const IncidentStatus = ({ incident }: { incident: Incident }) => { - const classes = useStyles(); - - return ( - -
- {incident.status === 'PENDING' ? : } -
-
- ); -}; diff --git a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx index e0fa82fe85..8ef5a990ec 100644 --- a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx +++ b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx @@ -13,14 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import { useApi } from '@backstage/core-plugin-api'; import Button from '@material-ui/core/Button'; import Grid from '@material-ui/core/Grid'; +import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import RepeatIcon from '@material-ui/icons/Repeat'; -import { Shift } from '../../types'; import { DateTime as dt } from 'luxon'; -import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { Shift } from '../../types'; import { ShiftOverrideModal } from '../Shift/ShiftOverrideModal'; const useStyles = makeStyles({ @@ -48,6 +50,7 @@ export const OnCallShiftItem = ({ refetchOnCallSchedules: () => void; }) => { const classes = useStyles(); + const ilertApi = useApi(ilertApiRef); const [isModalOpened, setIsModalOpened] = React.useState(false); const handleOverride = () => { @@ -71,7 +74,7 @@ export const OnCallShiftItem = ({ {shift && shift.user ? ( - {`${shift.user.firstName} ${shift.user.lastName} (${shift.user.username})`} + {ilertApi.getUserInitials(shift.user)} ) : null} diff --git a/plugins/ilert/src/components/Service/ServiceActionsMenu.tsx b/plugins/ilert/src/components/Service/ServiceActionsMenu.tsx new file mode 100644 index 0000000000..096cc391ee --- /dev/null +++ b/plugins/ilert/src/components/Service/ServiceActionsMenu.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { Service } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const ServiceActionsMenu = ({ service }: { service: Service }) => { + const ilertApi = useApi(ilertApiRef); + const [anchorEl, setAnchorEl] = React.useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleCloseMenu = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + + + + View in iLert + + + + + + ); +}; diff --git a/plugins/ilert/src/components/Service/ServiceLink.tsx b/plugins/ilert/src/components/Service/ServiceLink.tsx new file mode 100644 index 0000000000..8e7868a2d2 --- /dev/null +++ b/plugins/ilert/src/components/Service/ServiceLink.tsx @@ -0,0 +1,31 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { Service } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const ServiceLink = ({ service }: { service: Service | null }) => { + const ilertApi = useApi(ilertApiRef); + + if (!service) { + return null; + } + + return #{service.id}; +}; diff --git a/plugins/ilert/src/components/UptimeMonitor/index.ts b/plugins/ilert/src/components/Service/index.ts similarity index 93% rename from plugins/ilert/src/components/UptimeMonitor/index.ts rename to plugins/ilert/src/components/Service/index.ts index a4aa2fb88c..11b2148046 100644 --- a/plugins/ilert/src/components/UptimeMonitor/index.ts +++ b/plugins/ilert/src/components/Service/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './UptimeMonitorActionsMenu'; +export * from './ServiceActionsMenu'; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx similarity index 70% rename from plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx rename to plugins/ilert/src/components/ServicesPage/ServicesPage.tsx index 33612d492b..0e012b07a2 100644 --- a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx +++ b/plugins/ilert/src/components/ServicesPage/ServicesPage.tsx @@ -13,26 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { AuthenticationError } from '@backstage/errors'; -import { UptimeMonitorsTable } from './UptimeMonitorsTable'; -import { MissingAuthorizationHeaderError } from '../Errors'; -import { useUptimeMonitors } from '../../hooks/useUptimeMonitors'; import { Content, ContentHeader, - SupportButton, ResponseErrorPanel, + SupportButton, } from '@backstage/core-components'; +import React from 'react'; +import { useServices } from '../../hooks/useServices'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { ServicesTable } from './ServicesTable'; -export const UptimeMonitorsPage = () => { +export const ServicesPage = () => { const [ - { tableState, uptimeMonitors, isLoading, error }, - { onChangePage, onChangeRowsPerPage, onUptimeMonitorChanged }, - ] = useUptimeMonitors(); + { tableState, services, isLoading, error }, + { onChangePage, onChangeRowsPerPage, setIsLoading }, + ] = useServices(true); if (error) { - if (error instanceof AuthenticationError) { + if (error.name === 'AuthenticationError') { return ( @@ -49,18 +48,18 @@ export const UptimeMonitorsPage = () => { return ( - + This helps you to bring iLert into your developer portal. - ); diff --git a/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx new file mode 100644 index 0000000000..30a83e4cec --- /dev/null +++ b/plugins/ilert/src/components/ServicesPage/ServicesTable.tsx @@ -0,0 +1,137 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { TableState } from '../../api'; +import { Service } from '../../types'; +import { StatusChip } from './StatusChip'; + +import { Table, TableColumn } from '@backstage/core-components'; +import { ServiceActionsMenu } from '../Service/ServiceActionsMenu'; +import { ServiceLink } from '../Service/ServiceLink'; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const ServicesTable = ({ + services, + tableState, + isLoading, + onChangePage, + onChangeRowsPerPage, + compact, +}: { + services: Service[]; + tableState: TableState; + isLoading: boolean; + setIsLoading: (isLoading: boolean) => void; + onChangePage: (page: number) => void; + onChangeRowsPerPage: (pageSize: number) => void; + compact?: boolean; +}) => { + const classes = useStyles(); + + const smColumnStyle = { + width: '10%', + maxWidth: '10%', + }; + const xlColumnStyle = { + width: '30%', + maxWidth: '30%', + }; + const idColumn: TableColumn = { + title: 'ID', + field: 'id', + highlight: true, + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const nameColumn: TableColumn = { + title: 'Name', + field: 'name', + cellStyle: !compact ? xlColumnStyle : undefined, + headerStyle: !compact ? xlColumnStyle : undefined, + render: rowData => {rowData.name}, + }; + const statusColumn: TableColumn = { + title: 'Status', + field: 'status', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const uptimeColumn: TableColumn = { + title: 'Uptime in the last 90 days', + field: 'uptimePercentage', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => ( + {rowData.uptime.uptimePercentage.p90} + ), + }; + const actionsColumn: TableColumn = { + title: '', + field: '', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + + const columns: TableColumn[] = compact + ? [nameColumn, statusColumn, uptimeColumn, actionsColumn] + : [idColumn, nameColumn, statusColumn, uptimeColumn, actionsColumn]; + + return ( +
+ No services + + } + title={ + + SERVICES + + } + page={tableState.page} + onPageChange={onChangePage} + onRowsPerPageChange={onChangeRowsPerPage} + columns={columns} + data={services} + isLoading={isLoading} + /> + ); +}; diff --git a/plugins/ilert/src/components/ServicesPage/StatusChip.tsx b/plugins/ilert/src/components/ServicesPage/StatusChip.tsx new file mode 100644 index 0000000000..873bfbfb28 --- /dev/null +++ b/plugins/ilert/src/components/ServicesPage/StatusChip.tsx @@ -0,0 +1,89 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Chip, withStyles } from '@material-ui/core'; +import React from 'react'; +import { + DEGRADED, + MAJOR_OUTAGE, + OPERATIONAL, + PARTIAL_OUTAGE, + Service, + UNDER_MAINTENANCE, +} from '../../types'; + +const OperationalChip = withStyles({ + root: { + backgroundColor: '#388E3D', + color: 'white', + margin: 0, + }, +})(Chip); + +const UnderMaintenanceChip = withStyles({ + root: { + backgroundColor: '#616161', + color: 'white', + margin: 0, + }, +})(Chip); +const DegradedChip = withStyles({ + root: { + backgroundColor: '#FBC02D', + color: 'white', + margin: 0, + }, +})(Chip); +const PartialOutageChip = withStyles({ + root: { + backgroundColor: '#F57C02', + color: 'white', + margin: 0, + }, +})(Chip); +const MajorOutageChip = withStyles({ + root: { + backgroundColor: '#D22F2E', + color: 'white', + margin: 0, + }, +})(Chip); + +const serviceStatusLabels = { + [OPERATIONAL]: 'Operational', + [UNDER_MAINTENANCE]: 'Under maintenance', + [DEGRADED]: 'Degraded', + [PARTIAL_OUTAGE]: 'Partial outage', + [MAJOR_OUTAGE]: 'Major outage', +} as Record; + +export const StatusChip = ({ service }: { service: Service }) => { + const label = `${serviceStatusLabels[service.status]}`; + + switch (service.status) { + case OPERATIONAL: + return ; + case UNDER_MAINTENANCE: + return ; + case DEGRADED: + return ; + case PARTIAL_OUTAGE: + return ; + case MAJOR_OUTAGE: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/Incident/index.ts b/plugins/ilert/src/components/ServicesPage/index.ts similarity index 89% rename from plugins/ilert/src/components/Incident/index.ts rename to plugins/ilert/src/components/ServicesPage/index.ts index 5065cb1de9..6c4eef8100 100644 --- a/plugins/ilert/src/components/Incident/index.ts +++ b/plugins/ilert/src/components/ServicesPage/index.ts @@ -13,5 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './IncidentActionsMenu'; -export * from './IncidentStatus'; +export * from './ServicesPage'; +export * from './ServicesTable'; diff --git a/plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx b/plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx new file mode 100644 index 0000000000..8fe94eb2e1 --- /dev/null +++ b/plugins/ilert/src/components/StatusPage/StatusPageActionsMenu.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { StatusPage } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const StatusPageActionsMenu = ({ + statusPage, +}: { + statusPage: StatusPage; +}) => { + const ilertApi = useApi(ilertApiRef); + const [anchorEl, setAnchorEl] = React.useState(null); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleCloseMenu = () => { + setAnchorEl(null); + }; + + return ( + <> + + + + + + + + View in iLert + + + + + + + View status page + + + + + + ); +}; diff --git a/plugins/ilert/src/components/Incident/IncidentLink.tsx b/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx similarity index 67% rename from plugins/ilert/src/components/Incident/IncidentLink.tsx rename to plugins/ilert/src/components/StatusPage/StatusPageLink.tsx index 381a656b72..edc6922e0d 100644 --- a/plugins/ilert/src/components/Incident/IncidentLink.tsx +++ b/plugins/ilert/src/components/StatusPage/StatusPageLink.tsx @@ -14,33 +14,26 @@ * limitations under the License. */ import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { Incident } from '../../types'; import { ilertApiRef } from '../../api'; +import { StatusPage } from '../../types'; -import { useApi } from '@backstage/core-plugin-api'; import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; -const useStyles = makeStyles({ - link: { - lineHeight: '22px', - }, -}); - -export const IncidentLink = ({ incident }: { incident: Incident | null }) => { +export const StatusPageLink = ({ + statusPage, +}: { + statusPage: StatusPage | null; +}) => { const ilertApi = useApi(ilertApiRef); - const classes = useStyles(); - if (!incident) { + if (!statusPage) { return null; } return ( - - #{incident.id} + + #{statusPage.id} ); }; diff --git a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx b/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx similarity index 63% rename from plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx rename to plugins/ilert/src/components/StatusPage/StatusPageURL.tsx index c70a8a40f0..1aaf209770 100644 --- a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx +++ b/plugins/ilert/src/components/StatusPage/StatusPageURL.tsx @@ -14,37 +14,24 @@ * limitations under the License. */ import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { UptimeMonitor } from '../../types'; import { ilertApiRef } from '../../api'; +import { StatusPage } from '../../types'; -import { useApi } from '@backstage/core-plugin-api'; import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; -const useStyles = makeStyles({ - link: { - lineHeight: '22px', - }, -}); - -export const UptimeMonitorLink = ({ - uptimeMonitor, +export const StatusPageURL = ({ + statusPage, }: { - uptimeMonitor: UptimeMonitor | null; + statusPage: StatusPage | null; }) => { const ilertApi = useApi(ilertApiRef); - const classes = useStyles(); - if (!uptimeMonitor) { + if (!statusPage) { return null; } - return ( - - #{uptimeMonitor.id} - - ); + const url = ilertApi.getStatusPageURL(statusPage); + + return {url}; }; diff --git a/plugins/ilert/src/components/StatusPage/index.ts b/plugins/ilert/src/components/StatusPage/index.ts new file mode 100644 index 0000000000..d805811d90 --- /dev/null +++ b/plugins/ilert/src/components/StatusPage/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './StatusPageActionsMenu'; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx b/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx new file mode 100644 index 0000000000..5cd785c4cd --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/StatusChip.tsx @@ -0,0 +1,89 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Chip, withStyles } from '@material-ui/core'; +import React from 'react'; +import { + DEGRADED, + MAJOR_OUTAGE, + OPERATIONAL, + PARTIAL_OUTAGE, + StatusPage, + UNDER_MAINTENANCE, +} from '../../types'; + +const OperationalChip = withStyles({ + root: { + backgroundColor: '#388E3D', + color: 'white', + margin: 0, + }, +})(Chip); + +const UnderMaintenanceChip = withStyles({ + root: { + backgroundColor: '#616161', + color: 'white', + margin: 0, + }, +})(Chip); +const DegradedChip = withStyles({ + root: { + backgroundColor: '#FBC02D', + color: 'white', + margin: 0, + }, +})(Chip); +const PartialOutageChip = withStyles({ + root: { + backgroundColor: '#F57C02', + color: 'white', + margin: 0, + }, +})(Chip); +const MajorOutageChip = withStyles({ + root: { + backgroundColor: '#D22F2E', + color: 'white', + margin: 0, + }, +})(Chip); + +const statusPageStatusLabels = { + [OPERATIONAL]: 'Operational', + [UNDER_MAINTENANCE]: 'Under maintenance', + [DEGRADED]: 'Degraded', + [PARTIAL_OUTAGE]: 'Partial outage', + [MAJOR_OUTAGE]: 'Major outage', +} as Record; + +export const StatusChip = ({ statusPage }: { statusPage: StatusPage }) => { + const label = `${statusPageStatusLabels[statusPage.status]}`; + + switch (statusPage.status) { + case OPERATIONAL: + return ; + case UNDER_MAINTENANCE: + return ; + case DEGRADED: + return ; + case PARTIAL_OUTAGE: + return ; + case MAJOR_OUTAGE: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx b/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx new file mode 100644 index 0000000000..9d709c5e73 --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/StatusPagesPage.tsx @@ -0,0 +1,66 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { + Content, + ContentHeader, + ResponseErrorPanel, + SupportButton, +} from '@backstage/core-components'; +import React from 'react'; +import { useStatusPages } from '../../hooks/useStatusPages'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { StatusPagesTable } from './StatusPagesTable'; + +export const StatusPagesPage = () => { + const [ + { tableState, statusPages, isLoading, error }, + { onChangePage, onChangeRowsPerPage, setIsLoading }, + ] = useStatusPages(true); + + if (error) { + if (error.name === 'AuthenticationError') { + return ( + + + + ); + } + + return ( + + + + ); + } + + return ( + + + + This helps you to bring iLert into your developer portal. + + + + + ); +}; diff --git a/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx b/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx new file mode 100644 index 0000000000..8e965b42de --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/StatusPagesTable.tsx @@ -0,0 +1,152 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import React from 'react'; +import { TableState } from '../../api'; +import { StatusPage } from '../../types'; +import { VisibilityChip } from './VisibilityChip'; + +import { Table, TableColumn } from '@backstage/core-components'; +import { StatusPageActionsMenu } from '../StatusPage/StatusPageActionsMenu'; +import { StatusPageLink } from '../StatusPage/StatusPageLink'; +import { StatusPageURL } from '../StatusPage/StatusPageURL'; +import { StatusChip } from './StatusChip'; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const StatusPagesTable = ({ + statusPages, + tableState, + isLoading, + onChangePage, + onChangeRowsPerPage, + compact, +}: { + statusPages: StatusPage[]; + tableState: TableState; + isLoading: boolean; + setIsLoading: (isLoading: boolean) => void; + onChangePage: (page: number) => void; + onChangeRowsPerPage: (pageSize: number) => void; + compact?: boolean; +}) => { + const classes = useStyles(); + + const smColumnStyle = { + width: '10%', + maxWidth: '10%', + }; + const xlColumnStyle = { + width: '30%', + maxWidth: '30%', + }; + + const idColumn: TableColumn = { + title: 'ID', + field: 'id', + highlight: true, + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const nameColumn: TableColumn = { + title: 'Name', + field: 'name', + cellStyle: !compact ? xlColumnStyle : undefined, + headerStyle: !compact ? xlColumnStyle : undefined, + render: rowData => {rowData.name}, + }; + const urlColumn: TableColumn = { + title: 'URL', + field: 'url', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const visibilityColumn: TableColumn = { + title: 'Visibility', + field: 'visibility', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const statusColumn: TableColumn = { + title: 'Status', + field: 'status', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const actionsColumn: TableColumn = { + title: '', + field: '', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + + const columns: TableColumn[] = compact + ? [nameColumn, statusColumn, urlColumn, actionsColumn] + : [ + idColumn, + nameColumn, + statusColumn, + urlColumn, + visibilityColumn, + actionsColumn, + ]; + + return ( +
+ No status pages + + } + title={ + + STATUS PAGES + + } + page={tableState.page} + onPageChange={onChangePage} + onRowsPerPageChange={onChangeRowsPerPage} + columns={columns} + data={statusPages} + isLoading={isLoading} + /> + ); +}; diff --git a/plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx b/plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx new file mode 100644 index 0000000000..04e8019d3d --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/VisibilityChip.tsx @@ -0,0 +1,52 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { Chip, withStyles } from '@material-ui/core'; +import React from 'react'; +import { PRIVATE, PUBLIC, StatusPage } from '../../types'; + +const PrivateChip = withStyles({ + root: { + backgroundColor: '#4caf50', + color: 'white', + margin: 0, + }, +})(Chip); + +const PublicChip = withStyles({ + root: { + backgroundColor: '#ffb74d', + color: 'white', + margin: 0, + }, +})(Chip); + +const statusPageVisibilityLabels = { + [PUBLIC]: 'Public', + [PRIVATE]: 'Private', +} as Record; + +export const VisibilityChip = ({ statusPage }: { statusPage: StatusPage }) => { + const label = `${statusPageVisibilityLabels[statusPage.visibility]}`; + + switch (statusPage.visibility) { + case PRIVATE: + return ; + case PUBLIC: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/StatusPagePage/index.ts b/plugins/ilert/src/components/StatusPagePage/index.ts new file mode 100644 index 0000000000..8bc923ffe1 --- /dev/null +++ b/plugins/ilert/src/components/StatusPagePage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './StatusPagesPage'; +export * from './StatusPagesTable'; diff --git a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx b/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx deleted file mode 100644 index 2d25f8e50e..0000000000 --- a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; -import MoreVertIcon from '@material-ui/icons/MoreVert'; - -import { ilertApiRef } from '../../api'; -import { UptimeMonitor } from '../../types'; - -import { alertApiRef, useApi } from '@backstage/core-plugin-api'; -import { Link } from '@backstage/core-components'; - -export const UptimeMonitorActionsMenu = ({ - uptimeMonitor, - onUptimeMonitorChanged, -}: { - uptimeMonitor: UptimeMonitor; - onUptimeMonitorChanged?: (uptimeMonitor: UptimeMonitor) => void; -}) => { - const ilertApi = useApi(ilertApiRef); - const alertApi = useApi(alertApiRef); - const [anchorEl, setAnchorEl] = React.useState(null); - const callback = onUptimeMonitorChanged || ((_: UptimeMonitor): void => {}); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; - - const handleCloseMenu = () => { - setAnchorEl(null); - }; - - const handlePause = async (): Promise => { - try { - const newUptimeMonitor = await ilertApi.pauseUptimeMonitor(uptimeMonitor); - handleCloseMenu(); - alertApi.post({ message: 'Uptime monitor paused.' }); - - callback(newUptimeMonitor); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - }; - - const handleResume = async (): Promise => { - try { - const newUptimeMonitor = await ilertApi.resumeUptimeMonitor( - uptimeMonitor, - ); - handleCloseMenu(); - alertApi.post({ message: 'Uptime monitor resumed.' }); - - callback(newUptimeMonitor); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - }; - - const handleOpenReport = async (): Promise => { - try { - const um = await ilertApi.fetchUptimeMonitor(uptimeMonitor.id); - handleCloseMenu(); - window.open(um.shareUrl, '_blank'); - } catch (err) { - alertApi.post({ message: err, severity: 'error' }); - } - }; - - return ( - <> - - - - - {uptimeMonitor.paused ? ( - - - Resume - - - ) : null} - - {!uptimeMonitor.paused ? ( - - - Pause - - - ) : null} - - - - - View Report - - - - - - - - View in iLert - - - - - - ); -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx deleted file mode 100644 index 39f3ef250b..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import Chip from '@material-ui/core/Chip'; -import { withStyles } from '@material-ui/core/styles'; -import { UptimeMonitor } from '../../types'; - -const UpChip = withStyles({ - root: { - backgroundColor: '#4caf50', - color: 'white', - margin: 0, - }, -})(Chip); - -const DownChip = withStyles({ - root: { - backgroundColor: '#d32f2f', - color: 'white', - margin: 0, - }, -})(Chip); - -const UnknownChip = withStyles({ - root: { - backgroundColor: '#92949c', - color: 'white', - margin: 0, - }, -})(Chip); - -export const uptimeMonitorStatusLabels = { - ['up']: 'Up', - ['down']: 'Down', - ['unknown']: 'Unknown', -} as Record; - -export const StatusChip = ({ - uptimeMonitor, -}: { - uptimeMonitor: UptimeMonitor; -}) => { - let label = `${uptimeMonitorStatusLabels[uptimeMonitor.status]}`; - - if (uptimeMonitor.paused) { - label = 'Paused'; - return ; - } - - switch (uptimeMonitor.status) { - case 'up': - return ; - case 'down': - return ; - case 'unknown': - return ; - default: - return ; - } -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx deleted file mode 100644 index a39901836f..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { UptimeMonitor } from '../../types'; -import Typography from '@material-ui/core/Typography'; - -export const UptimeMonitorCheckType = ({ - uptimeMonitor, -}: { - uptimeMonitor: UptimeMonitor; -}) => { - switch (uptimeMonitor.region) { - case 'EU': - return ( - {`${uptimeMonitor.checkType.toUpperCase()} 🇩🇪`} - ); - default: - return ( - {`${uptimeMonitor.checkType.toUpperCase()} 🇺🇸`} - ); - } -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx deleted file mode 100644 index 3099018e91..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { TableState } from '../../api'; -import { UptimeMonitor } from '../../types'; -import { StatusChip } from './StatusChip'; -import Typography from '@material-ui/core/Typography'; -import { DateTime as dt, Interval } from 'luxon'; -import humanizeDuration from 'humanize-duration'; -import { EscalationPolicyLink } from '../EscalationPolicy/EscalationPolicyLink'; -import { UptimeMonitorCheckType } from './UptimeMonitorCheckType'; -import { UptimeMonitorActionsMenu } from '../UptimeMonitor/UptimeMonitorActionsMenu'; -import { UptimeMonitorLink } from '../UptimeMonitor/UptimeMonitorLink'; -import { Table, TableColumn } from '@backstage/core-components'; - -const useStyles = makeStyles(theme => ({ - empty: { - padding: theme.spacing(2), - display: 'flex', - justifyContent: 'center', - }, -})); - -export const UptimeMonitorsTable = ({ - uptimeMonitors, - tableState, - isLoading, - onChangePage, - onChangeRowsPerPage, - onUptimeMonitorChanged, -}: { - uptimeMonitors: UptimeMonitor[]; - tableState: TableState; - isLoading: boolean; - onChangePage: (page: number) => void; - onChangeRowsPerPage: (pageSize: number) => void; - onUptimeMonitorChanged: (uptimeMonitor: UptimeMonitor) => void; -}) => { - const classes = useStyles(); - - const smColumnStyle = { - width: '5%', - maxWidth: '5%', - }; - const mdColumnStyle = { - width: '10%', - maxWidth: '10%', - }; - const lgColumnStyle = { - width: '15%', - maxWidth: '15%', - }; - - const columns: TableColumn[] = [ - { - title: 'ID', - field: 'id', - highlight: true, - cellStyle: mdColumnStyle, - headerStyle: mdColumnStyle, - render: rowData => ( - - ), - }, - { - title: 'Name', - field: 'name', - render: rowData => ( - {(rowData as UptimeMonitor).name} - ), - }, - { - title: 'Check Type', - field: 'checkType', - cellStyle: lgColumnStyle, - headerStyle: lgColumnStyle, - render: rowData => ( - - ), - }, - { - title: 'Last state change', - field: 'lastStatusChange', - type: 'datetime', - cellStyle: mdColumnStyle, - headerStyle: mdColumnStyle, - render: rowData => ( - - {humanizeDuration( - Interval.fromDateTimes( - dt.fromISO((rowData as UptimeMonitor).lastStatusChange), - dt.now(), - ) - .toDuration() - .valueOf(), - { units: ['h', 'm', 's'], largest: 2, round: true }, - )} - - ), - }, - { - title: 'Escalation policy', - field: 'assignedTo', - cellStyle: lgColumnStyle, - headerStyle: lgColumnStyle, - render: rowData => ( - - ), - }, - { - title: 'Status', - field: 'status', - cellStyle: smColumnStyle, - headerStyle: smColumnStyle, - render: rowData => ( - - ), - }, - { - title: '', - field: '', - cellStyle: smColumnStyle, - headerStyle: smColumnStyle, - render: rowData => ( - - ), - }, - ]; - - return ( -
- No uptime monitor - - } - page={tableState.page} - onPageChange={onChangePage} - onRowsPerPageChange={onChangeRowsPerPage} - localization={{ header: { actions: undefined } }} - isLoading={isLoading} - columns={columns} - data={uptimeMonitors} - /> - ); -}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/index.ts b/plugins/ilert/src/components/UptimeMonitorsPage/index.ts deleted file mode 100644 index 865783b5e6..0000000000 --- a/plugins/ilert/src/components/UptimeMonitorsPage/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from './UptimeMonitorsPage'; -export * from './UptimeMonitorsTable'; diff --git a/plugins/ilert/src/hooks/useIncidentActions.ts b/plugins/ilert/src/hooks/useAlertActions.ts similarity index 69% rename from plugins/ilert/src/hooks/useIncidentActions.ts rename to plugins/ilert/src/hooks/useAlertActions.ts index 75621f67c3..875a8099f3 100644 --- a/plugins/ilert/src/hooks/useIncidentActions.ts +++ b/plugins/ilert/src/hooks/useAlertActions.ts @@ -13,48 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { Incident, IncidentAction } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { ilertApiRef } from '../api'; +import { Alert, AlertAction } from '../types'; -export const useIncidentActions = ( - incident: Incident | null, - open: boolean, -) => { +export const useAlertActions = (alert: Alert | null, open: boolean) => { const ilertApi = useApi(ilertApiRef); const errorApi = useApi(errorApiRef); - const [incidentActionsList, setIncidentActionsList] = React.useState< - IncidentAction[] - >([]); + const [alertActionsList, setAlertActionsList] = React.useState( + [], + ); const [isLoading, setIsLoading] = React.useState(false); const { error, retry } = useAsyncRetry(async () => { try { - if (!incident || !open) { + if (!alert || !open) { return; } - const data = await ilertApi.fetchIncidentActions(incident); - setIncidentActionsList(data); + const data = await ilertApi.fetchAlertActions(alert); + setAlertActionsList(data); } catch (e) { if (!(e instanceof AuthenticationError)) { errorApi.post(e); } throw e; } - }, [incident, open]); + }, [alert, open]); return [ { - incidentActions: incidentActionsList, + alertActions: alertActionsList, error, isLoading, }, { - setIncidentActionsList, + setAlertActionsList, setIsLoading, retry, }, diff --git a/plugins/ilert/src/hooks/useAlertSource.ts b/plugins/ilert/src/hooks/useAlertSource.ts index 279150a724..9048ee3031 100644 --- a/plugins/ilert/src/hooks/useAlertSource.ts +++ b/plugins/ilert/src/hooks/useAlertSource.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { AlertSource, UptimeMonitor } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { ilertApiRef } from '../api'; +import { AlertSource } from '../types'; export const useAlertSource = (integrationKey: string) => { const ilertApi = useApi(ilertApiRef); @@ -28,10 +28,6 @@ export const useAlertSource = (integrationKey: string) => { null, ); const [isAlertSourceLoading, setIsAlertSourceLoading] = React.useState(false); - const [uptimeMonitor, setUptimeMonitor] = - React.useState(null); - const [isUptimeMonitorLoading, setIsUptimeMonitorLoading] = - React.useState(false); const fetchAlertSourceCall = async () => { try { @@ -56,38 +52,13 @@ export const useAlertSource = (integrationKey: string) => { [integrationKey], ); - const fetchUptimeMonitorCall = async () => { - try { - if (!alertSource || alertSource.integrationType !== 'MONITOR') { - return; - } - setIsUptimeMonitorLoading(true); - const data = await ilertApi.fetchUptimeMonitor(alertSource.id); - setUptimeMonitor(data || null); - setIsUptimeMonitorLoading(false); - } catch (e) { - setIsUptimeMonitorLoading(false); - if (!(e instanceof AuthenticationError)) { - errorApi.post(e); - } - throw e; - } - }; - - const { error: uptimeMonitorError, retry: uptimeMonitorRetry } = - useAsyncRetry(fetchUptimeMonitorCall, [alertSource]); - - const retry = () => { - alertSourceRetry(); - uptimeMonitorRetry(); - }; + const retry = () => alertSourceRetry(); return [ { alertSource, - uptimeMonitor, - error: alertSourceError || uptimeMonitorError, - isLoading: isAlertSourceLoading || isUptimeMonitorLoading, + error: alertSourceError, + isLoading: isAlertSourceLoading, }, { retry, diff --git a/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts b/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts index 5a804e37fd..2884e890db 100644 --- a/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts +++ b/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts @@ -13,12 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { ilertApiRef } from '../api'; import { AlertSource, OnCall } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; export const useAlertSourceOnCalls = (alertSource?: AlertSource | null) => { const ilertApi = useApi(ilertApiRef); diff --git a/plugins/ilert/src/hooks/useIncidents.ts b/plugins/ilert/src/hooks/useAlerts.ts similarity index 57% rename from plugins/ilert/src/hooks/useIncidents.ts rename to plugins/ilert/src/hooks/useAlerts.ts index 45f28937c5..8acabe0086 100644 --- a/plugins/ilert/src/hooks/useIncidents.ts +++ b/plugins/ilert/src/hooks/useAlerts.ts @@ -13,20 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { GetIncidentsOpts, ilertApiRef, TableState } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { - ACCEPTED, - PENDING, - Incident, - IncidentStatus, - AlertSource, -} from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { GetAlertsOpts, ilertApiRef, TableState } from '../api'; +import { ACCEPTED, Alert, AlertSource, AlertStatus, PENDING } from '../types'; -export const useIncidents = ( +export const useAlerts = ( paging: boolean, singleSource?: boolean, alertSource?: AlertSource | null, @@ -38,21 +32,21 @@ export const useIncidents = ( page: 0, pageSize: 10, }); - const [states, setStates] = React.useState([ + const [states, setStates] = React.useState([ ACCEPTED, PENDING, ]); - const [incidentsList, setIncidentsList] = React.useState([]); - const [incidentsCount, setIncidentsCount] = React.useState(0); + const [alertsList, setAlertsList] = React.useState([]); + const [alertsCount, setAlertsCount] = React.useState(0); const [isLoading, setIsLoading] = React.useState(false); - const fetchIncidentsCall = async () => { + const fetchAlertsCall = async () => { try { if (singleSource && !alertSource) { return; } setIsLoading(true); - const opts: GetIncidentsOpts = { + const opts: GetAlertsOpts = { states, alertSources: alertSource ? [alertSource.id] : [], }; @@ -60,8 +54,8 @@ export const useIncidents = ( opts.maxResults = tableState.pageSize; opts.startIndex = tableState.page * tableState.pageSize; } - const data = await ilertApi.fetchIncidents(opts); - setIncidentsList(data || []); + const data = await ilertApi.fetchAlerts(opts); + setAlertsList(data || []); setIsLoading(false); } catch (e) { if (!(e instanceof AuthenticationError)) { @@ -72,10 +66,10 @@ export const useIncidents = ( } }; - const fetchIncidentsCountCall = async () => { + const fetchAlertsCountCall = async () => { try { - const count = await ilertApi.fetchIncidentsCount({ states }); - setIncidentsCount(count || 0); + const count = await ilertApi.fetchAlertsCount({ states }); + setAlertsCount(count || 0); } catch (e) { if (!(e instanceof AuthenticationError)) { errorApi.post(e); @@ -83,44 +77,44 @@ export const useIncidents = ( throw e; } }; - const fetchIncidents = useAsyncRetry(fetchIncidentsCall, [ + const fetchAlerts = useAsyncRetry(fetchAlertsCall, [ tableState, states, singleSource, alertSource, ]); - const refetchIncidents = () => { + const refetchAlerts = () => { setTableState({ ...tableState, page: 0 }); - Promise.all([fetchIncidentsCall(), fetchIncidentsCountCall()]); + Promise.all([fetchAlertsCall(), fetchAlertsCountCall()]); }; - const fetchIncidentsCount = useAsyncRetry(fetchIncidentsCountCall, [states]); + const fetchAlertsCount = useAsyncRetry(fetchAlertsCountCall, [states]); - const error = fetchIncidents.error || fetchIncidentsCount.error; + const error = fetchAlerts.error || fetchAlertsCount.error; const retry = () => { - fetchIncidents.retry(); - fetchIncidentsCount.retry(); + fetchAlerts.retry(); + fetchAlertsCount.retry(); }; - const onIncidentChanged = (newIncident: Incident) => { - let shouldRefetchIncidents = false; - setIncidentsList( - incidentsList.reduce((acc: Incident[], incident: Incident) => { - if (newIncident.id === incident.id) { - if (states.includes(newIncident.status)) { - acc.push(newIncident); + const onAlertChanged = (newAlert: Alert) => { + let shouldRefetchAlerts = false; + setAlertsList( + alertsList.reduce((acc: Alert[], alert: Alert) => { + if (newAlert.id === alert.id) { + if (states.includes(newAlert.status)) { + acc.push(newAlert); } else { - shouldRefetchIncidents = true; + shouldRefetchAlerts = true; } return acc; } - acc.push(incident); + acc.push(alert); return acc; }, []), ); - if (shouldRefetchIncidents) { - refetchIncidents(); + if (shouldRefetchAlerts) { + refetchAlerts(); } }; @@ -130,7 +124,7 @@ export const useIncidents = ( const onChangeRowsPerPage = (p: number) => { setTableState({ ...tableState, pageSize: p }); }; - const onIncidentStatesChange = (s: IncidentStatus[]) => { + const onAlertStatesChange = (s: AlertStatus[]) => { setStates(s); }; @@ -138,22 +132,22 @@ export const useIncidents = ( { tableState, states, - incidents: incidentsList, - incidentsCount, + alerts: alertsList, + alertsCount, error, isLoading, }, { setTableState, setStates, - setIncidentsList, + setAlertsList, setIsLoading, retry, - onIncidentChanged, - refetchIncidents, + onAlertChanged, + refetchAlerts, onChangePage, onChangeRowsPerPage, - onIncidentStatesChange, + onAlertStatesChange, }, ] as const; }; diff --git a/plugins/ilert/src/hooks/useAssignIncident.ts b/plugins/ilert/src/hooks/useAssignAlert.ts similarity index 68% rename from plugins/ilert/src/hooks/useAssignIncident.ts rename to plugins/ilert/src/hooks/useAssignAlert.ts index b760f31e9c..f81eae724f 100644 --- a/plugins/ilert/src/hooks/useAssignIncident.ts +++ b/plugins/ilert/src/hooks/useAssignAlert.ts @@ -13,30 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { Incident, IncidentResponder } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { ilertApiRef } from '../api'; +import { Alert, AlertResponder } from '../types'; -export const useAssignIncident = (incident: Incident | null, open: boolean) => { +export const useAssignAlert = (alert: Alert | null, open: boolean) => { const ilertApi = useApi(ilertApiRef); const errorApi = useApi(errorApiRef); - const [incidentRespondersList, setIncidentRespondersList] = React.useState< - IncidentResponder[] + const [alertRespondersList, setAlertRespondersList] = React.useState< + AlertResponder[] >([]); - const [incidentResponder, setIncidentResponder] = - React.useState(null); + const [alertResponder, setAlertResponder] = + React.useState(null); const [isLoading, setIsLoading] = React.useState(false); const { error, retry } = useAsyncRetry(async () => { try { - if (!incident || !open) { + if (!alert || !open) { return; } - const data = await ilertApi.fetchIncidentResponders(incident); + const data = await ilertApi.fetchAlertResponders(alert); if (data && Array.isArray(data)) { const groups = [ 'SUGGESTED', @@ -45,7 +45,7 @@ export const useAssignIncident = (incident: Incident | null, open: boolean) => { 'ON_CALL_SCHEDULE', ]; data.sort((a, b) => groups.indexOf(a.group) - groups.indexOf(b.group)); - setIncidentRespondersList(data); + setAlertRespondersList(data); } } catch (e) { if (!(e instanceof AuthenticationError)) { @@ -53,18 +53,18 @@ export const useAssignIncident = (incident: Incident | null, open: boolean) => { } throw e; } - }, [incident, open]); + }, [alert, open]); return [ { - incidentRespondersList, - incidentResponder, + alertRespondersList, + alertResponder, error, isLoading, }, { - setIncidentRespondersList, - setIncidentResponder, + setAlertRespondersList, + setAlertResponder, setIsLoading, retry, }, diff --git a/plugins/ilert/src/hooks/useNewIncident.ts b/plugins/ilert/src/hooks/useNewAlert.ts similarity index 95% rename from plugins/ilert/src/hooks/useNewIncident.ts rename to plugins/ilert/src/hooks/useNewAlert.ts index f2bb3a8cd3..a560c1b035 100644 --- a/plugins/ilert/src/hooks/useNewIncident.ts +++ b/plugins/ilert/src/hooks/useNewAlert.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { ilertApiRef } from '../api'; import { AlertSource } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; -export const useNewIncident = ( +export const useNewAlert = ( open: boolean, initialAlertSource?: AlertSource | null, ) => { diff --git a/plugins/ilert/src/hooks/useUptimeMonitors.ts b/plugins/ilert/src/hooks/useServices.ts similarity index 58% rename from plugins/ilert/src/hooks/useUptimeMonitors.ts rename to plugins/ilert/src/hooks/useServices.ts index c71d5f02af..b6ea120f80 100644 --- a/plugins/ilert/src/hooks/useUptimeMonitors.ts +++ b/plugins/ilert/src/hooks/useServices.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { ilertApiRef, TableState } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { UptimeMonitor } from '../types'; -import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { GetServicesOpts, ilertApiRef, TableState } from '../api'; +import { Service } from '../types'; -export const useUptimeMonitors = () => { +export const useServices = (paging: boolean) => { const ilertApi = useApi(ilertApiRef); const errorApi = useApi(errorApiRef); @@ -28,60 +28,64 @@ export const useUptimeMonitors = () => { page: 0, pageSize: 10, }); - const [uptimeMonitorsList, setUptimeMonitorsList] = React.useState< - UptimeMonitor[] - >([]); + + const [servicesList, setServicesList] = React.useState([]); const [isLoading, setIsLoading] = React.useState(false); - const { error, retry } = useAsyncRetry(async () => { + const fetchServicesCall = async () => { try { setIsLoading(true); - const data = await ilertApi.fetchUptimeMonitors(); - setUptimeMonitorsList(data || []); + const opts: GetServicesOpts = {}; + if (paging) { + opts.maxResults = tableState.pageSize; + opts.startIndex = tableState.page * tableState.pageSize; + } + const data = await ilertApi.fetchServices(opts); + setServicesList(data || []); setIsLoading(false); } catch (e) { - setIsLoading(false); if (!(e instanceof AuthenticationError)) { errorApi.post(e); } + setIsLoading(false); throw e; } - }, [tableState]); + }; - const onUptimeMonitorChanged = (newUptimeMonitor: UptimeMonitor) => { - setUptimeMonitorsList( - uptimeMonitorsList.map((uptimeMonitor: UptimeMonitor): UptimeMonitor => { - if (newUptimeMonitor.id === uptimeMonitor.id) { - return newUptimeMonitor; - } + const fetchServices = useAsyncRetry(fetchServicesCall, [tableState]); - return uptimeMonitor; - }), - ); + const refetchServices = () => { + setTableState({ ...tableState, page: 0 }); + Promise.all([fetchServicesCall()]); + }; + + const error = fetchServices.error; + const retry = () => { + fetchServices.retry(); }; const onChangePage = (page: number) => { setTableState({ ...tableState, page }); }; - const onChangeRowsPerPage = (pageSize: number) => { - setTableState({ ...tableState, pageSize }); + const onChangeRowsPerPage = (p: number) => { + setTableState({ ...tableState, pageSize: p }); }; return [ { tableState, - uptimeMonitors: uptimeMonitorsList, - error, + services: servicesList, isLoading, + error, }, { setTableState, - setUptimeMonitorsList, + setServicesList, + setIsLoading, retry, - onUptimeMonitorChanged, + refetchServices, onChangePage, onChangeRowsPerPage, - setIsLoading, }, ] as const; }; diff --git a/plugins/ilert/src/hooks/useStatusPages.ts b/plugins/ilert/src/hooks/useStatusPages.ts new file mode 100644 index 0000000000..740e0f515f --- /dev/null +++ b/plugins/ilert/src/hooks/useStatusPages.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { AuthenticationError } from '@backstage/errors'; +import React from 'react'; +import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { GetStatusPagesOpts, ilertApiRef, TableState } from '../api'; +import { StatusPage } from '../types'; + +export const useStatusPages = (paging: boolean) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [tableState, setTableState] = React.useState({ + page: 0, + pageSize: 10, + }); + + const [statusPagesList, setStatusPagesList] = React.useState( + [], + ); + const [isLoading, setIsLoading] = React.useState(false); + + const fetchStatusPagesCall = async () => { + try { + setIsLoading(true); + const opts: GetStatusPagesOpts = {}; + if (paging) { + opts.maxResults = tableState.pageSize; + opts.startIndex = tableState.page * tableState.pageSize; + } + const data = await ilertApi.fetchStatusPages(opts); + setStatusPagesList(data || []); + setIsLoading(false); + } catch (e) { + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + setIsLoading(false); + throw e; + } + }; + + const fetchStatusPages = useAsyncRetry(fetchStatusPagesCall, [tableState]); + + const refetchStatusPages = () => { + setTableState({ ...tableState, page: 0 }); + Promise.all([fetchStatusPagesCall()]); + }; + + const error = fetchStatusPages.error; + const retry = () => { + fetchStatusPages.retry(); + }; + + const onChangePage = (page: number) => { + setTableState({ ...tableState, page }); + }; + const onChangeRowsPerPage = (p: number) => { + setTableState({ ...tableState, pageSize: p }); + }; + + return [ + { + tableState, + statusPages: statusPagesList, + isLoading, + error, + }, + { + setTableState, + setStatusPagesList, + setIsLoading, + retry, + refetchStatusPages, + onChangePage, + onChangeRowsPerPage, + }, + ] as const; +}; diff --git a/plugins/ilert/src/types.ts b/plugins/ilert/src/types.ts index 3d1531329a..e55abb67a5 100644 --- a/plugins/ilert/src/types.ts +++ b/plugins/ilert/src/types.ts @@ -15,17 +15,18 @@ */ /** @public */ -export interface Incident { +export interface Alert { id: number; summary: string; details: string; reportTime: string; resolvedOn: string; - status: IncidentStatus; - priority: IncidentPriority; - incidentKey: string; + status: AlertStatus; + priority: AlertPriority; + alertKey: string; alertSource: AlertSource | null; assignedTo: User | null; + responders: Responder[]; logEntries: LogEntry[]; links: Link[]; images: Image[]; @@ -42,10 +43,10 @@ export const ACCEPTED = 'ACCEPTED'; export const RESOLVED = 'RESOLVED'; /** @public */ -export type IncidentStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; +export type AlertStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; /** @public */ -export type IncidentPriority = 'HIGH' | 'LOW'; +export type AlertPriority = 'HIGH' | 'LOW'; /** @public */ export interface Link { @@ -76,7 +77,7 @@ export interface LogEntry { timestamp: string; logEntryType: string; text: string; - incidentId?: number; + alertId?: number; iconName?: string; iconClass?: string; filterTypes?: string[]; @@ -99,6 +100,13 @@ export interface User { department: string; } +/** @public */ +export interface Responder { + acceptedAt?: string; + status: string; + user: User; +} + /** @public */ export type UserRole = | 'USER' @@ -125,8 +133,8 @@ export interface AlertSource { iconUrl?: string; lightIconUrl?: string; darkIconUrl?: string; - incidentCreation?: AlertSourceIncidentCreation; - incidentPriorityRule?: AlertSourceIncidentPriorityRule; + alertCreation?: AlertSourceAlertCreation; + alertPriorityRule?: AlertSourceAlertPriorityRule; emailFiltered?: boolean; emailResolveFiltered?: boolean; active?: boolean; @@ -209,20 +217,48 @@ export type AlertSourceIntegrationType = | 'CORTEXXSOAR' | string; /** @public */ -export type AlertSourceIncidentCreation = - | 'ONE_INCIDENT_PER_EMAIL' - | 'ONE_INCIDENT_PER_EMAIL_SUBJECT' - | 'ONE_PENDING_INCIDENT_ALLOWED' - | 'ONE_OPEN_INCIDENT_ALLOWED' +export type AlertSourceAlertCreation = + | 'ONE_ALERT_PER_EMAIL' + | 'ONE_ALERT_PER_EMAIL_SUBJECT' + | 'ONE_PENDING_ALERT_ALLOWED' + | 'ONE_OPEN_ALERT_ALLOWED' | 'OPEN_RESOLVE_ON_EXTRACTION'; /** @public */ export type AlertSourceFilterOperator = 'AND' | 'OR'; /** @public */ -export type AlertSourceIncidentPriorityRule = +export type AlertSourceAlertPriorityRule = | 'HIGH' | 'LOW' | 'HIGH_DURING_SUPPORT_HOURS' | 'LOW_DURING_SUPPORT_HOURS'; + +/** @public */ +export const OPERATIONAL = 'OPERATIONAL'; +/** @public */ +export const UNDER_MAINTENANCE = 'UNDER_MAINTENANCE'; +/** @public */ +export const DEGRADED = 'DEGRADED'; +/** @public */ +export const PARTIAL_OUTAGE = 'PARTIAL_OUTAGE'; +/** @public */ +export const MAJOR_OUTAGE = 'MAJOR_OUTAGE'; + +/** @public */ +export type ServiceStatus = + | typeof OPERATIONAL + | typeof UNDER_MAINTENANCE + | typeof DEGRADED + | typeof PARTIAL_OUTAGE + | typeof MAJOR_OUTAGE; + +/** @public */ +export const PRIVATE = 'PRIVATE'; +/** @public */ +export const PUBLIC = 'PUBLIC'; + +/** @public */ +export type StatusPageVisibility = typeof PRIVATE | typeof PUBLIC; + /** @public */ export interface AlertSourceEmailPredicate { field: 'EMAIL_FROM' | 'EMAIL_SUBJECT' | 'EMAIL_BODY'; @@ -251,7 +287,7 @@ export interface AlertSourceSupportDay { /** @public */ export interface AlertSourceSupportHours { timezone: AlertSourceTimeZone; - autoRaiseIncidents: boolean; + autoRaiseAlerts: boolean; supportDays: { MONDAY: AlertSourceSupportDay; TUESDAY: AlertSourceSupportDay; @@ -316,33 +352,7 @@ export interface Shift { } /** @public */ -export interface UptimeMonitor { - id: number; - name: string; - region: 'EU' | 'US'; - checkType: 'http' | 'tcp' | 'udp' | 'ping'; - checkParams: UptimeMonitorCheckParams; - intervalSec: number; - timeoutMs: number; - createIncidentAfterFailedChecks: number; - paused: boolean; - embedUrl: string; - shareUrl: string; - status: string; - lastStatusChange: string; - escalationPolicy: EscalationPolicy; - teams: TeamShort[]; -} - -/** @public */ -export interface UptimeMonitorCheckParams { - host?: string; - port?: number; - url?: string; -} - -/** @public */ -export interface IncidentResponder { +export interface AlertResponder { group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; id: number; name: string; @@ -350,19 +360,19 @@ export interface IncidentResponder { } /** @public */ -export interface IncidentAction { +export interface AlertAction { name: string; type: string; webhookId: string; extensionId?: string; - history?: IncidentActionHistory[]; + history?: AlertActionHistory[]; } /** @public */ -export interface IncidentActionHistory { +export interface AlertActionHistory { id: string; webhookId: string; - incidentId: number; + alertId: number; actor: User; success: boolean; } @@ -376,3 +386,31 @@ export interface OnCall { end: string; escalationLevel: number; } + +/** @public */ +export interface Service { + id: number; + name: string; + status: ServiceStatus; + uptime: Uptime; +} + +/** @public */ +export interface Uptime { + uptimePercentage: UptimePercentage; +} + +/** @public */ +export interface UptimePercentage { + p90: number; +} + +/** @public */ +export interface StatusPage { + id: number; + name: string; + domain: string; + subdomain: string; + visibility: StatusPageVisibility; + status: ServiceStatus; +} diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md index c4b15f8cdf..54573cdaec 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/plugin-jenkins-backend +## 0.1.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-jenkins-common@0.1.10-next.0 + +## 0.1.27 + +### Patch Changes + +- b19ea927af: Fixed a bug where `extraRequestHeaders` configuration was ignored. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-jenkins-common@0.1.9 + +## 0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.27-next.1 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index bc9bfd49c2..3a7a930f95 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins-backend", "description": "A Backstage backend plugin that integrates towards Jenkins", - "version": "0.1.27-next.1", + "version": "0.1.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins-common/CHANGELOG.md b/plugins/jenkins-common/CHANGELOG.md index 7329ecd7e4..fc9c74184d 100644 --- a/plugins/jenkins-common/CHANGELOG.md +++ b/plugins/jenkins-common/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-jenkins-common +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + +## 0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + ## 0.1.9-next.1 ### Patch Changes diff --git a/plugins/jenkins-common/package.json b/plugins/jenkins-common/package.json index 9696000d03..95158ea8bb 100644 --- a/plugins/jenkins-common/package.json +++ b/plugins/jenkins-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins-common", - "version": "0.1.9-next.1", + "version": "0.1.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index d113d79897..2a15d2917a 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-jenkins +## 0.7.10-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.10-next.0 + +## 0.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.9 + +## 0.7.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.7.9-next.1 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index cf6ad8c2d0..a77f3d2439 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins", "description": "A Backstage plugin that integrates towards Jenkins", - "version": "0.7.9-next.1", + "version": "0.7.10-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index e06332f8f0..3b52c65930 100644 --- a/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/jenkins/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -15,7 +15,6 @@ */ import { Box, - Link as MaterialLink, Paper, Table, TableBody, @@ -102,10 +101,12 @@ const BuildWithStepsView = () => { Jenkins - - View on Jenkins{' '} - - + {value?.url && ( + + View on Jenkins{' '} + + + )} @@ -114,10 +115,12 @@ const BuildWithStepsView = () => { GitHub - - View on GitHub{' '} - - + {value?.source?.url && ( + + View on GitHub{' '} + + + )} diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index 1f88887c18..5999228785 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-kafka-backend +## 0.2.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.30-next.1 ### Patch Changes diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index d9ed476b9a..096170dc7e 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka-backend", "description": "A Backstage backend plugin that integrates towards Kafka", - "version": "0.2.30-next.1", + "version": "0.2.31-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 1be95d1619..ec8563d64b 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-kafka +## 0.3.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index bc142ced57..0bcb7dfa24 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka", "description": "A Backstage plugin that integrates towards Kafka", - "version": "0.3.10-next.1", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx index 17f7af3740..d166195cf9 100644 --- a/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx +++ b/plugins/kafka/src/components/ConsumerGroupOffsets/ConsumerGroupOffsets.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ -import { Box, Grid, Typography, Link } from '@material-ui/core'; +import { Box, Grid, Typography } from '@material-ui/core'; import RetryIcon from '@material-ui/icons/Replay'; import React from 'react'; import { useConsumerGroupsOffsetsForEntity } from './useConsumerGroupsOffsetsForEntity'; -import { Table, TableColumn } from '@backstage/core-components'; +import { Table, TableColumn, Link } from '@backstage/core-components'; export type TopicPartitionInfo = { topic: string; @@ -103,11 +103,7 @@ export const ConsumerGroupOffsets = ({ Consumed Topics for {consumerGroup} ( - {(dashboardUrl && ( - - {clusterId} - - )) || + {(dashboardUrl && {clusterId}) || clusterId} ) diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 08008f5fe1..804112324d 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-kubernetes-backend +## 0.8.0-next.0 + +### Minor Changes + +- cbf5d11fdf: The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + + - **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. + - Add the `PodStatusFetchResponse` to the `FetchResponse` union type. + - Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-kubernetes-common@0.4.4-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.7.3 + +### Patch Changes + +- de676888bc: Added missing cluster locator configuration schema entries, for the catalog and local proxy types +- d4a8c683be: kubernetes service locator now take request context parameters +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-kubernetes-common@0.4.3 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + ## 0.7.3-next.1 ### Patch Changes diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index bf2c9e8082..d51ec4c211 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -22,7 +22,6 @@ import { Logger } from 'winston'; import { Metrics } from '@kubernetes/client-node'; import type { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { PodStatus } from '@kubernetes/client-node/dist/top'; import { TokenCredential } from '@azure/identity'; // @alpha (undocumented) @@ -267,10 +266,10 @@ export interface KubernetesFetcher { params: ObjectFetchParams, ): Promise; // (undocumented) - fetchPodMetricsByNamespace( + fetchPodMetricsByNamespaces( clusterDetails: ClusterDetails, - namespace: string, - ): Promise; + namespaces: Set, + ): Promise; } // @alpha (undocumented) diff --git a/plugins/kubernetes-backend/schema.d.ts b/plugins/kubernetes-backend/config.d.ts similarity index 94% rename from plugins/kubernetes-backend/schema.d.ts rename to plugins/kubernetes-backend/config.d.ts index 59fa7a5d97..2a514394d8 100644 --- a/plugins/kubernetes-backend/schema.d.ts +++ b/plugins/kubernetes-backend/config.d.ts @@ -31,18 +31,6 @@ export interface Config { type: 'multiTenant'; }; clusterLocatorMethods: Array< - | { - /** @visibility frontend */ - type: 'gke'; - /** @visibility frontend */ - projectId: string; - /** @visibility frontend */ - region?: string; - /** @visibility frontend */ - skipTLSVerify?: boolean; - /** @visibility frontend */ - skipMetricsLookup?: boolean; - } | { /** @visibility frontend */ type: 'config'; @@ -68,6 +56,26 @@ export interface Config { skipMetricsLookup?: boolean; }>; } + | { + /** @visibility frontend */ + type: 'catalog'; + } + | { + /** @visibility frontend */ + type: 'localKubectlProxy'; + } + | { + /** @visibility frontend */ + type: 'gke'; + /** @visibility frontend */ + projectId: string; + /** @visibility frontend */ + region?: string; + /** @visibility frontend */ + skipTLSVerify?: boolean; + /** @visibility frontend */ + skipMetricsLookup?: boolean; + } >; customResources?: Array<{ group: string; diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index e9a790b2ac..4082bd5b3a 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.7.3-next.1", + "version": "0.8.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,7 @@ "backstage", "kubernetes" ], - "configSchema": "schema.d.ts", + "configSchema": "config.d.ts", "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", @@ -69,6 +69,6 @@ }, "files": [ "dist", - "schema.d.ts" + "config.d.ts" ] } diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index ed56c2da8a..2766f9abfc 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -30,7 +30,6 @@ import { } from '../types/types'; import { KubernetesBuilder } from './KubernetesBuilder'; import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; -import { PodStatus } from '@kubernetes/client-node'; import { CatalogApi } from '@backstage/catalog-client'; describe('KubernetesBuilder', () => { @@ -222,11 +221,11 @@ describe('KubernetesBuilder', () => { }; const fetcher: KubernetesFetcher = { - fetchPodMetricsByNamespace( + fetchPodMetricsByNamespaces( _clusterDetails: ClusterDetails, - _namespace: string, - ): Promise { - return Promise.resolve([]); + _namespaces: Set, + ): Promise { + return Promise.resolve({ errors: [], responses: [] }); }, fetchObjectsForService( _params: ObjectFetchParams, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index 04256ed3dc..1af5371e53 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -18,13 +18,13 @@ import { getVoidLogger } from '@backstage/backend-common'; import { ClusterDetails, CustomResource, + FetchResponseWrapper, ObjectFetchParams, } from '../types/types'; import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; -import { PodStatus } from '@kubernetes/client-node/dist/top'; const fetchObjectsForService = jest.fn(); -const fetchPodMetricsByNamespace = jest.fn(); +const fetchPodMetricsByNamespaces = jest.fn(); const getClustersByEntity = jest.fn(); @@ -55,8 +55,9 @@ const mockFetch = (mock: jest.Mock) => { }; const mockMetrics = (mock: jest.Mock) => { - mock.mockImplementation((clusterDetails: ClusterDetails, namespace: string) => - Promise.resolve(generatePodStatus(clusterDetails.name, namespace)), + mock.mockImplementation( + (clusterDetails: ClusterDetails, namespaces: Set) => + Promise.resolve(generatePodStatus(clusterDetails.name, namespaces)), ); }; @@ -143,7 +144,7 @@ function mockFetchAndGetKubernetesFanOutHandler( customResources: CustomResource[], ) { mockFetch(fetchObjectsForService); - mockMetrics(fetchPodMetricsByNamespace); + mockMetrics(fetchPodMetricsByNamespaces); return getKubernetesFanOutHandler(customResources); } @@ -153,7 +154,7 @@ function getKubernetesFanOutHandler(customResources: CustomResource[]) { logger: getVoidLogger(), fetcher: { fetchObjectsForService, - fetchPodMetricsByNamespace, + fetchPodMetricsByNamespaces, }, serviceLocator: { getClustersByEntity, @@ -164,24 +165,32 @@ function getKubernetesFanOutHandler(customResources: CustomResource[]) { function generatePodStatus( _clusterName: string, - _namespace: string, -): PodStatus[] { - return [ - { - Pod: {}, - CPU: { - CurrentUsage: 100, - RequestTotal: 101, - LimitTotal: 102, - }, - Memory: { - CurrentUsage: BigInt('1000'), - RequestTotal: BigInt('1001'), - LimitTotal: BigInt('1002'), - }, - Containers: [], - }, - ] as any; + _namespaces: Set, +): FetchResponseWrapper { + return { + errors: [], + responses: Array.from(_namespaces).map(() => { + return { + type: 'podstatus', + resources: [ + { + Pod: {}, + CPU: { + CurrentUsage: 100, + RequestTotal: 101, + LimitTotal: 102, + }, + Memory: { + CurrentUsage: BigInt('1000'), + RequestTotal: BigInt('1001'), + LimitTotal: BigInt('1002'), + }, + Containers: [], + }, + ], + }; + }), + }; } function generateMockResourcesAndErrors( @@ -292,9 +301,9 @@ describe('getKubernetesObjectsByEntity', () => { expect(getClustersByEntity.mock.calls.length).toBe(1); expect(fetchObjectsForService.mock.calls.length).toBe(1); - expect(fetchPodMetricsByNamespace.mock.calls.length).toBe(1); - expect(fetchPodMetricsByNamespace.mock.calls[0][1]).toBe( - 'ns-test-component-test-cluster', + expect(fetchPodMetricsByNamespaces.mock.calls.length).toBe(1); + expect(fetchPodMetricsByNamespaces.mock.calls[0][1]).toStrictEqual( + new Set(['ns-test-component-test-cluster']), ); expect(result).toStrictEqual({ @@ -433,7 +442,7 @@ describe('getKubernetesObjectsByEntity', () => { }), ); - mockMetrics(fetchPodMetricsByNamespace); + mockMetrics(fetchPodMetricsByNamespaces); const sut = getKubernetesFanOutHandler([]); @@ -444,9 +453,10 @@ describe('getKubernetesObjectsByEntity', () => { expect(getClustersByEntity.mock.calls.length).toBe(1); expect(fetchObjectsForService.mock.calls.length).toBe(1); - expect(fetchPodMetricsByNamespace.mock.calls.length).toBe(2); - expect(fetchPodMetricsByNamespace.mock.calls[0][1]).toBe('ns-a'); - expect(fetchPodMetricsByNamespace.mock.calls[1][1]).toBe('ns-b'); + expect(fetchPodMetricsByNamespaces.mock.calls.length).toBe(1); + expect(fetchPodMetricsByNamespaces.mock.calls[0][1]).toStrictEqual( + new Set(['ns-a', 'ns-b']), + ); expect(result).toStrictEqual({ items: [ @@ -665,6 +675,82 @@ describe('getKubernetesObjectsByEntity', () => { ], }); }); + it('retrieve objects for two clusters, one fails to fetch pod metrics', async () => { + getClustersByEntity.mockImplementation(() => + Promise.resolve({ + clusters: [ + { + name: 'test-cluster', + authProvider: 'serviceAccount', + dashboardUrl: 'https://k8s.foo.coom', + }, + { + name: 'other-cluster', + authProvider: 'google', + }, + ], + }), + ); + + mockFetch(fetchObjectsForService); + + // To simulate the partial failure, return a valid response for the first call, + // and an error for the second call. + fetchPodMetricsByNamespaces + .mockImplementationOnce( + (clusterDetails: ClusterDetails, namespaces: Set) => + Promise.resolve(generatePodStatus(clusterDetails.name, namespaces)), + ) + .mockResolvedValueOnce({ + errors: [ + { + errorType: 'NOT_FOUND', + resourcePath: '/some/path', + statusCode: 404, + }, + ], + responses: [], + }); + + const sut = getKubernetesFanOutHandler([]); + + const result = await sut.getKubernetesObjectsByEntity({ + entity, + auth: { + google: 'google_token_123', + }, + }); + + expect(getClustersByEntity.mock.calls.length).toBe(1); + expect(fetchObjectsForService.mock.calls.length).toBe(2); + expect(result).toStrictEqual({ + items: [ + { + cluster: { + dashboardUrl: 'https://k8s.foo.coom', + name: 'test-cluster', + }, + errors: [], + podMetrics: [POD_METRICS_FIXTURE], + resources: resourcesByCluster('test-cluster'), + }, + { + cluster: { + name: 'other-cluster', + }, + errors: [ + { + errorType: 'NOT_FOUND', + resourcePath: '/some/path', + statusCode: 404, + }, + ], + podMetrics: [], + resources: resourcesByCluster('other-cluster'), + }, + ], + }); + }); }); describe('getCustomResourcesByEntity', () => { diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 64bbec6051..1fa9d23837 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -41,6 +41,7 @@ import { PodFetchResponse, KubernetesRequestAuth, CustomResourceMatcher, + PodStatusFetchResponse, } from '@backstage/plugin-kubernetes-common'; import { ContainerStatus, @@ -163,19 +164,22 @@ const toClientSafeContainer = ( }; const toClientSafePodMetrics = ( - podMetrics: PodStatus[][], + podMetrics: PodStatusFetchResponse[], ): ClientPodStatus[] => { - return podMetrics.flat().map((pd: PodStatus): ClientPodStatus => { - return { - pod: pd.Pod, - memory: toClientSafeResource(pd.Memory), - cpu: toClientSafeResource(pd.CPU), - containers: pd.Containers.map(toClientSafeContainer), - }; - }); + return podMetrics + .map(r => r.resources) + .flat() + .map((pd: PodStatus): ClientPodStatus => { + return { + pod: pd.Pod, + memory: toClientSafeResource(pd.Memory), + cpu: toClientSafeResource(pd.CPU), + containers: pd.Containers.map(toClientSafeContainer), + }; + }); }; -type responseWithMetrics = [FetchResponseWrapper, PodStatus[][]]; +type responseWithMetrics = [FetchResponseWrapper, PodStatusFetchResponse[]]; export class KubernetesFanOutHandler { private readonly logger: Logger; @@ -350,11 +354,17 @@ export class KubernetesFanOutHandler { .filter(isString), ); - const podMetrics = Array.from(namespaces).map(ns => - this.fetcher.fetchPodMetricsByNamespace(clusterDetails, ns), + if (namespaces.size === 0) { + return [result, []]; + } + + const podMetrics = await this.fetcher.fetchPodMetricsByNamespaces( + clusterDetails, + namespaces, ); - return Promise.all([result, Promise.all(podMetrics)]); + result.errors.push(...podMetrics.errors); + return [result, podMetrics.responses as PodStatusFetchResponse[]]; } private getAuthTranslator(provider: string): KubernetesAuthTranslator { diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 25a5c9312b..c2560e58b0 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -17,6 +17,12 @@ import { getVoidLogger } from '@backstage/backend-common'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { ObjectToFetch } from '../types/types'; +import { topPods } from '@kubernetes/client-node'; + +jest.mock('@kubernetes/client-node', () => ({ + ...jest.requireActual('@kubernetes/client-node'), + topPods: jest.fn(), +})); const OBJECTS_TO_FETCH = new Set([ { @@ -33,63 +39,52 @@ const OBJECTS_TO_FETCH = new Set([ }, ]); +const POD_METRICS_FIXTURE = { + containers: [], + cpu: { + currentUsage: 100, + limitTotal: 102, + requestTotal: 101, + }, + memory: { + currentUsage: '1000', + limitTotal: '1002', + requestTotal: '1001', + }, + pod: {}, +}; + describe('KubernetesFetcher', () => { - let clientMock: any; - let kubernetesClientProvider: any; - let sut: KubernetesClientBasedFetcher; + describe('fetchObjectsForService', () => { + let clientMock: any; + let kubernetesClientProvider: any; + let sut: KubernetesClientBasedFetcher; - beforeEach(() => { - jest.resetAllMocks(); - clientMock = { - listClusterCustomObject: jest.fn(), - listNamespacedCustomObject: jest.fn(), - addInterceptor: jest.fn(), - }; + beforeEach(() => { + jest.resetAllMocks(); + clientMock = { + listClusterCustomObject: jest.fn(), + listNamespacedCustomObject: jest.fn(), + addInterceptor: jest.fn(), + }; - kubernetesClientProvider = { - getCustomObjectsClient: jest.fn(() => clientMock), - }; + kubernetesClientProvider = { + getCustomObjectsClient: jest.fn(() => clientMock), + }; - sut = new KubernetesClientBasedFetcher({ - kubernetesClientProvider, - logger: getVoidLogger(), - }); - }); - - const testErrorResponse = async (errorResponse: any, expectedResult: any) => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'pod-name', - }, - }, - ], - }, + sut = new KubernetesClientBasedFetcher({ + kubernetesClientProvider, + logger: getVoidLogger(), + }); }); - clientMock.listClusterCustomObject.mockRejectedValue(errorResponse); - - const result = await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - customResources: [], - }); - - expect(result).toStrictEqual({ - errors: [expectedResult], - responses: [ - { - type: 'pods', - resources: [ + const testErrorResponse = async ( + errorResponse: any, + expectedResult: any, + ) => { + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'pod-name', @@ -97,82 +92,72 @@ describe('KubernetesFetcher', () => { }, ], }, - ], - }); + }); - expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); + clientMock.listClusterCustomObject.mockRejectedValue(errorResponse); - expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ - '', - 'v1', - 'pods', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + customResources: [], + }); - expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ - '', - 'v1', - 'services', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect( - kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, - ).toBe(2); - }; - - it('should return pods, services', async () => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ + expect(result).toStrictEqual({ + errors: [expectedResult], + responses: [ { - metadata: { - name: 'pod-name', - }, + type: 'pods', + resources: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], }, ], - }, - }); + }); - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', - }, - }, - ], - }, - }); + expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); - const result = await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - customResources: [], - }); + expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ + '', + 'v1', + 'pods', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); - expect(result).toStrictEqual({ - errors: [], - responses: [ - { - type: 'pods', - resources: [ + expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ + '', + 'v1', + 'services', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect( + kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, + ).toBe(2); + }; + + it('should return pods, services', async () => { + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'pod-name', @@ -180,9 +165,11 @@ describe('KubernetesFetcher', () => { }, ], }, - { - type: 'services', - resources: [ + }); + + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'service-name', @@ -190,100 +177,79 @@ describe('KubernetesFetcher', () => { }, ], }, - ], - }); + }); - expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); - - expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ - '', - 'v1', - 'pods', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ - '', - 'v1', - 'services', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect( - kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, - ).toBe(2); - }); - it('should return pods, services and customobjects', async () => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'pod-name', - }, - }, - ], - }, - }); - - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', - }, - }, - ], - }, - }); - - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'something-else', - }, - }, - ], - }, - }); - - const result = await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - customResources: [ - { - objectType: 'customresources', - group: 'some-group', - apiVersion: 'v2', - plural: 'things', + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', }, - ], - }); + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + customResources: [], + }); - expect(result).toStrictEqual({ - errors: [], - responses: [ - { - type: 'pods', - resources: [ + expect(result).toStrictEqual({ + errors: [], + responses: [ + { + type: 'pods', + resources: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + { + type: 'services', + resources: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + ], + }); + + expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); + + expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ + '', + 'v1', + 'pods', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ + '', + 'v1', + 'services', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect( + kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, + ).toBe(2); + }); + it('should return pods, services and customobjects', async () => { + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'pod-name', @@ -291,9 +257,11 @@ describe('KubernetesFetcher', () => { }, ], }, - { - type: 'services', - resources: [ + }); + + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'service-name', @@ -301,9 +269,11 @@ describe('KubernetesFetcher', () => { }, ], }, - { - type: 'customresources', - resources: [ + }); + + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'something-else', @@ -311,216 +281,358 @@ describe('KubernetesFetcher', () => { }, ], }, - ], + }); + + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + customResources: [ + { + objectType: 'customresources', + group: 'some-group', + apiVersion: 'v2', + plural: 'things', + }, + ], + }); + + expect(result).toStrictEqual({ + errors: [], + responses: [ + { + type: 'pods', + resources: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + { + type: 'services', + resources: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + { + type: 'customresources', + resources: [ + { + metadata: { + name: 'something-else', + }, + }, + ], + }, + ], + }); + + expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(3); + + expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ + '', + 'v1', + 'pods', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ + '', + 'v1', + 'services', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect(clientMock.listClusterCustomObject.mock.calls[2]).toEqual([ + 'some-group', + 'v2', + 'things', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect( + kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, + ).toBe(3); }); - - expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(3); - - expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ - '', - 'v1', - 'pods', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ - '', - 'v1', - 'services', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[2]).toEqual([ - 'some-group', - 'v2', - 'things', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect( - kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, - ).toBe(3); - }); - // they're in testErrorResponse - // eslint-disable-next-line jest/expect-expect - it('should return pods, bad request error', async () => { - await testErrorResponse( - { - response: { + // they're in testErrorResponse + // eslint-disable-next-line jest/expect-expect + it('should return pods, bad request error', async () => { + await testErrorResponse( + { + response: { + statusCode: 400, + request: { + uri: { + pathname: '/some/path', + }, + }, + }, + }, + { + errorType: 'BAD_REQUEST', + resourcePath: '/some/path', statusCode: 400, - request: { - uri: { - pathname: '/some/path', + }, + ); + }); + // they're in testErrorResponse + // eslint-disable-next-line jest/expect-expect + it('should return pods, unauthorized error', async () => { + await testErrorResponse( + { + response: { + statusCode: 401, + request: { + uri: { + pathname: '/some/path', + }, }, }, }, - }, - { - errorType: 'BAD_REQUEST', - resourcePath: '/some/path', - statusCode: 400, - }, - ); - }); - // they're in testErrorResponse - // eslint-disable-next-line jest/expect-expect - it('should return pods, unauthorized error', async () => { - await testErrorResponse( - { - response: { + { + errorType: 'UNAUTHORIZED_ERROR', + resourcePath: '/some/path', statusCode: 401, - request: { - uri: { - pathname: '/some/path', + }, + ); + }); + // they're in testErrorResponse + // eslint-disable-next-line jest/expect-expect + it('should return pods, system error', async () => { + await testErrorResponse( + { + response: { + statusCode: 500, + request: { + uri: { + pathname: '/some/path', + }, }, }, }, - }, - { - errorType: 'UNAUTHORIZED_ERROR', - resourcePath: '/some/path', - statusCode: 401, - }, - ); - }); - // they're in testErrorResponse - // eslint-disable-next-line jest/expect-expect - it('should return pods, system error', async () => { - await testErrorResponse( - { - response: { + { + errorType: 'SYSTEM_ERROR', + resourcePath: '/some/path', statusCode: 500, - request: { - uri: { - pathname: '/some/path', + }, + ); + }); + // they're in testErrorResponse + // eslint-disable-next-line jest/expect-expect + it('should return pods, unknown error', async () => { + await testErrorResponse( + { + response: { + statusCode: 900, + request: { + uri: { + pathname: '/some/path', + }, }, }, }, - }, - { - errorType: 'SYSTEM_ERROR', - resourcePath: '/some/path', - statusCode: 500, - }, - ); - }); - // they're in testErrorResponse - // eslint-disable-next-line jest/expect-expect - it('should return pods, unknown error', async () => { - await testErrorResponse( - { - response: { + { + errorType: 'UNKNOWN_ERROR', + resourcePath: '/some/path', statusCode: 900, - request: { - uri: { - pathname: '/some/path', - }, - }, }, - }, - { - errorType: 'UNKNOWN_ERROR', - resourcePath: '/some/path', - statusCode: 900, - }, - ); + ); + }); + it('should always add a labelSelector query', async () => { + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + }); + + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + }); + + await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + customResources: [], + }); + + const mockCall = clientMock.listClusterCustomObject.mock.calls[0]; + const actualSelector = mockCall[mockCall.length - 1]; + const expectedSelector = 'backstage.io/kubernetes-id=some-service'; + expect(actualSelector).toBe(expectedSelector); + }); + it('should use namespace if provided', async () => { + clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + }); + + clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + }); + + await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + namespace: 'some-namespace', + customResources: [], + }); + + const mockCall = clientMock.listNamespacedCustomObject.mock.calls[0]; + const namespace = mockCall[2]; + expect(namespace).toBe('some-namespace'); + }); }); - it('should always add a labelSelector query', async () => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ + + describe('fetchPodMetricsByNamespaces', () => { + let kubernetesClientProvider: any; + let sut: KubernetesClientBasedFetcher; + + beforeEach(() => { + jest.resetAllMocks(); + + kubernetesClientProvider = { + getMetricsClient: jest.fn(), + getCoreClientByClusterDetails: jest.fn(), + }; + + sut = new KubernetesClientBasedFetcher({ + kubernetesClientProvider, + logger: getVoidLogger(), + }); + }); + + it('should return pod metrics', async () => { + (topPods as jest.Mock).mockResolvedValue(POD_METRICS_FIXTURE); + + const result = await sut.fetchPodMetricsByNamespaces( + { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + new Set(['ns-a', 'ns-b']), + ); + expect(result).toStrictEqual({ + errors: [], + responses: [ { - metadata: { - name: 'pod-name', - }, + type: 'podstatus', + resources: POD_METRICS_FIXTURE, + }, + { + type: 'podstatus', + resources: POD_METRICS_FIXTURE, }, ], - }, + }); }); - - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', + it('should return pod metrics and error', async () => { + const topPodsMock = topPods as jest.Mock; + topPodsMock + .mockResolvedValueOnce(POD_METRICS_FIXTURE) + .mockRejectedValueOnce({ + response: { + statusCode: 404, + request: { + uri: { + pathname: '/some/path', + }, }, }, - ], - }, - }); + }); - await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - customResources: [], - }); - - const mockCall = clientMock.listClusterCustomObject.mock.calls[0]; - const actualSelector = mockCall[mockCall.length - 1]; - const expectedSelector = 'backstage.io/kubernetes-id=some-service'; - expect(actualSelector).toBe(expectedSelector); - }); - it('should use namespace if provided', async () => { - clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ - body: { - items: [ + const result = await sut.fetchPodMetricsByNamespaces( + { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + new Set(['ns-a', 'ns-b']), + ); + expect(result).toStrictEqual({ + errors: [ { - metadata: { - name: 'pod-name', - }, + errorType: 'NOT_FOUND', + resourcePath: '/some/path', + statusCode: 404, }, ], - }, - }); - - clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ - body: { - items: [ + responses: [ { - metadata: { - name: 'service-name', - }, + type: 'podstatus', + resources: POD_METRICS_FIXTURE, }, ], - }, + }); }); - - await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - namespace: 'some-namespace', - customResources: [], - }); - - const mockCall = clientMock.listNamespacedCustomObject.mock.calls[0]; - const namespace = mockCall[2]; - expect(namespace).toBe('some-namespace'); }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 1189dfa390..fd6400529b 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { CoreV1Api, topPods } from '@kubernetes/client-node'; +import { topPods } from '@kubernetes/client-node'; import lodash, { Dictionary } from 'lodash'; import { Logger } from 'winston'; import { @@ -29,13 +29,9 @@ import { FetchResponse, KubernetesFetchError, KubernetesErrorTypes, + PodStatusFetchResponse, } from '@backstage/plugin-kubernetes-common'; import { KubernetesClientProvider } from './KubernetesClientProvider'; -import { PodStatus } from '@kubernetes/client-node/dist/top'; - -export interface Clients { - core: CoreV1Api; -} export interface KubernetesClientBasedFetcherOptions { kubernetesClientProvider: KubernetesClientProvider; @@ -66,6 +62,8 @@ const statusCodeToErrorType = (statusCode: number): KubernetesErrorTypes => { return 'BAD_REQUEST'; case 401: return 'UNAUTHORIZED_ERROR'; + case 404: + return 'NOT_FOUND'; case 500: return 'SYSTEM_ERROR'; default: @@ -104,10 +102,10 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { return Promise.all(fetchResults).then(fetchResultsToResponseWrapper); } - fetchPodMetricsByNamespace( + fetchPodMetricsByNamespaces( clusterDetails: ClusterDetails, - namespace: string, - ): Promise { + namespaces: Set, + ): Promise { const metricsClient = this.kubernetesClientProvider.getMetricsClient(clusterDetails); const coreApi = @@ -115,7 +113,18 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { clusterDetails, ); - return topPods(coreApi, metricsClient, namespace); + const fetchResults = Array.from(namespaces).map(ns => + topPods(coreApi, metricsClient, ns) + .then(r => { + return { + type: 'podstatus', + resources: r, + } as PodStatusFetchResponse; + }) + .catch(this.captureKubernetesErrorsRethrowOthers.bind(this)), + ); + + return Promise.all(fetchResults).then(fetchResultsToResponseWrapper); } private captureKubernetesErrorsRethrowOthers(e: any): KubernetesFetchError { diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index a90f94c491..ed43935202 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -25,7 +25,6 @@ import type { KubernetesRequestBody, ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-common'; -import { PodStatus } from '@kubernetes/client-node/dist/top'; /** * @@ -53,10 +52,10 @@ export interface KubernetesFetcher { fetchObjectsForService( params: ObjectFetchParams, ): Promise; - fetchPodMetricsByNamespace( + fetchPodMetricsByNamespaces( clusterDetails: ClusterDetails, - namespace: string, - ): Promise; + namespaces: Set, + ): Promise; } /** diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 7071045b04..b85e3c00ee 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-kubernetes-common +## 0.4.4-next.0 + +### Patch Changes + +- cbf5d11fdf: The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + + - **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. + - Add the `PodStatusFetchResponse` to the `FetchResponse` union type. + - Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index 85015662c6..8509fa97cc 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -5,6 +5,7 @@ ```ts import { Entity } from '@backstage/catalog-model'; import type { JsonObject } from '@backstage/types'; +import { PodStatus } from '@kubernetes/client-node'; import { V1ConfigMap } from '@kubernetes/client-node'; import { V1CronJob } from '@kubernetes/client-node'; import { V1DaemonSet } from '@kubernetes/client-node'; @@ -147,7 +148,8 @@ export type FetchResponse = | IngressesFetchResponse | CustomResourceFetchResponse | StatefulSetsFetchResponse - | DaemonSetsFetchResponse; + | DaemonSetsFetchResponse + | PodStatusFetchResponse; // @public (undocumented) export interface HorizontalPodAutoscalersFetchResponse { @@ -177,6 +179,7 @@ export interface JobsFetchResponse { export type KubernetesErrorTypes = | 'BAD_REQUEST' | 'UNAUTHORIZED_ERROR' + | 'NOT_FOUND' | 'SYSTEM_ERROR' | 'UNKNOWN_ERROR'; @@ -230,6 +233,14 @@ export interface PodFetchResponse { type: 'pods'; } +// @public (undocumented) +export interface PodStatusFetchResponse { + // (undocumented) + resources: Array; + // (undocumented) + type: 'podstatus'; +} + // @public (undocumented) export interface ReplicaSetsFetchResponse { // (undocumented) diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 97c42e823d..48c04fb222 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-common", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", - "version": "0.4.3-next.1", + "version": "0.4.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index 149caadb15..3feddd7f4c 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -16,6 +16,7 @@ import type { JsonObject } from '@backstage/types'; import { + PodStatus, V1ConfigMap, V1CronJob, V1DaemonSet, @@ -134,7 +135,8 @@ export type FetchResponse = | IngressesFetchResponse | CustomResourceFetchResponse | StatefulSetsFetchResponse - | DaemonSetsFetchResponse; + | DaemonSetsFetchResponse + | PodStatusFetchResponse; /** @public */ export interface PodFetchResponse { @@ -214,6 +216,12 @@ export interface DaemonSetsFetchResponse { resources: Array; } +/** @public */ +export interface PodStatusFetchResponse { + type: 'podstatus'; + resources: Array; +} + /** @public */ export interface KubernetesFetchError { errorType: KubernetesErrorTypes; @@ -225,6 +233,7 @@ export interface KubernetesFetchError { export type KubernetesErrorTypes = | 'BAD_REQUEST' | 'UNAUTHORIZED_ERROR' + | 'NOT_FOUND' | 'SYSTEM_ERROR' | 'UNKNOWN_ERROR'; diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 75d2c3dc46..2272804b22 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-kubernetes +## 0.7.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-kubernetes-common@0.4.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## 0.7.3 + +### Patch Changes + +- 51af8361de: Add useCustomResources react hook for fetching Kubernetes Custom Resources +- 35a6cfe257: Fix infinite call bug in `useCustomResources` hook +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-kubernetes-common@0.4.3 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + ## 0.7.3-next.1 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 09b177eea2..ee4da4b55f 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.7.3-next.1", + "version": "0.7.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 3ca5529726..8a5a1bee97 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-lighthouse +## 0.3.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index f05e4f9732..8c87e8eec3 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-lighthouse", "description": "A Backstage plugin that integrates towards Lighthouse", - "version": "0.3.10-next.1", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/newrelic-dashboard/CHANGELOG.md b/plugins/newrelic-dashboard/CHANGELOG.md index 82dd592c92..81efe251d3 100644 --- a/plugins/newrelic-dashboard/CHANGELOG.md +++ b/plugins/newrelic-dashboard/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-newrelic-dashboard +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index a7a791db57..a8100bdd38 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic-dashboard", - "version": "0.2.3-next.1", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index 0f2c3808b1..526010f2f8 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-newrelic +## 0.3.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## 0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.28-next.1 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 270a8a3961..0eedad1ced 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-newrelic", "description": "A Backstage plugin that integrates towards New Relic", - "version": "0.3.28-next.1", + "version": "0.3.29-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index c66dbe7484..682da2da91 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-org +## 0.5.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.5.10 + +### Patch Changes + +- f2b4b55636: consistently show parent and child relations in group profile card +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.5.10-next.1 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index c9e72c9584..507660f1a3 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-org", "description": "A Backstage plugin that helps you create entity pages for your organization", - "version": "0.5.10-next.1", + "version": "0.5.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 0638130d2e..ec4a152a4d 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-pagerduty +## 0.5.4-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.5.3-next.1 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 3edffd0a52..64ab795489 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-pagerduty", "description": "A Backstage plugin that integrates towards PagerDuty", - "version": "0.5.3-next.1", + "version": "0.5.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx index 5945c45e83..b23fcb8490 100644 --- a/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx +++ b/plugins/pagerduty/src/components/Incident/IncidentListItem.tsx @@ -22,7 +22,6 @@ import { ListItemText, makeStyles, IconButton, - Link, Typography, Chip, } from '@material-ui/core'; @@ -32,6 +31,7 @@ import { DateTime, Duration } from 'luxon'; import { PagerDutyIncident } from '../types'; import OpenInBrowserIcon from '@material-ui/icons/OpenInBrowser'; import { BackstageTheme } from '@backstage/theme'; +import { Link } from '@backstage/core-components'; const useStyles = makeStyles(theme => ({ denseListIcon: { @@ -100,13 +100,7 @@ export const IncidentListItem = ({ incident }: Props) => { secondary={ Created {createdAt} and assigned to{' '} - - {user?.summary ?? 'nobody'} - + {user?.summary ?? 'nobody'} } /> diff --git a/plugins/periskop-backend/CHANGELOG.md b/plugins/periskop-backend/CHANGELOG.md index 403f762c4e..63ca36cc60 100644 --- a/plugins/periskop-backend/CHANGELOG.md +++ b/plugins/periskop-backend/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-periskop-backend +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.1.8 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## 0.1.8-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index 12c4f62ca6..3a91af5252 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop-backend", - "version": "0.1.8-next.1", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/periskop/CHANGELOG.md b/plugins/periskop/CHANGELOG.md index a263bd1ff4..afec1859f2 100644 --- a/plugins/periskop/CHANGELOG.md +++ b/plugins/periskop/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-periskop +## 0.1.9-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.1.8-next.1 ### Patch Changes diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 944f614b17..53971e9930 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop", - "version": "0.1.8-next.1", + "version": "0.1.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/periskop/src/components/EntityPeriskopErrorsCard/EntityPeriskopErrorsCard.tsx b/plugins/periskop/src/components/EntityPeriskopErrorsCard/EntityPeriskopErrorsCard.tsx index cd4aa6da0f..73a06693b1 100644 --- a/plugins/periskop/src/components/EntityPeriskopErrorsCard/EntityPeriskopErrorsCard.tsx +++ b/plugins/periskop/src/components/EntityPeriskopErrorsCard/EntityPeriskopErrorsCard.tsx @@ -34,7 +34,7 @@ import { Link, } from '@backstage/core-components'; import useAsync from 'react-use/lib/useAsync'; -import { periskopApiRef } from '../..'; +import { periskopApiRef } from '../../plugin'; import { AggregatedError, NotFoundInInstance } from '../../types'; /** diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 7e2f0dff31..d57c79ca08 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-permission-backend +## 0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.5.12 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.5.12-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.5.12-next.1 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index aabcbafde2..16351d26b9 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.12-next.1", + "version": "0.5.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts index 4c50e1646f..80782848e7 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts @@ -30,6 +30,7 @@ import { createPermissionRule, } from '@backstage/plugin-permission-node'; import { PermissionIntegrationClient } from './PermissionIntegrationClient'; +import { z } from 'zod'; describe('PermissionIntegrationClient', () => { describe('applyConditions', () => { @@ -38,8 +39,12 @@ describe('PermissionIntegrationClient', () => { const mockConditions: PermissionCriteria = { not: { allOf: [ - { rule: 'RULE_1', resourceType: 'test-resource', params: [] }, - { rule: 'RULE_2', resourceType: 'test-resource', params: ['abc'] }, + { rule: 'RULE_1', resourceType: 'test-resource', params: {} }, + { + rule: 'RULE_2', + resourceType: 'test-resource', + params: { foo: 'abc' }, + }, ], }, }; @@ -279,7 +284,10 @@ describe('PermissionIntegrationClient', () => { name: 'RULE_1', description: 'Test rule 1', resourceType: 'test-resource', - apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', + paramsSchema: z.object({ + input: z.enum(['yes', 'no']), + }), + apply: (_resource, { input }) => input === 'yes', toQuery: () => { throw new Error('Not implemented'); }, @@ -288,7 +296,11 @@ describe('PermissionIntegrationClient', () => { name: 'RULE_2', description: 'Test rule 2', resourceType: 'test-resource', - apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', + + paramsSchema: z.object({ + input: z.enum(['yes', 'no']), + }), + apply: (_resource, { input }) => input === 'yes', toQuery: () => { throw new Error('Not implemented'); }, @@ -344,7 +356,9 @@ describe('PermissionIntegrationClient', () => { conditions: { rule: 'RULE_1', resourceType: 'test-resource', - params: ['no'], + params: { + input: 'no', + }, }, }, ]), @@ -365,13 +379,17 @@ describe('PermissionIntegrationClient', () => { { rule: 'RULE_1', resourceType: 'test-resource', - params: ['yes'], + params: { + input: 'yes', + }, }, { not: { rule: 'RULE_2', resourceType: 'test-resource', - params: ['no'], + params: { + input: 'no', + }, }, }, ], @@ -382,12 +400,16 @@ describe('PermissionIntegrationClient', () => { { rule: 'RULE_1', resourceType: 'test-resource', - params: ['no'], + params: { + input: 'no', + }, }, { rule: 'RULE_2', resourceType: 'test-resource', - params: ['yes'], + params: { + input: 'yes', + }, }, ], }, diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index b013d4bd09..31c212bffd 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,90 @@ # @backstage/plugin-permission-common +## 0.7.1-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 64848c963c: Properly handle rules that have no parameters in `PermissionClient` +- Updated dependencies + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.6.5-next.1 ### Patch Changes diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md index c0c351d6cd..1c2476d20d 100644 --- a/plugins/permission-common/api-report.md +++ b/plugins/permission-common/api-report.md @@ -4,6 +4,7 @@ ```ts import { Config } from '@backstage/config'; +import { JsonPrimitive } from '@backstage/types'; // @public export type AllOfCriteria = { @@ -172,11 +173,11 @@ export class PermissionClient implements PermissionEvaluator { // @public export type PermissionCondition< TResourceType extends string = string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { resourceType: TResourceType; rule: string; - params: TParams; + params?: TParams; }; // @public @@ -203,6 +204,14 @@ export type PermissionMessageBatch = { items: IdentifiedPermissionMessage[]; }; +// @public +export type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[]; + +// @public +export type PermissionRuleParams = + | undefined + | Record; + // @public export type PolicyDecision = | DefinitivePolicyDecision diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index a8fe1e7947..394cbe896f 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-common", "description": "Isomorphic types and client for Backstage permissions and authorization", - "version": "0.6.5-next.1", + "version": "0.7.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { @@ -43,6 +43,7 @@ "dependencies": { "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/types": "workspace:^", "cross-fetch": "^3.1.5", "uuid": "^8.0.0", "zod": "^3.11.6" diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts index 9b2bdcb9ff..75eb2b1ff3 100644 --- a/plugins/permission-common/src/PermissionClient.test.ts +++ b/plugins/permission-common/src/PermissionClient.test.ts @@ -227,7 +227,7 @@ describe('PermissionClient', () => { conditions: { resourceType: 'test-resource', rule: 'FOO', - params: ['bar'], + params: { foo: 'bar' }, }, }), ); @@ -275,7 +275,7 @@ describe('PermissionClient', () => { conditions: { rule: 'FOO', resourceType: 'test-resource', - params: ['bar'], + params: { foo: 'bar' }, }, }), ); @@ -310,6 +310,40 @@ describe('PermissionClient', () => { ).rejects.toThrow(/request failed with 401/i); }); + it('should handle reponses with rules with no params', async () => { + mockPolicyDecisionHandler.mockImplementationOnce( + (req, res, { json }: RestContext) => { + const responses = req.body.items.map( + (a: IdentifiedPermissionMessage) => ({ + id: a.id, + pluginId: 'test-plugin', + resourceType: 'test-resource', + result: AuthorizeResult.CONDITIONAL, + conditions: { + resourceType: 'test-resource', + rule: 'FOO', + }, + }), + ); + + return res(json({ items: responses })); + }, + ); + + const response = await client.authorizeConditional([ + mockResourceAuthorizeConditional, + ]); + expect(response[0]).toEqual( + expect.objectContaining({ + result: AuthorizeResult.CONDITIONAL, + conditions: { + rule: 'FOO', + resourceType: 'test-resource', + }, + }), + ); + }); + it('should reject responses with missing ids', async () => { mockPolicyDecisionHandler.mockImplementationOnce( (_req, res, { json }: RestContext) => { diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index 9a7bb0bba4..a5d08205d8 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -41,7 +41,7 @@ const permissionCriteriaSchema: z.ZodSchema< .object({ rule: z.string(), resourceType: z.string(), - params: z.array(z.unknown()), + params: z.record(z.any()).optional(), }) .or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() })) .or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() })) diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index f29a639d18..322195a63f 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { ResourcePermission } from '.'; -import { Permission } from './permission'; +import { JsonPrimitive } from '@backstage/types'; +import { Permission, ResourcePermission } from './permission'; /** * A request with a UUID identifier, so that batched responses can be matched up with the original @@ -101,11 +101,11 @@ export type PolicyDecision = */ export type PermissionCondition< TResourceType extends string = string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { resourceType: TResourceType; rule: string; - params: TParams; + params?: TParams; }; /** @@ -148,6 +148,22 @@ export type PermissionCriteria = | NotCriteria | TQuery; +/** + * A parameter to a permission rule. + * + * @public + */ +export type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[]; + +/** + * Types that can be used as parameters to permission rules. + * + * @public + */ +export type PermissionRuleParams = + | undefined + | Record; + /** * An individual request sent to the permission backend. * @public diff --git a/plugins/permission-common/src/types/index.ts b/plugins/permission-common/src/types/index.ts index f244a70b1a..5702f47c1c 100644 --- a/plugins/permission-common/src/types/index.ts +++ b/plugins/permission-common/src/types/index.ts @@ -33,6 +33,8 @@ export type { PolicyDecision, PermissionCondition, PermissionCriteria, + PermissionRuleParam, + PermissionRuleParams, AllOfCriteria, AnyOfCriteria, NotCriteria, diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 9ca95faa76..2d31993117 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,95 @@ # @backstage/plugin-permission-node +## 0.7.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- 9335ad115e: Exported types for the .metadata endpoint of the permission router +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.6.6-next.1 ### Patch Changes diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 702d363137..3f712fe362 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -19,11 +19,14 @@ import { Permission } from '@backstage/plugin-permission-common'; import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { PolicyDecision } from '@backstage/plugin-permission-common'; import { QueryPermissionRequest } from '@backstage/plugin-permission-common'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { TokenManager } from '@backstage/backend-common'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; // @public export type ApplyConditionsRequest = { @@ -53,7 +56,9 @@ export type Condition = TRule extends PermissionRule< infer TResourceType, infer TParams > - ? (...params: TParams) => PermissionCondition + ? undefined extends TParams + ? () => PermissionCondition + : (params: TParams) => PermissionCondition : never; // @public @@ -74,7 +79,7 @@ export const createConditionExports: < TResource, TRules extends Record< string, - PermissionRule + PermissionRule >, >(options: { pluginId: string; @@ -85,7 +90,7 @@ export const createConditionExports: < createConditionalDecision: ( permission: ResourcePermission, conditions: PermissionCriteria< - PermissionCondition + PermissionCondition >, ) => ConditionalPolicyDecision; }; @@ -93,15 +98,15 @@ export const createConditionExports: < // @public export const createConditionFactory: < TResourceType extends string, - TParams extends any[], + TParams extends PermissionRuleParams = PermissionRuleParams, >( rule: PermissionRule, -) => (...params: TParams) => PermissionCondition; +) => (params: TParams) => PermissionCondition; // @public export const createConditionTransformer: < TQuery, - TRules extends PermissionRule[], + TRules extends PermissionRule[], >( permissionRules: [...TRules], ) => ConditionTransformer; @@ -113,7 +118,12 @@ export const createPermissionIntegrationRouter: < >(options: { resourceType: TResourceType; permissions?: Permission[] | undefined; - rules: PermissionRule, unknown[]>[]; + rules: PermissionRule< + TResource, + any, + NoInfer, + PermissionRuleParams + >[]; getResources: (resourceRefs: string[]) => Promise<(TResource | undefined)[]>; }) => express.Router; @@ -122,7 +132,7 @@ export const createPermissionRule: < TResource, TQuery, TResourceType extends string, - TParams extends unknown[], + TParams extends PermissionRuleParams = undefined, >( rule: PermissionRule, ) => PermissionRule; @@ -147,10 +157,24 @@ export const makeCreatePermissionRule: < TResource, TQuery, TResourceType extends string, ->() => ( +>() => ( rule: PermissionRule, ) => PermissionRule; +// @public +export type MetadataResponse = { + permissions?: Permission[]; + rules: MetadataResponseSerializedRule[]; +}; + +// @public +export type MetadataResponseSerializedRule = { + name: string; + description: string; + resourceType: string; + paramsSchema?: ReturnType; +}; + // @public export interface PermissionPolicy { // (undocumented) @@ -165,13 +189,14 @@ export type PermissionRule< TResource, TQuery, TResourceType extends string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { name: string; description: string; resourceType: TResourceType; - apply(resource: TResource, ...params: TParams): boolean; - toQuery(...params: TParams): PermissionCriteria; + paramsSchema?: z.ZodSchema; + apply(resource: TResource, params: NoInfer): boolean; + toQuery(params: NoInfer): PermissionCriteria; }; // @public diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index db2479dc27..33baf825ac 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.6.6-next.1", + "version": "0.7.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -41,7 +41,8 @@ "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", - "zod": "^3.11.6" + "zod": "^3.11.6", + "zod-to-json-schema": "^3.18.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts index 54d5652996..dad57e2b8b 100644 --- a/plugins/permission-node/src/integration/createConditionExports.test.ts +++ b/plugins/permission-node/src/integration/createConditionExports.test.ts @@ -18,6 +18,7 @@ import { AuthorizeResult, createPermission, } from '@backstage/plugin-permission-common'; +import { z } from 'zod'; import { createConditionExports } from './createConditionExports'; import { createPermissionRule } from './createPermissionRule'; @@ -30,23 +31,28 @@ const testIntegration = () => name: 'testRule1', description: 'Test rule 1', resourceType: 'test-resource', - apply: jest.fn( - (_resource: any, _firstParam: string, _secondParam: number) => true, - ), - toQuery: jest.fn((firstParam: string, secondParam: number) => ({ + paramsSchema: z.object({ + foo: z.string(), + bar: z.number(), + }), + apply: (_resource: any, _params) => true, + toQuery: params => ({ query: 'testRule1', - params: [firstParam, secondParam], - })), + params, + }), }), testRule2: createPermissionRule({ name: 'testRule2', description: 'Test rule 2', resourceType: 'test-resource', - apply: jest.fn((_resource: any, _firstParam: object) => false), - toQuery: jest.fn((firstParam: object) => ({ + paramsSchema: z.object({ + foo: z.string().optional(), + }), + apply: (_resource: any) => false, + toQuery: params => ({ query: 'testRule2', - params: [firstParam], - })), + params, + }), }), }, }); @@ -56,16 +62,24 @@ describe('createConditionExports', () => { it('creates condition factories for the supplied rules', () => { const { conditions } = testIntegration(); - expect(conditions.testRule1('a', 1)).toEqual({ + expect( + conditions.testRule1({ + foo: 'a', + bar: 1, + }), + ).toEqual({ rule: 'testRule1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }); - expect(conditions.testRule2({ baz: 'quux' })).toEqual({ + expect(conditions.testRule2({})).toEqual({ rule: 'testRule2', resourceType: 'test-resource', - params: [{ baz: 'quux' }], + params: {}, }); }); }); @@ -85,7 +99,10 @@ describe('createConditionExports', () => { { rule: 'testRule1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, ], }), @@ -98,7 +115,10 @@ describe('createConditionExports', () => { { rule: 'testRule1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, ], }, diff --git a/plugins/permission-node/src/integration/createConditionExports.ts b/plugins/permission-node/src/integration/createConditionExports.ts index ba34c07fed..1c82434add 100644 --- a/plugins/permission-node/src/integration/createConditionExports.ts +++ b/plugins/permission-node/src/integration/createConditionExports.ts @@ -36,7 +36,9 @@ export type Condition = TRule extends PermissionRule< infer TResourceType, infer TParams > - ? (...params: TParams) => PermissionCondition + ? undefined extends TParams + ? () => PermissionCondition + : (params: TParams) => PermissionCondition : never; /** diff --git a/plugins/permission-node/src/integration/createConditionFactory.test.ts b/plugins/permission-node/src/integration/createConditionFactory.test.ts index 1941b5ac0b..520fcc01f9 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.test.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { z } from 'zod'; import { createConditionFactory } from './createConditionFactory'; import { createPermissionRule } from './createPermissionRule'; @@ -22,8 +23,11 @@ describe('createConditionFactory', () => { name: 'test-rule', description: 'test-description', resourceType: 'test-resource', - apply: jest.fn(), - toQuery: jest.fn(), + paramsSchema: z.object({ + foo: z.string(), + }), + apply: (_resource, _params) => true, + toQuery: _params => ({}), }); it('returns a function', () => { @@ -33,10 +37,17 @@ describe('createConditionFactory', () => { describe('return value', () => { it('constructs a condition with the rule name and supplied params', () => { const conditionFactory = createConditionFactory(testRule); - expect(conditionFactory('a', 'b', 1, 2)).toEqual({ + + expect( + conditionFactory({ + foo: 'bar', + }), + ).toEqual({ rule: 'test-rule', resourceType: 'test-resource', - params: ['a', 'b', 1, 2], + params: { + foo: 'bar', + }, }); }); }); diff --git a/plugins/permission-node/src/integration/createConditionFactory.ts b/plugins/permission-node/src/integration/createConditionFactory.ts index 15a5b4fd08..7f8cb1ced8 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { PermissionCondition } from '@backstage/plugin-permission-common'; +import { + PermissionCondition, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; /** @@ -33,12 +36,17 @@ import { PermissionRule } from '../types'; * * @public */ -export const createConditionFactory = - ( - rule: PermissionRule, - ) => - (...params: TParams): PermissionCondition => ({ - rule: rule.name, - resourceType: rule.resourceType, - params, - }); +export const createConditionFactory = < + TResourceType extends string, + TParams extends PermissionRuleParams = PermissionRuleParams, +>( + rule: PermissionRule, +) => { + return (params: TParams): PermissionCondition => { + return { + rule: rule.name, + resourceType: rule.resourceType, + params, + }; + }; +}; diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts index 917449561d..a5507f0b44 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.test.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts @@ -18,6 +18,7 @@ import { PermissionCondition, PermissionCriteria, } from '@backstage/plugin-permission-common'; +import { z } from 'zod'; import { createConditionTransformer } from './createConditionTransformer'; import { createPermissionRule } from './createPermissionRule'; @@ -26,20 +27,22 @@ const transformConditions = createConditionTransformer([ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', + paramsSchema: z.object({ + foo: z.string(), + bar: z.number(), + }), apply: jest.fn(), - toQuery: jest.fn( - (firstParam: string, secondParam: number) => - `test-rule-1:${firstParam}/${secondParam}`, - ), + toQuery: jest.fn(({ foo, bar }) => `test-rule-1:${foo}/${bar}`), }), createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', + paramsSchema: z.object({ + foo: z.string(), + }), apply: jest.fn(), - toQuery: jest.fn( - (firstParam: object) => `test-rule-2:${JSON.stringify(firstParam)}`, - ), + toQuery: jest.fn(({ foo }) => `test-rule-2:${foo}`), }), ]); @@ -52,7 +55,10 @@ describe('createConditionTransformer', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['abc', 123], + params: { + foo: 'abc', + bar: 123, + }, }, expectedResult: 'test-rule-1:abc/123', }, @@ -60,9 +66,11 @@ describe('createConditionTransformer', () => { conditions: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ foo: 0 }], + params: { + foo: '0', + }, }, - expectedResult: 'test-rule-2:{"foo":0}', + expectedResult: 'test-rule-2:0', }, { conditions: { @@ -70,13 +78,22 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: 'b', + }, }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, expectedResult: { - anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'], + anyOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, }, { @@ -85,13 +102,22 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: 'b', + }, }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, expectedResult: { - allOf: ['test-rule-1:a/1', 'test-rule-2:{}'], + allOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, }, { @@ -99,11 +125,13 @@ describe('createConditionTransformer', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: 'a', + }, }, }, expectedResult: { - not: 'test-rule-2:{}', + not: 'test-rule-2:a', }, }, { @@ -114,12 +142,17 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: 'b', + }, }, ], }, @@ -129,61 +162,16 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['b', 2], + params: { + foo: 'b', + bar: 2, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ c: 3 }], - }, - ], - }, - }, - ], - }, - expectedResult: { - allOf: [ - { - anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'], - }, - { - not: { - allOf: ['test-rule-1:b/2', 'test-rule-2:{"c":3}'], - }, - }, - ], - }, - }, - { - conditions: { - allOf: [ - { - anyOf: [ - { - rule: 'test-rule-1', - resourceType: 'test-resource', - params: ['a', 1], - }, - { - rule: 'test-rule-2', - resourceType: 'test-resource', - params: [{ b: 2 }], - }, - ], - }, - { - not: { - allOf: [ - { - rule: 'test-rule-1', - resourceType: 'test-resource', - params: ['c', 3], - }, - { - not: { - rule: 'test-rule-2', - resourceType: 'test-resource', - params: [{ d: 4 }], + params: { + foo: 'c', }, }, ], @@ -194,11 +182,71 @@ describe('createConditionTransformer', () => { expectedResult: { allOf: [ { - anyOf: ['test-rule-1:a/1', 'test-rule-2:{"b":2}'], + anyOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, { not: { - allOf: ['test-rule-1:c/3', { not: 'test-rule-2:{"d":4}' }], + allOf: ['test-rule-1:b/2', 'test-rule-2:c'], + }, + }, + ], + }, + }, + { + conditions: { + allOf: [ + { + anyOf: [ + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: 'b', + }, + }, + ], + }, + { + not: { + allOf: [ + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: { + foo: 'c', + bar: 3, + }, + }, + { + not: { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: 'd', + }, + }, + }, + ], + }, + }, + ], + }, + expectedResult: { + allOf: [ + { + anyOf: ['test-rule-1:a/1', 'test-rule-2:b'], + }, + { + not: { + allOf: ['test-rule-1:c/3', { not: 'test-rule-2:d' }], }, }, ], diff --git a/plugins/permission-node/src/integration/createConditionTransformer.ts b/plugins/permission-node/src/integration/createConditionTransformer.ts index 112bb30d01..4b6f933f79 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { InputError } from '@backstage/errors'; import { AllOfCriteria, AnyOfCriteria, @@ -45,7 +46,14 @@ const mapConditions = ( }; } - return getRule(criteria.rule).toQuery(...criteria.params); + const rule = getRule(criteria.rule); + const result = rule.paramsSchema?.safeParse(criteria.params); + + if (result && !result.success) { + throw new InputError(`Parameters to rule are invalid`, result.error); + } + + return rule.toQuery(criteria.params ?? {}); }; /** diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index ac4005c487..32d814465f 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -21,6 +21,7 @@ import { } from '@backstage/plugin-permission-common'; import express, { Express, Router } from 'express'; import request, { Response } from 'supertest'; +import { z } from 'zod'; import { createPermissionIntegrationRouter } from './createPermissionIntegrationRouter'; import { createPermissionRule } from './createPermissionRule'; @@ -39,16 +40,20 @@ const testRule1 = createPermissionRule({ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - apply: (_resource: any, _firstParam: string, _secondParam: number) => true, - toQuery: (_firstParam: string, _secondParam: number) => ({}), + paramsSchema: z.object({ + foo: z.string(), + bar: z.number().describe('bar'), + }), + apply: (_resource: any, _params) => true, + toQuery: _params => ({}), }); const testRule2 = createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - apply: (_resource: any, _firstParam: object) => false, - toQuery: (_firstParam: object) => ({}), + apply: (_resource: any) => false, + toQuery: () => ({}), }); describe('createPermissionIntegrationRouter', () => { @@ -79,23 +84,31 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['abc', 123], + params: { + foo: 'abc', + bar: 123, + }, }, { anyOf: [ { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], }, }, { @@ -105,12 +118,14 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], }, ], }, @@ -120,12 +135,14 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['b', 2], + params: { + foo: 'b', + bar: 2, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ c: 3 }], }, ], }, @@ -161,16 +178,21 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ foo: 0 }], }, { allOf: [ { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, { @@ -180,12 +202,14 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ b: 2 }], }, ], }, @@ -195,13 +219,15 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['c', 3], + params: { + foo: 'c', + bar: 3, + }, }, { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ d: 4 }], }, }, ], @@ -247,7 +273,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, { @@ -257,7 +286,6 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [], }, }, { @@ -268,7 +296,10 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, }, @@ -280,7 +311,6 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [], }, }, }, @@ -293,12 +323,14 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [], }, ], }, @@ -342,7 +374,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-incorrect-resource-1', - params: [{}], + params: { + foo: {}, + }, }, }, { @@ -352,7 +386,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, }, { @@ -362,7 +398,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-incorrect-resource-2', - params: [{}], + params: { + foo: {}, + }, }, }, ], @@ -390,7 +428,7 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: {}, }, }, ], @@ -427,7 +465,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, { @@ -437,7 +478,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, { @@ -447,7 +491,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, ], @@ -524,13 +571,32 @@ describe('createPermissionIntegrationRouter', () => { name: testRule1.name, description: testRule1.description, resourceType: testRule1.resourceType, - parameters: { count: 2 }, + paramsSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + foo: { + type: 'string', + }, + bar: { + description: 'bar', + type: 'number', + }, + }, + required: ['foo', 'bar'], + type: 'object', + }, }, { name: testRule2.name, description: testRule2.description, resourceType: testRule2.resourceType, - parameters: { count: 1 }, + paramsSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: {}, + type: 'object', + }, }, ], }); diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 28abc2f48d..23b59c9c43 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -17,6 +17,7 @@ import express, { Response } from 'express'; import Router from 'express-promise-router'; import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; import { InputError } from '@backstage/errors'; import { errorHandler } from '@backstage/backend-common'; import { @@ -29,6 +30,7 @@ import { } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; import { + NoInfer, createGetRule, isAndCriteria, isNotCriteria, @@ -45,7 +47,7 @@ const permissionCriteriaSchema: z.ZodSchema< z.object({ rule: z.string(), resourceType: z.string(), - params: z.array(z.unknown()), + params: z.record(z.any()).optional(), }), ]), ); @@ -100,6 +102,29 @@ export type ApplyConditionsResponse = { items: ApplyConditionsResponseEntry[]; }; +/** + * Serialized permission rules, with the paramsSchema + * converted from a ZodSchema to a JsonSchema. + * + * @public + */ +export type MetadataResponseSerializedRule = { + name: string; + description: string; + resourceType: string; + paramsSchema?: ReturnType; +}; + +/** + * Response type for the .metadata endpoint. + * + * @public + */ +export type MetadataResponse = { + permissions?: Permission[]; + rules: MetadataResponseSerializedRule[]; +}; + const applyConditions = ( criteria: PermissionCriteria>, resource: TResource | undefined, @@ -124,16 +149,15 @@ const applyConditions = ( return !applyConditions(criteria.not, resource, getRule); } - return getRule(criteria.rule).apply(resource, ...criteria.params); -}; + const rule = getRule(criteria.rule); + const result = rule.paramsSchema?.safeParse(criteria.params); -/** - * Prevent use of type parameter from contributing to type inference. - * - * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 - * @ignore - */ -type NoInfer = T extends infer S ? S : never; + if (result && !result.success) { + throw new InputError(`Parameters to rule are invalid`, result.error); + } + + return rule.apply(resource, criteria.params ?? {}); +}; /** * Create an express Router which provides an authorization route to allow @@ -190,16 +214,21 @@ export const createPermissionIntegrationRouter = < router.use(express.json()); router.get('/.well-known/backstage/permissions/metadata', (_, res) => { - const serializableRules = rules.map(rule => ({ - name: rule.name, - description: rule.description, - resourceType: rule.resourceType, - parameters: { - count: rule.toQuery.length, - }, - })); + const serializedRules: MetadataResponseSerializedRule[] = rules.map( + rule => ({ + name: rule.name, + description: rule.description, + resourceType: rule.resourceType, + paramsSchema: zodToJsonSchema(rule.paramsSchema ?? z.object({})), + }), + ); - return res.json({ permissions, rules: serializableRules }); + const responseJson: MetadataResponse = { + permissions, + rules: serializedRules, + }; + + return res.json(responseJson); }); const getRule = createGetRule(rules); diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts index 6673fe76de..44187e58a6 100644 --- a/plugins/permission-node/src/integration/createPermissionRule.ts +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; /** @@ -25,7 +26,7 @@ export const createPermissionRule = < TResource, TQuery, TResourceType extends string, - TParams extends unknown[], + TParams extends PermissionRuleParams = undefined, >( rule: PermissionRule, ) => rule; @@ -40,7 +41,7 @@ export const createPermissionRule = < */ export const makeCreatePermissionRule = () => - ( + ( rule: PermissionRule, ) => createPermissionRule(rule); diff --git a/plugins/permission-node/src/integration/util.ts b/plugins/permission-node/src/integration/util.ts index 68b7fff2b8..e448068e17 100644 --- a/plugins/permission-node/src/integration/util.ts +++ b/plugins/permission-node/src/integration/util.ts @@ -22,6 +22,14 @@ import { } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; +/** + * Prevent use of type parameter from contributing to type inference. + * + * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 + * @ignore + */ +export type NoInfer = T extends infer S ? S : never; + /** * Utility function used to parse a PermissionCriteria * @param criteria - a PermissionCriteria diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 66b4a7b22b..36e30714f6 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import type { PermissionCriteria } from '@backstage/plugin-permission-common'; +import type { + PermissionCriteria, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; +import { z } from 'zod'; +import { NoInfer } from './integration/util'; /** * A conditional rule that can be provided in an @@ -36,23 +41,28 @@ export type PermissionRule< TResource, TQuery, TResourceType extends string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { name: string; description: string; resourceType: TResourceType; + /** + * A ZodSchema that reflects the structure of the parameters that are passed to + */ + paramsSchema?: z.ZodSchema; + /** * Apply this rule to a resource already loaded from a backing data source. The params are * arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the * params. */ - apply(resource: TResource, ...params: TParams): boolean; + apply(resource: TResource, params: NoInfer): boolean; /** * Translate this rule to criteria suitable for use in querying a backing data store. The criteria * can be used for loading a collection of resources efficiently with conditional criteria already * applied. */ - toQuery(...params: TParams): PermissionCriteria; + toQuery(params: NoInfer): PermissionCriteria; }; diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 6e4996b989..c8e1cb072e 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-permission-react +## 0.4.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + +## 0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + ## 0.4.6-next.1 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index 27660b6a7a..857e1a3881 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.6-next.1", + "version": "0.4.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist-backend/CHANGELOG.md b/plugins/playlist-backend/CHANGELOG.md index 1799243663..afb6a2867f 100644 --- a/plugins/playlist-backend/CHANGELOG.md +++ b/plugins/playlist-backend/CHANGELOG.md @@ -1,5 +1,89 @@ # @backstage/plugin-playlist-backend +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-playlist-common@0.1.2-next.0 + +## 0.2.0 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-playlist-common@0.1.1 + +## 0.2.0-next.2 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/playlist-backend/api-report.md b/plugins/playlist-backend/api-report.md index c7109efbfb..6b51f316c4 100644 --- a/plugins/playlist-backend/api-report.md +++ b/plugins/playlist-backend/api-report.md @@ -15,6 +15,7 @@ import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionPolicy } from '@backstage/plugin-permission-node'; import { PermissionRule } from '@backstage/plugin-permission-node'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PlaylistMetadata } from '@backstage/plugin-playlist-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -26,7 +27,7 @@ import { ResourcePermission } from '@backstage/plugin-permission-common'; export const createPlaylistConditionalDecision: ( permission: ResourcePermission<'playlist-list'>, conditions: PermissionCriteria< - PermissionCondition<'playlist-list', unknown[]> + PermissionCondition<'playlist-list', PermissionRuleParams> >, ) => ConditionalPolicyDecision; @@ -70,13 +71,15 @@ export const playlistConditions: Conditions<{ PlaylistMetadata, ListPlaylistsFilter, 'playlist-list', - [userOwnershipRefs: string[]] + { + owners: string[]; + } >; isPublic: PermissionRule< PlaylistMetadata, ListPlaylistsFilter, 'playlist-list', - [] + undefined >; }>; diff --git a/plugins/playlist-backend/package.json b/plugins/playlist-backend/package.json index d9790a7d48..6bb557d8a3 100644 --- a/plugins/playlist-backend/package.json +++ b/plugins/playlist-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist-backend", - "version": "0.1.1-next.1", + "version": "0.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,7 +39,8 @@ "node-fetch": "^2.6.7", "uuid": "^8.2.0", "winston": "^3.2.1", - "yn": "^4.0.0" + "yn": "^4.0.0", + "zod": "^3.11.6" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts index 179b0d7f9b..17ee4a840a 100644 --- a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts +++ b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts @@ -67,10 +67,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { resourceType: PLAYLIST_LIST_RESOURCE_TYPE, conditions: { anyOf: [ - playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), playlistConditions.isPublic(), ], }, @@ -89,10 +88,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { resourceType: PLAYLIST_LIST_RESOURCE_TYPE, conditions: { anyOf: [ - playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), playlistConditions.isPublic(), ], }, @@ -109,10 +107,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { result: AuthorizeResult.CONDITIONAL, pluginId: 'playlist', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - conditions: playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + conditions: playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), }); }); @@ -126,10 +123,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { result: AuthorizeResult.CONDITIONAL, pluginId: 'playlist', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - conditions: playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + conditions: playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), }); }); }); diff --git a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts index 86d103e789..b4556fbb4f 100644 --- a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts +++ b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts @@ -68,7 +68,9 @@ export class DefaultPlaylistPermissionPolicy implements PermissionPolicy { ) { return createPlaylistConditionalDecision(request.permission, { anyOf: [ - playlistConditions.isOwner(user?.identity.ownershipEntityRefs ?? []), + playlistConditions.isOwner({ + owners: user?.identity.ownershipEntityRefs ?? [], + }), playlistConditions.isPublic(), ], }); @@ -81,7 +83,9 @@ export class DefaultPlaylistPermissionPolicy implements PermissionPolicy { ) { return createPlaylistConditionalDecision( request.permission, - playlistConditions.isOwner(user?.identity.ownershipEntityRefs ?? []), + playlistConditions.isOwner({ + owners: user?.identity.ownershipEntityRefs ?? [], + }), ); } diff --git a/plugins/playlist-backend/src/permissions/rules.ts b/plugins/playlist-backend/src/permissions/rules.ts index cbf81bee65..99f4725972 100644 --- a/plugins/playlist-backend/src/permissions/rules.ts +++ b/plugins/playlist-backend/src/permissions/rules.ts @@ -19,6 +19,7 @@ import { PLAYLIST_LIST_RESOURCE_TYPE, PlaylistMetadata, } from '@backstage/plugin-playlist-common'; +import { z } from 'zod'; import { ListPlaylistsFilter } from '../service'; @@ -32,11 +33,13 @@ const isOwner = createPlaylistPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the playlist belongs to the user', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - apply: (list: PlaylistMetadata, userOwnershipRefs: string[]) => - userOwnershipRefs.includes(list.owner), - toQuery: (userOwnershipRefs: string[]) => ({ + paramsSchema: z.object({ + owners: z.array(z.string()).describe('List of owner entity refs'), + }), + apply: (list: PlaylistMetadata, { owners }) => owners.includes(list.owner), + toQuery: ({ owners }) => ({ key: 'owner', - values: userOwnershipRefs, + values: owners, }), }); diff --git a/plugins/playlist-common/CHANGELOG.md b/plugins/playlist-common/CHANGELOG.md index aeba2f2cb1..7dcf06740e 100644 --- a/plugins/playlist-common/CHANGELOG.md +++ b/plugins/playlist-common/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-playlist-common +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/playlist-common/package.json b/plugins/playlist-common/package.json index ddb822c47a..287c69495a 100644 --- a/plugins/playlist-common/package.json +++ b/plugins/playlist-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-playlist-common", "description": "Common functionalities for the playlist plugin", - "version": "0.1.1-next.1", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist/CHANGELOG.md b/plugins/playlist/CHANGELOG.md index 705a8b24e1..b064b2e5b6 100644 --- a/plugins/playlist/CHANGELOG.md +++ b/plugins/playlist/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/plugin-playlist +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-playlist-common@0.1.2-next.0 + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-react@1.2.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + - @backstage/plugin-playlist-common@0.1.1 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/playlist/package.json b/plugins/playlist/package.json index 883c83962b..df620fc97a 100644 --- a/plugins/playlist/package.json +++ b/plugins/playlist/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist", - "version": "0.1.1-next.1", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index fb4ab82cf4..91c3419ccd 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-proxy-backend +## 0.2.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.2.31 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## 0.2.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.2.31-next.1 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 2ce2a23420..2deee67f17 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-proxy-backend", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", - "version": "0.2.31-next.1", + "version": "0.2.32-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index b412e2e913..962b167902 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-rollbar-backend +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.1.34 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## 0.1.34-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.34-next.1 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index e4b9a7011c..0fb2c0a4e0 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar-backend", "description": "A Backstage backend plugin that integrates towards Rollbar", - "version": "0.1.34-next.1", + "version": "0.1.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 615a8d13a3..340e06d265 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-rollbar +## 0.4.11-next.0 + +### Patch Changes + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.4.10-next.1 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 3c5fd7295a..1c4b585f5f 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar", "description": "A Backstage plugin that integrates towards Rollbar", - "version": "0.4.10-next.1", + "version": "0.4.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar/src/components/RollbarTopItemsTable/RollbarTopItemsTable.tsx b/plugins/rollbar/src/components/RollbarTopItemsTable/RollbarTopItemsTable.tsx index 45d1ab8e4f..89fa7596b0 100644 --- a/plugins/rollbar/src/components/RollbarTopItemsTable/RollbarTopItemsTable.tsx +++ b/plugins/rollbar/src/components/RollbarTopItemsTable/RollbarTopItemsTable.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Box, Link, Typography } from '@material-ui/core'; +import { Box, Typography } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; import React from 'react'; import { @@ -24,7 +24,7 @@ import { } from '../../api/types'; import { buildItemUrl } from '../../utils'; import { TrendGraph } from '../TrendGraph/TrendGraph'; -import { Table, TableColumn } from '@backstage/core-components'; +import { Table, TableColumn, Link } from '@backstage/core-components'; const columns: TableColumn[] = [ { @@ -34,11 +34,7 @@ const columns: TableColumn[] = [ align: 'left', width: '70px', render: (data: any) => ( - + {data.item.counter} ), diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index ce3ab79f21..1a73a7fc41 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.2.12-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 5f4227b603..a42bfdcf60 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", - "version": "0.2.12-next.1", + "version": "0.2.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts index 23d343f354..bfa4aa0507 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts @@ -74,6 +74,7 @@ describe('fetch:cookiecutter', () => { }; const mockReader: UrlReader = { + readUrl: jest.fn(), read: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index bdc0a6c933..388ad285f1 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + ## 0.4.5-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index c0f0c5e318..6a08271e80 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", "description": "A module for the scaffolder backend that lets you template projects using Rails", - "version": "0.4.5-next.1", + "version": "0.4.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts index 597847130a..98ac3a403f 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts @@ -75,6 +75,7 @@ describe('fetch:rails', () => { const mockReader: UrlReader = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), }; diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 02a136e6c5..52654ecc2a 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.11-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + ## 0.2.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 79ff057a2a..d617fec38e 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.2.10-next.1", + "version": "0.2.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 1e6cb05ec6..caf413992b 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,120 @@ # @backstage/plugin-scaffolder-backend +## 1.8.0-next.0 + +### Minor Changes + +- ea14eb62a2: Added a set of default Prometheus metrics around scaffolding. See below for a list of metrics and an explanation of their labels: + + - `scaffolder_task_count`: Tracks successful task runs. + + Labels: + + - `template`: The entity ref of the scaffolded template + - `user`: The entity ref of the user that invoked the template run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + - `scaffolder_task_duration`: a histogram which tracks the duration of a task run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `result`: A boolean describing whether the task ran successfully + + - `scaffolder_step_count`: a count that tracks each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + - `scaffolder_step_duration`: a histogram which tracks the duration of each step run + + Labels: + + - `template`: The entity ref of the scaffolded template + - `step`: The name of the step that was run + - `result`: A string describing whether the task ran successfully, failed, or was skipped + + You can find a guide for running Prometheus metrics here: https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/prometheus-metrics.md + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-catalog-backend@1.5.1-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/backend-plugin-api@0.1.4-next.0 + - @backstage/plugin-catalog-node@1.2.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + +## 1.7.0 + +### Minor Changes + +- 253453fa14: Added a new property called `additionalTemplateGlobals` which allows you to add global functions to the scaffolder nunjucks templates. +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- 304305dd20: Add `allowAutoMerge` option for `publish:github` action +- 694bfe2d61: Add functionality to shutdown scaffolder tasks if they are stale +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 489621f613: Switching off duplicated timestamp in case of logging via task logger in a custom action +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 1.7.0-next.2 + +### Minor Changes + +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + ## 1.7.0-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 2be7b62f75..37565a8d7a 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "1.7.0-next.1", + "version": "1.8.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -72,6 +72,7 @@ "octokit": "^2.0.0", "octokit-plugin-create-pull-request": "^3.10.0", "p-limit": "^3.1.0", + "prom-client": "^14.0.1", "uuid": "^8.2.0", "vm2": "^3.9.11", "winston": "^3.2.1", diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts index 3c58c833ff..50ec94e099 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts @@ -40,6 +40,7 @@ describe('fetchContent helper', () => { const readTree = jest.fn(); const reader: UrlReader = { read: jest.fn(), + readUrl: jest.fn(), readTree, search: jest.fn(), }; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts index 740f27a623..1595578666 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts @@ -33,6 +33,7 @@ describe('fetch:plain', () => { }), ); const reader: UrlReader = { + readUrl: jest.fn(), read: jest.fn(), readTree: jest.fn(), search: jest.fn(), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts index 5518662e45..bc9b797000 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubIssuesLabel.test.ts @@ -23,7 +23,7 @@ import { import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { TemplateAction } from '../..'; +import { TemplateAction } from '../../types'; const mockOctokit = { rest: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts index 0798b04a93..97fdb3d634 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.test.ts @@ -23,7 +23,7 @@ import { import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; -import { TemplateAction } from '../..'; +import { TemplateAction } from '../../types'; const mockOctokit = { rest: { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index c434d9802b..5893a88485 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -26,7 +26,7 @@ import { PassThrough } from 'stream'; import { generateExampleOutput, isTruthy } from './helper'; import { validate as validateJsonSchema } from 'jsonschema'; import { parseRepoUrl } from '../actions/builtin/publish/util'; -import { TemplateActionRegistry } from '../actions'; +import { TemplateAction, TemplateActionRegistry } from '../actions'; import { TemplateFilter, SecureTemplater, @@ -39,6 +39,7 @@ import { TaskStep, } from '@backstage/plugin-scaffolder-common'; import { UserEntity } from '@backstage/catalog-model'; +import { createCounterMetric, createHistogramMetric } from '../../util/metrics'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -97,6 +98,7 @@ const createStepLogger = ({ export class NunjucksWorkflowRunner implements WorkflowRunner { constructor(private readonly options: NunjucksWorkflowRunnerOptions) {} + private readonly tracker = scaffoldingTracker(); private isSingleTemplateString(input: string) { const { parser, nodes } = nunjucks as unknown as { @@ -202,10 +204,8 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { }); try { + const taskTrack = await this.tracker.taskStart(task); await fs.ensureDir(workspacePath); - await task.emitLog( - `Starting up task with ${task.spec.steps.length} steps`, - ); const context: TemplateContext = { parameters: task.spec.parameters, @@ -214,6 +214,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { }; for (const step of task.spec.steps) { + const stepTrack = await this.tracker.stepStart(task, step); try { if (step.if) { const ifResult = await this.render( @@ -222,19 +223,11 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { renderTemplate, ); if (!isTruthy(ifResult)) { - await task.emitLog( - `Skipping step ${step.id} because it's if condition was false`, - { stepId: step.id, status: 'skipped' }, - ); + await stepTrack.skipFalsy(); continue; } } - await task.emitLog(`Beginning step ${step.name}`, { - stepId: step.id, - status: 'processing', - }); - const action = this.options.actionRegistry.get(step.action); const { taskLogger, streamLogger } = createStepLogger({ task, step }); @@ -266,13 +259,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { )}`, ); if (!action.supportsDryRun) { - task.emitLog( - `Skipping because ${action.id} does not support dry-run`, - { - stepId: step.id, - status: 'skipped', - }, - ); + await taskTrack.skipDryRun(step, action); const outputSchema = action.schema?.output; if (outputSchema) { context.steps[step.id] = { @@ -341,20 +328,16 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { context.steps[step.id] = { output: stepOutput }; - await task.emitLog(`Finished step ${step.name}`, { - stepId: step.id, - status: 'completed', - }); + await stepTrack.markSuccessful(); } catch (err) { - await task.emitLog(String(err.stack), { - stepId: step.id, - status: 'failed', - }); + await taskTrack.markFailed(step, err); + await stepTrack.markFailed(); throw err; } } const output = this.render(task.spec.output, context, renderTemplate); + await taskTrack.markSuccessful(); return { output }; } finally { @@ -364,3 +347,128 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { } } } + +function scaffoldingTracker() { + const taskCount = createCounterMetric({ + name: 'scaffolder_task_count', + help: 'Count of task runs', + labelNames: ['template', 'user', 'result'], + }); + const taskDuration = createHistogramMetric({ + name: 'scaffolder_task_duration', + help: 'Duration of a task run', + labelNames: ['template', 'result'], + }); + const stepCount = createCounterMetric({ + name: 'scaffolder_step_count', + help: 'Count of step runs', + labelNames: ['template', 'step', 'result'], + }); + const stepDuration = createHistogramMetric({ + name: 'scaffolder_step_duration', + help: 'Duration of a step runs', + labelNames: ['template', 'step', 'result'], + }); + + async function taskStart(task: TaskContext) { + await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`); + const template = task.spec.templateInfo?.entityRef || ''; + const user = task.spec.user?.ref || ''; + + const taskTimer = taskDuration.startTimer({ + template, + }); + + async function skipDryRun( + step: TaskStep, + action: TemplateAction, + ) { + task.emitLog(`Skipping because ${action.id} does not support dry-run`, { + stepId: step.id, + status: 'skipped', + }); + } + + async function markSuccessful() { + taskCount.inc({ + template, + user, + result: 'ok', + }); + taskTimer({ result: 'ok' }); + } + + async function markFailed(step: TaskStep, err: Error) { + await task.emitLog(String(err.stack), { + stepId: step.id, + status: 'failed', + }); + taskCount.inc({ + template, + user, + result: 'failed', + }); + taskTimer({ result: 'failed' }); + } + + return { + skipDryRun, + markSuccessful, + markFailed, + }; + } + + async function stepStart(task: TaskContext, step: TaskStep) { + await task.emitLog(`Beginning step ${step.name}`, { + stepId: step.id, + status: 'processing', + }); + const template = task.spec.templateInfo?.entityRef || ''; + + const stepTimer = stepDuration.startTimer({ + template, + step: step.name, + }); + + async function markSuccessful() { + await task.emitLog(`Finished step ${step.name}`, { + stepId: step.id, + status: 'completed', + }); + stepCount.inc({ + template, + step: step.name, + result: 'ok', + }); + stepTimer({ result: 'ok' }); + } + + async function markFailed() { + stepCount.inc({ + template, + step: step.name, + result: 'failed', + }); + stepTimer({ result: 'failed' }); + } + + async function skipFalsy() { + await task.emitLog( + `Skipping step ${step.id} because its if condition was false`, + { stepId: step.id, status: 'skipped' }, + ); + stepTimer({ result: 'skipped' }); + } + + return { + markSuccessful, + markFailed, + skipFalsy, + }; + } + + return { + taskStart, + stepStart, + }; +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 4fc142a991..421afb2329 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -126,12 +126,15 @@ describe('StorageTaskBroker', () => { const logPromise = new Promise(resolve => { const observedEvents = new Array(); - broker2.event$({ taskId, after: undefined }).subscribe(({ events }) => { - observedEvents.push(...events); - if (events.some(e => e.type === 'completion')) { - resolve(observedEvents); - } - }); + const subscription = broker2 + .event$({ taskId, after: undefined }) + .subscribe(({ events }) => { + observedEvents.push(...events); + if (events.some(e => e.type === 'completion')) { + resolve(observedEvents); + subscription.unsubscribe(); + } + }); }); const task = await broker1.claim(); await task.emitLog('log 1'); @@ -148,11 +151,12 @@ describe('StorageTaskBroker', () => { ]); const afterLogs = await new Promise(resolve => { - broker2 + const subscription = broker2 .event$({ taskId, after: logs[1].id }) - .subscribe(({ events }) => - resolve(events.map(e => e.body.message as string)), - ); + .subscribe(({ events }) => { + resolve(events.map(e => e.body.message as string)); + subscription.unsubscribe(); + }); }); expect(afterLogs).toEqual([ 'log 3', diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 9e60bc7727..7a015c111f 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JsonObject, Observable } from '@backstage/types'; -import ObservableImpl from 'zen-observable'; + import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { JsonObject, Observable } from '@backstage/types'; import { Logger } from 'winston'; +import ObservableImpl from 'zen-observable'; import { + SerializedTask, + SerializedTaskEvent, + TaskBroker, + TaskBrokerDispatchOptions, TaskCompletionState, TaskContext, TaskSecrets, TaskStore, - TaskBroker, - SerializedTaskEvent, - SerializedTask, } from './types'; -import { TaskBrokerDispatchOptions } from '.'; /** * TaskManager diff --git a/plugins/scaffolder-backend/src/util/metrics.ts b/plugins/scaffolder-backend/src/util/metrics.ts new file mode 100644 index 0000000000..207135f234 --- /dev/null +++ b/plugins/scaffolder-backend/src/util/metrics.ts @@ -0,0 +1,73 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { + Counter, + CounterConfiguration, + Gauge, + GaugeConfiguration, + Histogram, + HistogramConfiguration, + register, + Summary, + SummaryConfiguration, +} from 'prom-client'; + +export function createCounterMetric( + config: CounterConfiguration, +): Counter { + let metric = register.getSingleMetric(config.name); + if (!metric) { + metric = new Counter(config); + register.registerMetric(metric); + } + return metric as Counter; +} + +export function createGaugeMetric( + config: GaugeConfiguration, +): Gauge { + let metric = register.getSingleMetric(config.name); + if (!metric) { + metric = new Gauge(config); + register.registerMetric(metric); + } + return metric as Gauge; +} + +export function createSummaryMetric( + config: SummaryConfiguration, +): Summary { + let metric = register.getSingleMetric(config.name); + if (!metric) { + metric = new Summary(config); + register.registerMetric(metric); + } + + return metric as Summary; +} + +export function createHistogramMetric( + config: HistogramConfiguration, +): Histogram { + let metric = register.getSingleMetric(config.name); + if (!metric) { + metric = new Histogram(config); + register.registerMetric(metric); + } + + return metric as Histogram; +} diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index aba3278a54..b6f8b3e77b 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-scaffolder-common +## 1.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + +## 1.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/types@1.0.0 + +## 1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 1.2.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index ff3c9a9275..6db049aaed 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-common", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", - "version": "1.2.1-next.1", + "version": "1.2.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 25b7d73e60..709809f5f7 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,92 @@ # @backstage/plugin-scaffolder +## 1.8.0-next.0 + +### Minor Changes + +- edae17309e: Added props to override default Scaffolder page title, subtitle and pageTitleOverride. + Routes like `rootRouteRef`, `selectedTemplateRouteRef`, `nextRouteRef`, `nextSelectedTemplateRouteRef` were made public and can be used in your app (e.g. in custom TemplateCard component). + +### Patch Changes + +- 4830a3569f: Basic analytics instrumentation is now in place: + + - As users make their way through template steps, a `click` event is fired, including the step number. + - After a user clicks "Create" a `create` event is fired, including the name of the software that was just created. The template used at creation is set on the `entityRef` context key. + +- f905853ad6: Prefer using `Link` from `@backstage/core-components` rather than material-UI. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-permission-react@0.4.7-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-scaffolder-common@1.2.2-next.0 + +## 1.7.0 + +### Minor Changes + +- f13d5f3f06: Add support for link to TechDocs and other links defined in template entity specification metadata on TemplateCard +- 05f22193c5: EntityPickers now support flags to control when to include default namespace + in result + +### Patch Changes + +- e4f0a96424: Making the description of the GitLab repoUrl owner field more clearer by focusing it refers to the GitLab namespace. +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 8220f2fd83: Support custom layouts in dry run editor +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- 8960d83013: Add support for `allowedOrganizations` and `allowedOwners` to the `AzureRepoPicker`. +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-react@0.4.6 + +## 1.7.0-next.2 + +### Patch Changes + +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + ## 1.7.0-next.1 ### Patch Changes diff --git a/plugins/scaffolder/README.md b/plugins/scaffolder/README.md index b74c03ec34..83fa01e6e5 100644 --- a/plugins/scaffolder/README.md +++ b/plugins/scaffolder/README.md @@ -79,6 +79,48 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( ``` +### Troubleshooting + +If you encounter the [issue of closing EventStream](https://github.com/backstage/backstage/issues/5535) +which auto-updates logs during task execution, you can enable long polling. To do so, +update your `packages/app/src/apis.ts` file to register a `ScaffolderClient` with the +`useLongPollingLogs` set to `true`. By default, it is `false`. + +```typescript +import { + createApiFactory, + discoveryApiRef, + fetchApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { + scaffolderApiRef, + ScaffolderClient, +} from '@backstage/plugin-scaffolder'; + +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ scmIntegrationsApi, discoveryApi, identityApi, fetchApi }) => + new ScaffolderClient({ + discoveryApi, + identityApi, + scmIntegrationsApi, + fetchApi, + useLongPollingLogs: true, + }), + }), + // ... other factories +``` + +This replaces the default implementation of the `scaffolderApiRef`. + ## Links - [scaffolder-backend](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 23b374541b..158c75e5bd 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -15,20 +15,34 @@ import { Extension } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; import { FieldProps } from '@rjsf/core'; +import { FieldProps as FieldProps_2 } from '@rjsf/utils'; import { FieldValidation } from '@rjsf/core'; +import { FieldValidation as FieldValidation_2 } from '@rjsf/utils'; import type { FormProps } from '@rjsf/core'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; +import { PathParams } from '@backstage/core-plugin-api'; import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { SubRouteRef } from '@backstage/core-plugin-api'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { TaskStep } from '@backstage/plugin-scaffolder-common'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { UIOptionsType } from '@rjsf/utils'; +import { UiSchema } from '@rjsf/utils'; + +// @alpha +export function createNextScaffolderFieldExtension< + TReturnValue = unknown, + TInputProps extends UIOptionsType = {}, +>( + options: NextFieldExtensionOptions, +): Extension>; // @public export function createScaffolderFieldExtension< @@ -155,6 +169,41 @@ export type LogEvent = { taskId: string; }; +// @alpha +export type NextCustomFieldValidator = ( + data: TFieldReturnValue, + field: FieldValidation_2, + context: { + apiHolder: ApiHolder; + }, +) => void | Promise; + +// @alpha +export interface NextFieldExtensionComponentProps< + TFieldReturnValue, + TUiOptions = {}, +> extends PropsWithChildren> { + // (undocumented) + uiSchema?: UiSchema & { + 'ui:options'?: TUiOptions & UIOptionsType; + }; +} + +// @alpha +export type NextFieldExtensionOptions< + TFieldReturnValue = unknown, + TInputProps = unknown, +> = { + name: string; + component: ( + props: NextFieldExtensionComponentProps, + ) => JSX.Element | null; + validation?: NextCustomFieldValidator; +}; + +// @alpha (undocumented) +export const nextRouteRef: RouteRef; + // @alpha export type NextRouterProps = { components?: { @@ -171,6 +220,11 @@ export const NextScaffolderPage: ( props: PropsWithChildren, ) => JSX.Element; +// @alpha (undocumented) +export const nextSelectedTemplateRouteRef: SubRouteRef< + PathParams<'/templates/:namespace/:templateName'> +>; + // @public export const OwnedEntityPickerFieldExtension: FieldExtensionComponent< string, @@ -243,6 +297,9 @@ export interface RepoUrlPickerUiOptions { }; } +// @public (undocumented) +export const rootRouteRef: RouteRef; + // @public export type RouterProps = { components?: { @@ -258,6 +315,11 @@ export type RouterProps = { filter: (entity: Entity) => boolean; }>; defaultPreviewTemplate?: string; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; contextMenu?: { editor?: boolean; actions?: boolean; @@ -464,6 +526,11 @@ export interface ScaffolderUseTemplateSecrets { setSecrets: (input: Record) => void; } +// @public (undocumented) +export const selectedTemplateRouteRef: SubRouteRef< + PathParams<'/templates/:namespace/:templateName'> +>; + // @public export const TaskPage: ({ loadingText }: TaskPageProps) => JSX.Element; diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 9b81d6593a..cfa69c128a 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "1.7.0-next.1", + "version": "1.8.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -55,7 +55,11 @@ "@material-ui/lab": "4.0.0-alpha.57", "@react-hookz/web": "^15.0.0", "@rjsf/core": "^3.2.1", + "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.10", "@rjsf/material-ui": "^3.2.1", + "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.10", + "@rjsf/utils": "^5.0.0-beta.10", + "@rjsf/validator-ajv8": "^5.0.0-beta.10", "@types/json-schema": "^7.0.9", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 8c71df67b6..7afbeb5a97 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -28,6 +28,8 @@ import { errorApiRef, useApi, featureFlagsApiRef, + useAnalytics, + useRouteRefParams, } from '@backstage/core-plugin-api'; import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; import { Theme as MuiTheme } from '@rjsf/material-ui'; @@ -37,6 +39,7 @@ import { Content, StructuredMetadataTable } from '@backstage/core-components'; import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; import { LayoutOptions } from '../../layouts'; +import { selectedTemplateRouteRef } from '../../routes'; const Form = withTheme(MuiTheme); type Step = { @@ -123,6 +126,8 @@ export const MultistepJsonForm = (props: Props) => { finishButtonLabel, layouts, } = props; + const { templateName } = useRouteRefParams(selectedTemplateRouteRef); + const analytics = useAnalytics(); const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); const errorApi = useApi(errorApiRef); @@ -171,7 +176,9 @@ export const MultistepJsonForm = (props: Props) => { onReset(); }; const handleNext = () => { - setActiveStep(Math.min(activeStep + 1, steps.length)); + const stepNum = Math.min(activeStep + 1, steps.length); + setActiveStep(stepNum); + analytics.captureEvent('click', `Next Step (${stepNum})`); }; const handleBack = () => setActiveStep(Math.max(activeStep - 1, 0)); const handleCreate = async () => { @@ -182,6 +189,7 @@ export const MultistepJsonForm = (props: Props) => { setDisableButtons(true); try { await onFinish(); + analytics.captureEvent('create', formData.name || `new ${templateName}`); } catch (err) { errorApi.post(err); } finally { diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 021d73f5e9..097eddf6cd 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -63,6 +63,11 @@ export type RouterProps = { filter: (entity: Entity) => boolean; }>; defaultPreviewTemplate?: string; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; /** * Options for the context menu on the scaffolder page. */ @@ -143,6 +148,7 @@ export const Router = (props: RouterProps) => { groups={groups} TemplateCardComponent={TemplateCardComponent} contextMenu={props.contextMenu} + headerOptions={props.headerOptions} /> } /> diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 60a761d171..9c13db72b1 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -54,12 +54,18 @@ export type ScaffolderPageProps = { actions?: boolean; tasks?: boolean; }; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; }; export const ScaffolderPageContents = ({ TemplateCardComponent, groups, contextMenu, + headerOptions, }: ScaffolderPageProps) => { const registerComponentLink = useRouteRef(registerComponentRouteRef); const otherTemplatesGroup = { @@ -80,6 +86,7 @@ export const ScaffolderPageContents = ({ pageTitleOverride="Create a New Component" title="Create a New Component" subtitle="Create new software components using standard templates" + {...headerOptions} > @@ -134,12 +141,14 @@ export const ScaffolderPage = ({ TemplateCardComponent, groups, contextMenu, + headerOptions, }: ScaffolderPageProps) => ( ); diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index 9d1509e7a6..984e51f3b5 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -24,6 +24,7 @@ import { import { Button, ItemCardHeader, + Link, MarkdownContent, } from '@backstage/core-components'; import { @@ -52,7 +53,6 @@ import { CardMedia, Chip, IconButton, - Link, makeStyles, Tooltip, Typography, @@ -162,7 +162,7 @@ const DeprecationWarning = () => {
diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx index d35b561f2b..a48644e72d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx @@ -13,7 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; +import { + MockAnalyticsApi, + renderInTestApp, + TestApiRegistry, +} from '@backstage/test-utils'; import { act, fireEvent, within } from '@testing-library/react'; import React from 'react'; import { Route, Routes } from 'react-router'; @@ -24,6 +28,7 @@ import { TemplatePage } from './TemplatePage'; import { featureFlagsApiRef, FeatureFlagsApi, + analyticsApiRef, } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; @@ -57,6 +62,8 @@ const featureFlagsApiMock: jest.Mocked = { const errorApiMock = { post: jest.fn(), error$: jest.fn() }; +const analyticsMock = new MockAnalyticsApi(); + const schemaMockValue = { title: 'my-schema', steps: [ @@ -105,6 +112,7 @@ const apis = TestApiRegistry.from( [scaffolderApiRef, scaffolderApiMock], [errorApiRef, errorApiMock], [featureFlagsApiRef, featureFlagsApiMock], + [analyticsApiRef, analyticsMock], ); describe('TemplatePage', () => { @@ -158,6 +166,66 @@ describe('TemplatePage', () => { }); }); + it('captures expected analytics events', async () => { + scaffolderApiMock.scaffold.mockResolvedValue({ taskId: 'xyz' }); + scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue({ + title: 'schema-4-analytics', + steps: [ + { + title: 'Fill in some steps', + schema: { + properties: { + name: { + title: 'Name', + type: 'string', + }, + }, + required: ['name'], + }, + }, + ], + }); + const { findByLabelText, findByText } = await renderInTestApp( + + + , + { + mountedRoutes: { + '/create': rootRouteRef, + }, + }, + ); + + // Fill out the name field + expect(await findByText('Fill in some steps')).toBeInTheDocument(); + fireEvent.change(await findByLabelText('Name', { exact: false }), { + target: { value: 'expected-name' }, + }); + + // Go to the final page + fireEvent.click(await findByText('Next step')); + expect(await findByText('Reset')).toBeInTheDocument(); + + // Create the software + await act(async () => { + fireEvent.click(await findByText('Create')); + }); + + // The "Next Step" button should have fired an event + expect(analyticsMock.getEvents()[0]).toMatchObject({ + action: 'click', + subject: 'Next Step (1)', + context: { entityRef: 'template:default/test' }, + }); + + // And the "Create" button should have fired an event + expect(analyticsMock.getEvents()[1]).toMatchObject({ + action: 'create', + subject: 'expected-name', + context: { entityRef: 'template:default/test' }, + }); + }); + it('navigates away if no template was loaded', async () => { scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue( undefined as any, diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 9eba61b53b..87678d2a83 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -32,6 +32,7 @@ import { createValidator } from './createValidator'; import { Content, Header, InfoCard, Page } from '@backstage/core-components'; import { + AnalyticsContext, errorApiRef, useApi, useApiHolder, @@ -129,41 +130,43 @@ export const TemplatePage = ({ ); return ( - -
- - {loading && } - {schema && ( - - { - return { - ...step, - validate: createValidator( - step.schema, - customFieldValidators, - { apiHolder }, - ), - }; - })} - /> - - )} - - + + +
+ + {loading && } + {schema && ( + + { + return { + ...step, + validate: createValidator( + step.schema, + customFieldValidators, + { apiHolder }, + ), + }; + })} + /> + + )} + + + ); }; diff --git a/plugins/scaffolder/src/extensions/index.tsx b/plugins/scaffolder/src/extensions/index.tsx index 60b9022053..08075b5b9d 100644 --- a/plugins/scaffolder/src/extensions/index.tsx +++ b/plugins/scaffolder/src/extensions/index.tsx @@ -19,8 +19,12 @@ import { CustomFieldValidator, FieldExtensionOptions, FieldExtensionComponentProps, + NextCustomFieldValidator, + NextFieldExtensionOptions, + NextFieldExtensionComponentProps, } from './types'; import { Extension, attachComponentData } from '@backstage/core-plugin-api'; +import { UIOptionsType } from '@rjsf/utils'; export const FIELD_EXTENSION_WRAPPER_KEY = 'scaffolder.extensions.wrapper.v1'; export const FIELD_EXTENSION_KEY = 'scaffolder.extensions.field.v1'; @@ -58,6 +62,32 @@ export function createScaffolderFieldExtension< }; } +/** + * Method for creating field extensions that can be used in the scaffolder + * frontend form. + * @alpha + */ +export function createNextScaffolderFieldExtension< + TReturnValue = unknown, + TInputProps extends UIOptionsType = {}, +>( + options: NextFieldExtensionOptions, +): Extension> { + return { + expose() { + const FieldExtensionDataHolder: any = () => null; + + attachComponentData( + FieldExtensionDataHolder, + FIELD_EXTENSION_KEY, + options, + ); + + return FieldExtensionDataHolder; + }, + }; +} + /** * The Wrapping component for defining fields extensions inside * @@ -76,6 +106,9 @@ export type { CustomFieldValidator, FieldExtensionOptions, FieldExtensionComponentProps, + NextCustomFieldValidator, + NextFieldExtensionOptions, + NextFieldExtensionComponentProps, }; export { DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS } from './default'; diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index 9b42ba478b..62f17dff15 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -15,6 +15,14 @@ */ import { ApiHolder } from '@backstage/core-plugin-api'; import { FieldValidation, FieldProps } from '@rjsf/core'; +import { PropsWithChildren } from 'react'; + +import { + UIOptionsType, + FieldProps as FieldPropsV5, + UiSchema as UiSchemaV5, + FieldValidation as FieldValidationV5, +} from '@rjsf/utils'; /** * Field validation type for Custom Field Extensions. @@ -58,3 +66,45 @@ export interface FieldExtensionComponentProps< 'ui:options'?: TUiOptions; }; } + +/** + * Type for Field Extension Props for RJSF v5 + * + * @alpha + */ +export interface NextFieldExtensionComponentProps< + TFieldReturnValue, + TUiOptions = {}, +> extends PropsWithChildren> { + uiSchema?: UiSchemaV5 & { + 'ui:options'?: TUiOptions & UIOptionsType; + }; +} + +/** + * Field validation type for Custom Field Extensions. + * + * @alpha + */ +export type NextCustomFieldValidator = ( + data: TFieldReturnValue, + field: FieldValidationV5, + context: { apiHolder: ApiHolder }, +) => void | Promise; + +/** + * Type for the Custom Field Extension with the + * name and components and validation function. + * + * @alpha + */ +export type NextFieldExtensionOptions< + TFieldReturnValue = unknown, + TInputProps = unknown, +> = { + name: string; + component: ( + props: NextFieldExtensionComponentProps, + ) => JSX.Element | null; + validation?: NextCustomFieldValidator; +}; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index c16e12beb5..7853667101 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -59,11 +59,23 @@ export { RepoUrlPickerFieldExtension, ScaffolderPage, scaffolderPlugin, - NextScaffolderPage, } from './plugin'; export * from './components'; +export { + rootRouteRef, + nextRouteRef, + selectedTemplateRouteRef, + nextSelectedTemplateRouteRef, +} from './routes'; export type { TaskPageProps } from './components/TaskPage'; /** next exports */ +export { NextScaffolderPage } from './plugin'; export type { NextRouterProps } from './next'; export type { TemplateGroupFilter } from './next'; +export { + createNextScaffolderFieldExtension, + type NextCustomFieldValidator, + type NextFieldExtensionOptions, + type NextFieldExtensionComponentProps, +} from './extensions'; diff --git a/plugins/scaffolder/src/next/Router/Router.tsx b/plugins/scaffolder/src/next/Router/Router.tsx index 758e268cb3..ebe00fdfb8 100644 --- a/plugins/scaffolder/src/next/Router/Router.tsx +++ b/plugins/scaffolder/src/next/Router/Router.tsx @@ -18,10 +18,11 @@ import { Routes, Route, useOutlet } from 'react-router'; import { TemplateListPage } from '../TemplateListPage'; import { TemplateWizardPage } from '../TemplateWizardPage'; import { - FieldExtensionOptions, FIELD_EXTENSION_WRAPPER_KEY, FIELD_EXTENSION_KEY, DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS, + NextFieldExtensionOptions, + FieldExtensionOptions, } from '../../extensions'; import { useElementFilter } from '@backstage/core-plugin-api'; @@ -60,7 +61,7 @@ export const Router = (props: PropsWithChildren) => { .selectByComponentData({ key: FIELD_EXTENSION_WRAPPER_KEY, }) - .findComponentData({ + .findComponentData({ key: FIELD_EXTENSION_KEY, }), ); @@ -73,7 +74,7 @@ export const Router = (props: PropsWithChildren) => { customFieldExtension => customFieldExtension.name === name, ), ), - ]; + ] as NextFieldExtensionOptions[]; return ( @@ -86,7 +87,6 @@ export const Router = (props: PropsWithChildren) => { /> } /> - { return (
- + {text || url}
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx index f605e9c114..12ad05ab75 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.test.tsx @@ -30,7 +30,7 @@ describe('Stepper', () => { }; const { getByText } = await renderInTestApp( - , + , ); for (const step of manifest.steps) { @@ -48,7 +48,7 @@ describe('Stepper', () => { }; const { getByRole } = await renderInTestApp( - , + , ); expect(getByRole('button', { name: 'Next' })).toBeInTheDocument(); @@ -88,7 +88,7 @@ describe('Stepper', () => { }; const { getByRole } = await renderInTestApp( - , + , ); await fireEvent.change(getByRole('textbox', { name: 'name' }), { @@ -134,6 +134,7 @@ describe('Stepper', () => { , ); diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx index f5656092c1..511b020c3c 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/Stepper.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { useApiHolder } from '@backstage/core-plugin-api'; -import { JsonObject } from '@backstage/types'; +import { JsonObject, JsonValue } from '@backstage/types'; import { Stepper as MuiStepper, Step as MuiStep, @@ -22,14 +22,15 @@ import { Button, makeStyles, } from '@material-ui/core'; -import { FieldValidation, withTheme } from '@rjsf/core'; -import { Theme as MuiTheme } from '@rjsf/material-ui'; +import { withTheme } from '@rjsf/core-v5'; +import { ErrorSchema, FieldValidation } from '@rjsf/utils'; import React, { useMemo, useState } from 'react'; -import { FieldExtensionOptions } from '../../../extensions'; +import { NextFieldExtensionOptions } from '../../../extensions'; import { TemplateParameterSchema } from '../../../types'; import { createAsyncValidators } from './createAsyncValidators'; import { useTemplateSchema } from './useTemplateSchema'; import { ReviewState } from './ReviewState'; +import validator from '@rjsf/validator-ajv8'; const useStyles = makeStyles(theme => ({ backButton: { @@ -48,10 +49,14 @@ const useStyles = makeStyles(theme => ({ export interface StepperProps { manifest: TemplateParameterSchema; - extensions: FieldExtensionOptions[]; + extensions: NextFieldExtensionOptions[]; + onComplete: (values: Record) => Promise; } -const Form = withTheme(MuiTheme); +// TODO(blam): We require here, as the types in this package depend on @rjsf/core explicitly +// which is what we're using here as the default types, it needs to depend on @rjsf/core-v5 because +// of the re-writing we're doing. Once we've migrated, we can import this the exact same as before. +const Form = withTheme(require('@rjsf/material-ui-v5').Theme); export const Stepper = (props: StepperProps) => { const { steps } = useTemplateSchema(props.manifest); @@ -92,9 +97,9 @@ export const Stepper = (props: StepperProps) => { const returnedValidation = await validation(formData); - const hasErrors = Object.values(returnedValidation).some(i => { - return i.__errors.length > 0; - }); + const hasErrors = Object.values(returnedValidation).some( + i => i.__errors?.length, + ); if (hasErrors) { setErrors(returnedValidation); @@ -105,10 +110,6 @@ export const Stepper = (props: StepperProps) => { setFormState(current => ({ ...current, ...formData })); }; - const handleCreate = () => { - // TODO(blam): Create the template in a modal with the ability to view the logs etc. - }; - return ( <> @@ -124,7 +125,8 @@ export const Stepper = (props: StepperProps) => {
{activeStep < steps.length ? (
{ > Back -
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts index ed887fb56d..a4a937e6fe 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { JsonObject } from '@backstage/types'; -import { CustomFieldValidator } from '../../../extensions'; +import { NextCustomFieldValidator } from '../../../extensions'; import { createAsyncValidators } from './createAsyncValidators'; describe('createAsyncValidators', () => { @@ -79,13 +79,16 @@ describe('createAsyncValidators', () => { }, }; - const NameField: CustomFieldValidator = (value, { addError }) => { + const NameField: NextCustomFieldValidator = ( + value, + { addError }, + ) => { if (!value) { addError('something is broken here!'); } }; - const AddressField: CustomFieldValidator<{ + const AddressField: NextCustomFieldValidator<{ street?: string; postcode?: string; }> = (value, { addError }) => { @@ -101,8 +104,8 @@ describe('createAsyncValidators', () => { const validate = createAsyncValidators( schema, { - NameField: NameField as CustomFieldValidator, - AddressField: AddressField as CustomFieldValidator, + NameField: NameField as NextCustomFieldValidator, + AddressField: AddressField as NextCustomFieldValidator, }, { apiHolder: { get: jest.fn() }, diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts index d89f0d4e5f..1355387eda 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { JsonObject } from '@backstage/types'; import { ApiHolder } from '@backstage/core-plugin-api'; -import { CustomFieldValidator } from '../../../extensions'; +import { NextCustomFieldValidator } from '../../../extensions'; import { Draft07 as JSONSchema } from 'json-schema-library'; import { createFieldValidation } from './schema'; export const createAsyncValidators = ( rootSchema: JsonObject, - validators: Record>, + validators: Record>, context: { apiHolder: ApiHolder; }, diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts index e2d5e89932..4c647bc045 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { JsonObject } from '@backstage/types'; -import { FieldValidation, UiSchema } from '@rjsf/core'; +import { FieldValidation, UiSchema } from '@rjsf/utils'; function isObject(value: unknown): value is JsonObject { return typeof value === 'object' && value !== null && !Array.isArray(value); @@ -119,7 +119,7 @@ export const createFieldValidation = (): FieldValidation => { const fieldValidation: FieldValidation = { __errors: [] as string[], addError: (message: string) => { - fieldValidation.__errors.push(message); + fieldValidation.__errors?.push(message); }, }; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts index d0d8590a7b..6aad6baa96 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts @@ -15,7 +15,7 @@ */ import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; -import { UiSchema } from '@rjsf/core'; +import { UiSchema } from '@rjsf/utils'; import { TemplateParameterSchema } from '../../../types'; import { extractSchemaFromStep } from './schema'; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index 468db438d5..b650e4a676 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import { Page, Header, @@ -22,8 +22,8 @@ import { InfoCard, MarkdownContent, } from '@backstage/core-components'; -import { FieldExtensionOptions } from '../../extensions'; -import { Navigate } from 'react-router'; +import { NextFieldExtensionOptions } from '../../extensions'; +import { Navigate, useNavigate } from 'react-router'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { errorApiRef, @@ -36,10 +36,16 @@ import useAsync from 'react-use/lib/useAsync'; import { makeStyles } from '@material-ui/core'; import { Stepper } from './Stepper'; import { BackstageTheme } from '@backstage/theme'; -import { nextRouteRef, selectedTemplateRouteRef } from '../../routes'; +import { + nextRouteRef, + scaffolderTaskRouteRef, + selectedTemplateRouteRef, +} from '../../routes'; +import { SecretsContext } from '../../components/secrets/SecretsContext'; +import { JsonValue } from '@backstage/types'; export interface TemplateWizardPageProps { - customFieldExtensions: FieldExtensionOptions[]; + customFieldExtensions: NextFieldExtensionOptions[]; } const useStyles = makeStyles(() => ({ @@ -67,17 +73,32 @@ const useTemplateParameterSchema = (templateRef: string) => { export const TemplateWizardPage = (props: TemplateWizardPageProps) => { const styles = useStyles(); const rootRef = useRouteRef(nextRouteRef); + const taskRoute = useRouteRef(scaffolderTaskRouteRef); + const { secrets } = useContext(SecretsContext) ?? {}; + const scaffolderApi = useApi(scaffolderApiRef); + const navigate = useNavigate(); const { templateName, namespace } = useRouteRefParams( selectedTemplateRouteRef, ); + + const templateRef = stringifyEntityRef({ + kind: 'Template', + namespace, + name: templateName, + }); + const errorApi = useApi(errorApiRef); - const { loading, manifest, error } = useTemplateParameterSchema( - stringifyEntityRef({ - kind: 'Template', - namespace, - name: templateName, - }), - ); + const { loading, manifest, error } = useTemplateParameterSchema(templateRef); + + const onComplete = async (values: Record) => { + const { taskId } = await scaffolderApi.scaffold({ + templateRef, + values, + secrets, + }); + + navigate(taskRoute({ taskId })); + }; useEffect(() => { if (error) { @@ -113,6 +134,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { )} diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 2ef6583b08..7451a3a943 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -30,6 +30,7 @@ export const viewTechDocRouteRef = createExternalRouteRef({ params: ['namespace', 'kind', 'name'], }); +/** @public */ export const rootRouteRef = createRouteRef({ id: 'scaffolder', }); @@ -43,16 +44,19 @@ export const legacySelectedTemplateRouteRef = createSubRouteRef({ path: '/templates/:templateName', }); +/** @alpha */ export const nextRouteRef = createRouteRef({ id: 'scaffolder/next', }); +/** @public */ export const selectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/selected-template', parent: rootRouteRef, path: '/templates/:namespace/:templateName', }); +/** @alpha */ export const nextSelectedTemplateRouteRef = createSubRouteRef({ id: 'scaffolder/next/selected-template', parent: nextRouteRef, @@ -65,6 +69,12 @@ export const scaffolderTaskRouteRef = createSubRouteRef({ path: '/tasks/:taskId', }); +export const nextScaffolderTaskRouteRef = createSubRouteRef({ + id: 'scaffolder/next/task', + parent: nextRouteRef, + path: '/tasks/:taskId', +}); + export const scaffolderListTaskRouteRef = createSubRouteRef({ id: 'scaffolder/list-tasks', parent: rootRouteRef, diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 08deccee3d..30a3367bf4 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.0.3 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + ## 1.0.3-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 49411fafeb..a45c3c43e5 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", "description": "A module for the search backend that implements search using ElasticSearch", - "version": "1.0.3-next.1", + "version": "1.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts index c81950ea41..e32c49559d 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts @@ -63,7 +63,7 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { constructor(options: ElasticSearchSearchEngineIndexerOptions) { super({ batchSize: options.batchSize }); - this.logger = options.logger; + this.logger = options.logger.child({ documentType: options.type }); this.startTimestamp = process.hrtime(); this.type = options.type; this.indexPrefix = options.indexPrefix; diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 32111a501d..dcd0bbdc78 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-search-backend-module-pg +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.4.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.4.1-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 29f80a9218..5e91bfabeb 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-pg", "description": "A module for the search backend that implements search using PostgreSQL", - "version": "0.4.1-next.1", + "version": "0.4.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 41b0066847..bfcd09dcad 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-search-backend-node +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.0.3 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 1.0.3-next.1 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index ffbd089813..a0c4964ccf 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-node", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", - "version": "1.0.3-next.1", + "version": "1.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 0ba9ba0059..907519f289 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -112,13 +112,14 @@ export class IndexBuilder { }); Object.keys(this.collators).forEach(type => { + const taskLogger = this.logger.child({ documentType: type }); scheduler.addToSchedule({ id: `search_index_${type.replace('-', '_').toLocaleLowerCase('en-US')}`, scheduledRunner: this.collators[type].schedule, task: async () => { // Instantiate the collator. const collator = await this.collators[type].factory.getCollator(); - this.logger.info( + taskLogger.info( `Collating documents for ${type} via ${this.collators[type].factory.constructor.name}`, ); @@ -128,7 +129,7 @@ export class IndexBuilder { .concat(this.decorators[type] || []) .map(async factory => { const decorator = await factory.getDecorator(); - this.logger.info( + taskLogger.info( `Attached decorator via ${factory.constructor.name} to ${type} index pipeline.`, ); return decorator; @@ -144,13 +145,13 @@ export class IndexBuilder { [collator, ...decorators, indexer], (error: NodeJS.ErrnoException | null) => { if (error) { - this.logger.error( + taskLogger.error( `Collating documents for ${type} failed: ${error}`, ); reject(error); } else { // Signal index pipeline completion! - this.logger.info(`Collating documents for ${type} succeeded`); + taskLogger.info(`Collating documents for ${type} succeeded`); resolve(); } }, diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts index fde6b9412a..9d922aace6 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts @@ -93,7 +93,7 @@ export class NewlineDelimitedJsonCollatorFactory options.type, options.searchPattern, options.reader, - options.logger, + options.logger.child({ documentType: options.type }), options.visibilityPermission, ); } diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index fcbfc1d8d5..c2cc89ac5b 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,66 @@ # @backstage/plugin-search-backend +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-permission-node@0.7.1-next.0 + - @backstage/plugin-search-backend-node@1.0.4-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.1.0 + +### Minor Changes + +- 16c853a6ed: Be less restrictive with unknown keys on query endpoint +- a799972bb1: The query received by search engines now contains a property called `pageLimit`, it specifies how many results to return per page when sending a query request to the search backend. + + Example: + _Returns up to 30 results per page_ + + ``` + GET /query?pageLimit=30 + ``` + + The search backend validates the page limit and this value must not exceed 100, but it doesn't set a default value for the page limit parameter, it leaves it up to each search engine to set this, so Lunr, Postgres and Elastic Search set 25 results per page as a default value. + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 1.1.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 1.1.0-next.1 ### Minor Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 5e678e5380..d221cf1ea5 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend", "description": "The Backstage backend plugin that provides your backstage app with search", - "version": "1.1.0-next.1", + "version": "1.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index 344f82a757..4ed96d5397 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-search-common +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/types@1.0.1-next.0 + +## 1.1.0 + +### Minor Changes + +- a799972bb1: There is a new property called `pageLimit` on the `SearchQuery` interface that specifies how many results should be returned per page. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + - @backstage/types@1.0.0 + +## 1.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/types@1.0.0 + ## 1.1.0-next.1 ### Minor Changes diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index 99842fba59..a0ec2513b4 100644 --- a/plugins/search-common/package.json +++ b/plugins/search-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-common", "description": "Common functionalities for Search, to be shared between various search-enabled plugins", - "version": "1.1.0-next.1", + "version": "1.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index ade862c218..d5f100d97c 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,211 @@ # @backstage/plugin-search-react +## 1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.2.0 + +### Minor Changes + +- 4ed1fa2480: The search query state now has an optional `pageLimit` property that determines how many results will be requested per page, it defaults to 25. + + Examples: + _Basic_ + + ```jsx + + {results => { + // Item rendering logic is omitted + }} + + ``` + + _With context_ + + ```jsx + + + {results => { + // Item rendering logic is omitted + }} + + + ``` + +- bed5a1dc6e: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + + ``` + +- 3de4bd4f19: A `` component was created for limiting the number of results shown per search page. Use this new component to give users options to select how many search results they want to display per page. The default options are 10, 25, 50, 100. + + See examples below: + + _Basic_ + + ```jsx + import React, { useState } from 'react'; + import { Grid } from '@material-ui/core'; + import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; + import { + SearchBarBase, + SearchPaginationBase, + SearchResultList, + } from '@backstage/plugin-search-react'; + + const SearchPage = () => { + const [term, setTerm] = useState(''); + const [pageLimit, setPageLimit] = useState(25); + const [pageCursor, setPageCursor] = useState(); + + return ( + +
} /> + + + + + + + + + + + + + + + ); + }; + ``` + + _With context_ + + ```jsx + import React from 'react'; + import { Grid } from '@material-ui/core'; + import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; + import { + SearchBar, + SearchResult, + SearchPagination, + SearchResultListLayout, + SearchContextProvider, + DefaultResultListItem, + } from '@backstage/plugin-search-react'; + + const SearchPage = () => ( + + +
} /> + + + + + + + + + + + {({ results }) => ( + ( + + )} + /> + )} + + + + + + + ); + ``` + +- 6faaa05626: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + title="Documentation" + noResultsComponent={} + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + } + title="Documentation" + disableRenderingWithNoResults + /> + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + ## 1.2.0-next.1 ### Minor Changes diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md index ddfe5d5b39..ef3e8b19bd 100644 --- a/plugins/search-react/api-report.md +++ b/plugins/search-react/api-report.md @@ -211,6 +211,52 @@ export type SearchFilterWrapperProps = SearchFilterComponentProps & { debug?: boolean; }; +// @public +export const SearchPagination: (props: SearchPaginationProps) => JSX.Element; + +// @public +export const SearchPaginationBase: ( + props: SearchPaginationBaseProps, +) => JSX.Element; + +// @public +export type SearchPaginationBaseProps = { + className?: string; + total?: number; + cursor?: string; + onCursorChange?: (pageCursor: string) => void; + limit?: number; + limitLabel?: ReactNode; + limitText?: SearchPaginationLimitText; + limitOptions?: SearchPaginationLimitOption[]; + onLimitChange?: (value: number) => void; +}; + +// @public +export type SearchPaginationLimitOption< + Current extends number = 101, + Accumulator extends number[] = [], +> = Accumulator['length'] extends Current + ? Accumulator[number] + : SearchPaginationLimitOption< + Current, + [...Accumulator, Accumulator['length']] + >; + +// @public +export type SearchPaginationLimitText = (params: { + from: number; + to: number; + page: number; + count: number; +}) => ReactNode; + +// @public +export type SearchPaginationProps = Omit< + SearchPaginationBaseProps, + 'pageLimit' | 'onPageLimitChange' | 'pageCursor' | 'onPageCursorChange' +>; + // @public export const SearchResult: (props: SearchResultProps) => JSX.Element; diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index bf7d87ca7a..7097d8fa4e 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.2.0-next.1", + "version": "1.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx new file mode 100644 index 0000000000..4e72c0fa25 --- /dev/null +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx @@ -0,0 +1,63 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { ComponentType } from 'react'; +import { Grid } from '@material-ui/core'; + +import { TestApiProvider } from '@backstage/test-utils'; + +import { searchApiRef, MockSearchApi } from '../../api'; +import { SearchContextProvider } from '../../context'; + +import { SearchPagination } from './SearchPagination'; + +export default { + title: 'Plugins/Search/SearchPagination', + component: SearchPagination, + decorators: [ + (Story: ComponentType<{}>) => ( + + + + + + + + + + ), + ], +}; + +export const Default = () => { + return ; +}; + +export const CustomPageLimitLabel = () => { + return ; +}; + +export const CustomPageLimitText = () => { + return ( + `${from}-${to} of more than ${to}`} + /> + ); +}; + +export const CustomPageLimitOptions = () => { + return ; +}; diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx new file mode 100644 index 0000000000..d2ecc55d31 --- /dev/null +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -0,0 +1,188 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; + +import { searchApiRef } from '../../api'; +import { SearchContextProvider } from '../../context'; + +import { SearchPagination } from './SearchPagination'; + +const query = jest.fn().mockResolvedValue({ + results: [], + nextPageCursor: 'Mg==', + previousPageCursor: 'MA==', +}); + +describe('SearchPagination', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('Renders without exploding', async () => { + await renderWithEffects( + + + + + , + ); + + expect(screen.getByText('Results per page:')).toBeInTheDocument(); + expect(screen.getByText('25')).toBeInTheDocument(); + expect(screen.getByText('1-25')).toBeInTheDocument(); + expect(screen.getByLabelText('Next page')).toBeEnabled(); + expect(screen.getByLabelText('Previous page')).toBeDisabled(); + }); + + it('Define default page limit options', async () => { + await renderWithEffects( + + + + + , + ); + + await userEvent.click(screen.getByText('25')); + + const options = screen.getAllByRole('option'); + expect(options).toHaveLength(4); + expect(options[0]).toHaveTextContent('10'); + expect(options[1]).toHaveTextContent('25'); + expect(options[2]).toHaveTextContent('50'); + expect(options[3]).toHaveTextContent('100'); + }); + + it('Accept custom page limit label', async () => { + const label = 'Page limit:'; + await renderWithEffects( + + + + + , + ); + + expect(screen.getByText(label)).toBeInTheDocument(); + }); + + it('Show the total in text', async () => { + await renderWithEffects( + + + + + , + ); + + expect(screen.getByText('of 100')).toBeInTheDocument(); + }); + + it('Accept custom page limit text', async () => { + await renderWithEffects( + + + `${from}-${to} of more than ${to}`} + /> + + , + ); + + expect(screen.getByText('1-25 of more than 25')).toBeInTheDocument(); + }); + + it('Accept custom page limit options', async () => { + await renderWithEffects( + + + + + , + ); + + await userEvent.click(screen.getByText('25')); + + const options = screen.getAllByRole('option'); + expect(options).toHaveLength(4); + expect(options[0]).toHaveTextContent('5'); + expect(options[1]).toHaveTextContent('10'); + expect(options[2]).toHaveTextContent('20'); + expect(options[3]).toHaveTextContent('25'); + }); + + it('Set page limit in the context', async () => { + await renderWithEffects( + + + + + , + ); + + await userEvent.click(screen.getByText('25')); + + await userEvent.click(screen.getByText('10')); + + expect(query).toHaveBeenCalledWith( + expect.objectContaining({ + pageLimit: 10, + }), + ); + }); + + it('Set page cursor in the context', async () => { + const initialState = { + term: '', + types: [], + filters: {}, + pageCursor: 'MQ==', // page: 1 + }; + + await renderWithEffects( + + + + + , + ); + + await userEvent.click(screen.getByLabelText('Next page')); + + expect(screen.getByText('51-75')).toBeInTheDocument(); + + expect(query).toHaveBeenLastCalledWith( + expect.objectContaining({ + pageCursor: 'Mg==', // page: 2 + }), + ); + + await userEvent.click(screen.getByLabelText('Previous page')); + + expect(screen.getByText('26-50')).toBeInTheDocument(); + + expect(query).toHaveBeenLastCalledWith( + expect.objectContaining({ + pageCursor: 'MQ==', // page: 1 + }), + ); + }); +}); diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx new file mode 100644 index 0000000000..34b6deaff5 --- /dev/null +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx @@ -0,0 +1,190 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { + ReactNode, + ChangeEvent, + MouseEvent, + useCallback, + useMemo, +} from 'react'; +import { TablePagination } from '@material-ui/core'; +import { useSearch } from '../../context'; + +const encodePageCursor = (pageCursor: number): string => { + return Buffer.from(pageCursor.toString(), 'utf-8').toString('base64'); +}; + +const decodePageCursor = (pageCursor?: string): number => { + if (!pageCursor) return 0; + return Number(Buffer.from(pageCursor, 'base64').toString('utf-8')); +}; + +/** + * A page limit option, this value must not be greater than 100. + * @public + */ +export type SearchPaginationLimitOption< + Current extends number = 101, + Accumulator extends number[] = [], +> = Accumulator['length'] extends Current + ? Accumulator[number] + : SearchPaginationLimitOption< + Current, + [...Accumulator, Accumulator['length']] + >; + +/** + * A page limit text, this function is called with a "\{ from, to, page, count \}" object. + * @public + */ +export type SearchPaginationLimitText = (params: { + from: number; + to: number; + page: number; + count: number; +}) => ReactNode; + +/** + * Props for {@link SearchPaginationBase}. + * @public + */ +export type SearchPaginationBaseProps = { + /** + * The component class name. + */ + className?: string; + /** + * The total number of results. + * For an unknown number of items, provide -1. + * Defaults to -1. + */ + total?: number; + /** + * The cursor for the current page. + */ + cursor?: string; + /** + * Callback fired when the current page cursor is changed. + */ + onCursorChange?: (pageCursor: string) => void; + /** + * The limit of results per page. + * Set -1 to display all the results. + */ + limit?: number; + /** + * Customize the results per page label. + * Defaults to "Results per page:". + */ + limitLabel?: ReactNode; + /** + * Customize the results per page text. + * Defaults to "(\{ from, to, count \}) =\> count \> 0 ? `of $\{count\}` : `$\{from\}-$\{to\}`". + */ + limitText?: SearchPaginationLimitText; + /** + * Options for setting how many results show per page. + * If less than two options are available, no select field will be displayed. + * Use -1 for the value with a custom label to show all the results. + * Defaults to [10, 25, 50, 100]. + */ + limitOptions?: SearchPaginationLimitOption[]; + /** + * Callback fired when the number of results per page is changed. + */ + onLimitChange?: (value: number) => void; +}; + +/** + * A component with controls for search results pagination. + * @param props - See {@link SearchPaginationBaseProps}. + * @public + */ +export const SearchPaginationBase = (props: SearchPaginationBaseProps) => { + const { + total: count = -1, + cursor: pageCursor, + onCursorChange: onPageCursorChange, + limit: rowsPerPage = 25, + limitLabel: labelRowsPerPage = 'Results per page:', + limitText: labelDisplayedRows = ({ from, to }) => + count > 0 ? `of ${count}` : `${from}-${to}`, + limitOptions: rowsPerPageOptions, + onLimitChange: onPageLimitChange, + ...rest + } = props; + + const page = useMemo(() => decodePageCursor(pageCursor), [pageCursor]); + + const handlePageChange = useCallback( + (_: MouseEvent | null, newValue: number) => { + onPageCursorChange?.(encodePageCursor(newValue)); + }, + [onPageCursorChange], + ); + + const handleRowsPerPageChange = useCallback( + (e: ChangeEvent) => { + const newValue = e.target.value; + onPageLimitChange?.(parseInt(newValue, 10)); + }, + [onPageLimitChange], + ); + + return ( + + ); +}; + +/** + * Props for {@link SearchPagination}. + * @public + */ +export type SearchPaginationProps = Omit< + SearchPaginationBaseProps, + 'pageLimit' | 'onPageLimitChange' | 'pageCursor' | 'onPageCursorChange' +>; + +/** + * A component for setting the search context page limit and cursor. + * @param props - See {@link SearchPaginationProps}. + * @public + */ +export const SearchPagination = (props: SearchPaginationProps) => { + const { pageLimit, setPageLimit, pageCursor, setPageCursor } = useSearch(); + + return ( + + ); +}; diff --git a/plugins/search-react/src/components/SearchPagination/index.ts b/plugins/search-react/src/components/SearchPagination/index.ts new file mode 100644 index 0000000000..74053f0e11 --- /dev/null +++ b/plugins/search-react/src/components/SearchPagination/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './SearchPagination'; diff --git a/plugins/search-react/src/components/index.ts b/plugins/search-react/src/components/index.ts index 62d8b611a4..5f68905407 100644 --- a/plugins/search-react/src/components/index.ts +++ b/plugins/search-react/src/components/index.ts @@ -20,6 +20,7 @@ export * from './SearchAutocomplete'; export * from './SearchFilter'; export * from './SearchResult'; export * from './SearchResultPager'; +export * from './SearchPagination'; export * from './SearchResultList'; export * from './SearchResultGroup'; export * from './DefaultResultListItem'; diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 9f647525de..5e71299d1e 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/plugin-search +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-react@1.2.0-next.2 + ## 1.0.3-next.1 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 7777eb6565..7d0f6d4f52 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search", "description": "The Backstage plugin that provides your backstage app with search", - "version": "1.0.3-next.1", + "version": "1.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index bbb7556046..661950e6ca 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-sentry +## 0.4.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.4.3-next.1 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 3f35506c06..190e9b048b 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry", "description": "A Backstage plugin that integrates towards Sentry", - "version": "0.4.3-next.1", + "version": "0.4.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index 65f2c7fbbc..e918aa8608 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-shortcuts +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/theme@0.2.16 + +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 0.3.2-next.1 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 7300018e80..2bd0204af7 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-shortcuts", "description": "A Backstage plugin that provides a shortcuts feature to the sidebar", - "version": "0.3.2-next.1", + "version": "0.3.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sonarqube-backend/CHANGELOG.md b/plugins/sonarqube-backend/CHANGELOG.md index ad209e3a25..e752ee9a2b 100644 --- a/plugins/sonarqube-backend/CHANGELOG.md +++ b/plugins/sonarqube-backend/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-sonarqube-backend +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.2 + +### Patch Changes + +- 9c595302cb: Normalize on winston version ^3.2.1 +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.1.2-next.1 ### Patch Changes diff --git a/plugins/sonarqube-backend/package.json b/plugins/sonarqube-backend/package.json index e1f5168ef6..59f02b2950 100644 --- a/plugins/sonarqube-backend/package.json +++ b/plugins/sonarqube-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube-backend", - "version": "0.1.2-next.1", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "express": "^4.18.1", "express-promise-router": "^4.1.0", "node-fetch": "^2.6.7", - "winston": "^3.8.1", + "winston": "^3.2.1", "yn": "^5.0.0" }, "devDependencies": { diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index c6a06294e5..dd5aa80817 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-sonarqube +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.4.2-next.1 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 02f29fd308..b46cb5c0b1 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sonarqube", "description": "", - "version": "0.4.2-next.1", + "version": "0.4.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index f71d3557f9..02a736b782 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-splunk-on-call +## 0.3.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.3.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.3.34-next.1 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index c633319121..0434c7b07b 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-splunk-on-call", "description": "A Backstage plugin that integrates towards Splunk On-Call", - "version": "0.3.34-next.1", + "version": "0.3.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/stack-overflow-backend/CHANGELOG.md b/plugins/stack-overflow-backend/CHANGELOG.md index 71a6531527..50b989079a 100644 --- a/plugins/stack-overflow-backend/CHANGELOG.md +++ b/plugins/stack-overflow-backend/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-stack-overflow-backend +## 0.1.7-next.0 + +### Patch Changes + +- cbe11d1e23: Tweak README +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/cli@0.21.0-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.1.6 + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/cli@0.20.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + ## 0.1.6-next.1 ### Patch Changes diff --git a/plugins/stack-overflow-backend/README.md b/plugins/stack-overflow-backend/README.md index 62115b889c..acff784cc6 100644 --- a/plugins/stack-overflow-backend/README.md +++ b/plugins/stack-overflow-backend/README.md @@ -37,7 +37,7 @@ Before you are able to start index stack overflow questions to search, you need When you have your `packages/backend/src/plugins/search.ts` file ready to make modifications, add the following code snippet to add the `StackOverflowQuestionsCollatorFactory`. Note that you can modify the `requestParams`. -> Note: if your baseUrl is set to the external stack overflow api `https://api.stackexchange.com/2.2`, you can find optional and required parameters under the official API documentation under [`Usage of /questions GET`](https://api.stackexchange.com/docs/questions) +> Note: if your `baseUrl` is set to the external stack overflow api `https://api.stackexchange.com/2.2`, you can find optional and required parameters under the official API documentation under [`Usage of /questions GET`](https://api.stackexchange.com/docs/questions) ```ts indexBuilder.addCollator({ diff --git a/plugins/stack-overflow-backend/config.d.ts b/plugins/stack-overflow-backend/config.d.ts index d329a4389f..617524f0a3 100644 --- a/plugins/stack-overflow-backend/config.d.ts +++ b/plugins/stack-overflow-backend/config.d.ts @@ -21,7 +21,6 @@ export interface Config { stackoverflow?: { /** * The base url of the Stack Overflow API used for the plugin - * @visibility backend */ baseUrl?: string; diff --git a/plugins/stack-overflow-backend/package.json b/plugins/stack-overflow-backend/package.json index 551f1694e1..4714c29551 100644 --- a/plugins/stack-overflow-backend/package.json +++ b/plugins/stack-overflow-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-stack-overflow-backend", - "version": "0.1.6-next.1", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts index 688f54f005..1aa0e5d300 100644 --- a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts +++ b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts @@ -76,7 +76,7 @@ export class StackOverflowQuestionsCollatorFactory this.apiKey = options.apiKey; this.maxPage = options.maxPage; this.requestParams = options.requestParams; - this.logger = options.logger; + this.logger = options.logger.child({ documentType: this.type }); } static fromConfig( diff --git a/plugins/stack-overflow/CHANGELOG.md b/plugins/stack-overflow/CHANGELOG.md index d12473302e..a725475f69 100644 --- a/plugins/stack-overflow/CHANGELOG.md +++ b/plugins/stack-overflow/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-stack-overflow +## 0.1.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-home@0.4.27-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-home@0.4.26 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-home@0.4.26-next.2 + ## 0.1.6-next.1 ### Patch Changes diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index ff748f4384..2b0910bbae 100644 --- a/plugins/stack-overflow/package.json +++ b/plugins/stack-overflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-stack-overflow", - "version": "0.1.6-next.1", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md index 31039fbee6..84a682dc8e 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-tech-insights-backend-module-jsonfc +## 0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-tech-insights-node@0.3.5 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-tech-insights-common@0.2.7 + +## 0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + ## 0.1.21-next.1 ### Patch Changes diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 5e4761bc11..9a811bc65a 100644 --- a/plugins/tech-insights-backend-module-jsonfc/package.json +++ b/plugins/tech-insights-backend-module-jsonfc/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend-module-jsonfc", - "version": "0.1.21-next.1", + "version": "0.1.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-backend/CHANGELOG.md b/plugins/tech-insights-backend/CHANGELOG.md index 2940da433a..b39c640ddb 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-tech-insights-backend +## 0.5.4-next.0 + +### Patch Changes + +- 06cf8f1cf2: Add a default delay to the fact retrievers to prevent cold-start errors +- 30e43717c7: Use `HumanDuration` from `@backstage/types` +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/plugin-tech-insights-node@0.3.6-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## 0.5.3 + +### Patch Changes + +- 296aea34da: The Tech Insights plugin supports running fact retrievers across multiple instances. Update the README to remove the stale instructions. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- f7cbfb97ed: Modify router endpoint to handle singular and collections of request parameters similarly. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-tech-insights-node@0.3.5 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-tech-insights-common@0.2.7 + +## 0.5.3-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + ## 0.5.3-next.1 ### Patch Changes diff --git a/plugins/tech-insights-backend/README.md b/plugins/tech-insights-backend/README.md index c5c6e66270..26dac78db5 100644 --- a/plugins/tech-insights-backend/README.md +++ b/plugins/tech-insights-backend/README.md @@ -114,22 +114,7 @@ const builder = buildTechInsightsContext({ #### Running fact retrievers in a multi-instance installation -Current logic on running scheduled fact retrievers is intended to be executed in a single instance. Running on multi-instane environment there might be some additional data accumulation when multiple fact retrievers would retrieve and persist their facts. To mitigate this it is recommended to mark a single instance to be a specific fact retriever instance. One way to do this is by using environment variables to indicate if the retrievers should be registered. This can be done for example like the code snippet below - -```diff -const builder = buildTechInsightsContext({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - scheduler: env.scheduler, -- factRetrievers: [], -+ factRetrievers: process.env.MAIN_FACT_RETRIEVER_INSTANCE ? [myFactRetrieverRegistration] : [], -}); -``` - -Where the instance dedicated to handling retrieval of facts would have environment variable `MAIN_FACT_RETRIEVER_INSTANCE` set to true. +The Tech Insights plugin utilizes the `PluginTaskScheduler` for scheduling tasks and coordinating the task invocation across instances. See [the PluginTaskScheduler documentation](https://backstage.io/docs/reference/backend-tasks.plugintaskscheduler) for more information. ### Creating Fact Retrievers diff --git a/plugins/tech-insights-backend/api-report.md b/plugins/tech-insights-backend/api-report.md index 94ac35ad9f..8dc8efbb24 100644 --- a/plugins/tech-insights-backend/api-report.md +++ b/plugins/tech-insights-backend/api-report.md @@ -13,7 +13,7 @@ import { FactLifecycle } from '@backstage/plugin-tech-insights-node'; import { FactRetriever } from '@backstage/plugin-tech-insights-node'; import { FactRetrieverRegistration } from '@backstage/plugin-tech-insights-node'; import { FactSchema } from '@backstage/plugin-tech-insights-node'; -import { HumanDuration } from '@backstage/backend-tasks'; +import { HumanDuration } from '@backstage/types'; import { Logger } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -60,6 +60,7 @@ export type FactRetrieverRegistrationOptions = { factRetriever: FactRetriever; lifecycle?: FactLifecycle; timeout?: Duration | HumanDuration; + initialDelay?: Duration | HumanDuration; }; // @public (undocumented) diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index f28dbf4329..6d42c1aecc 100644 --- a/plugins/tech-insights-backend/package.json +++ b/plugins/tech-insights-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend", - "version": "0.5.3-next.1", + "version": "0.5.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -41,6 +41,7 @@ "@backstage/errors": "workspace:^", "@backstage/plugin-tech-insights-common": "workspace:^", "@backstage/plugin-tech-insights-node": "workspace:^", + "@backstage/types": "workspace:^", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", "express": "^4.17.1", diff --git a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts index 2d738b7de8..e223604829 100644 --- a/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts +++ b/plugins/tech-insights-backend/src/service/fact/FactRetrieverEngine.ts @@ -76,6 +76,7 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { private readonly scheduler: PluginTaskScheduler, private readonly defaultCadence?: string, private readonly defaultTimeout?: Duration, + private readonly defaultInitialDelay?: Duration, ) {} static async create(options: { @@ -85,6 +86,7 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { scheduler: PluginTaskScheduler; defaultCadence?: string; defaultTimeout?: Duration; + defaultInitialDelay?: Duration; }) { const { repository, @@ -93,6 +95,7 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { scheduler, defaultCadence, defaultTimeout, + defaultInitialDelay, } = options; const retrievers = await factRetrieverRegistry.listRetrievers(); @@ -106,6 +109,7 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { scheduler, defaultCadence, defaultTimeout, + defaultInitialDelay, ); } @@ -115,17 +119,25 @@ export class DefaultFactRetrieverEngine implements FactRetrieverEngine { await Promise.all( registrations.map(async registration => { - const { factRetriever, cadence, lifecycle, timeout } = registration; + const { factRetriever, cadence, lifecycle, timeout, initialDelay } = + registration; const cronExpression = cadence || this.defaultCadence || randomDailyCron(); const timeLimit = timeout || this.defaultTimeout || Duration.fromObject({ minutes: 5 }); + const initialDelaySetting = + initialDelay || + this.defaultInitialDelay || + Duration.fromObject({ seconds: 5 }); try { await this.scheduler.scheduleTask({ id: factRetriever.id, frequency: { cron: cronExpression }, fn: this.createFactRetrieverHandler(factRetriever, lifecycle), timeout: timeLimit, + // We add a delay in order to prevent errors due to the + // fact that the backend is not yet online in a cold-start scenario + initialDelay: initialDelaySetting, }); newRegs.push(factRetriever.id); } catch (e) { diff --git a/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts b/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts index aaba29ecb1..2d715e5e4c 100644 --- a/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts +++ b/plugins/tech-insights-backend/src/service/fact/createFactRetriever.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { HumanDuration } from '@backstage/backend-tasks'; + +import { HumanDuration } from '@backstage/types'; import { FactLifecycle, FactRetriever, @@ -35,6 +36,7 @@ export type FactRetrieverRegistrationOptions = { factRetriever: FactRetriever; lifecycle?: FactLifecycle; timeout?: Duration | HumanDuration; + initialDelay?: Duration | HumanDuration; }; /** @@ -45,6 +47,7 @@ export type FactRetrieverRegistrationOptions = { * @param factRetriever - Implementation of fact retriever consisting of at least id, version, schema and handler * @param lifecycle - Optional lifecycle definition indicating the cleanup logic of facts when this retriever is run * @param timeout - Optional duration to determine how long the fact retriever should be allowed to run, defaults to 5 minutes + * @param initialDelay - Optional initial delay to determine how long the fact retriever should wait before the initial run, defaults to 5 seconds * * * @remarks @@ -68,11 +71,12 @@ export type FactRetrieverRegistrationOptions = { export function createFactRetrieverRegistration( options: FactRetrieverRegistrationOptions, ): FactRetrieverRegistration { - const { cadence, factRetriever, lifecycle, timeout } = options; + const { cadence, factRetriever, lifecycle, timeout, initialDelay } = options; return { cadence, factRetriever, lifecycle, timeout, + initialDelay, }; } diff --git a/plugins/tech-insights-common/CHANGELOG.md b/plugins/tech-insights-common/CHANGELOG.md index 790b4a12ef..80db666774 100644 --- a/plugins/tech-insights-common/CHANGELOG.md +++ b/plugins/tech-insights-common/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-tech-insights-common +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 0.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 0.2.7-next.1 ### Patch Changes diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json index a47a5d1c8d..016ccc9a2a 100644 --- a/plugins/tech-insights-common/package.json +++ b/plugins/tech-insights-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-common", - "version": "0.2.7-next.1", + "version": "0.2.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-node/CHANGELOG.md b/plugins/tech-insights-node/CHANGELOG.md index e8419a8d3f..8173dbc132 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-tech-insights-node +## 0.3.6-next.0 + +### Patch Changes + +- 06cf8f1cf2: Add a default delay to the fact retrievers to prevent cold-start errors +- 30e43717c7: Use `HumanDuration` from `@backstage/types` +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## 0.3.5 + +### Patch Changes + +- 0963b4d5fb: Updated package role to be `node-library`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7 + +## 0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + ## 0.3.5-next.1 ### Patch Changes diff --git a/plugins/tech-insights-node/api-report.md b/plugins/tech-insights-node/api-report.md index a57cf3ec4a..47e76b07ce 100644 --- a/plugins/tech-insights-node/api-report.md +++ b/plugins/tech-insights-node/api-report.md @@ -8,7 +8,7 @@ import { Config } from '@backstage/config'; import { DateTime } from 'luxon'; import { Duration } from 'luxon'; import { DurationLike } from 'luxon'; -import { HumanDuration } from '@backstage/backend-tasks'; +import { HumanDuration } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Logger } from 'winston'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -75,6 +75,7 @@ export type FactRetrieverRegistration = { cadence?: string; timeout?: Duration | HumanDuration; lifecycle?: FactLifecycle; + initialDelay?: Duration | HumanDuration; }; // @public diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json index 22802bccc9..7300089744 100644 --- a/plugins/tech-insights-node/package.json +++ b/plugins/tech-insights-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-node", - "version": "0.3.5-next.1", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-node/src/facts.ts b/plugins/tech-insights-node/src/facts.ts index 41db302fd5..a4a0834970 100644 --- a/plugins/tech-insights-node/src/facts.ts +++ b/plugins/tech-insights-node/src/facts.ts @@ -15,13 +15,12 @@ */ import { DateTime, Duration, DurationLike } from 'luxon'; import { Config } from '@backstage/config'; -import { JsonValue } from '@backstage/types'; +import { HumanDuration, JsonValue } from '@backstage/types'; import { PluginEndpointDiscovery, TokenManager, } from '@backstage/backend-common'; import { Logger } from 'winston'; -import { HumanDuration } from '@backstage/backend-tasks'; /** * A container for facts. The shape of the fact records needs to correspond to the FactSchema with same `ref` value. @@ -279,4 +278,11 @@ export type FactRetrieverRegistration = { * If defined this value will be used to determine expired items which will deleted when this fact retriever is run */ lifecycle?: FactLifecycle; + + /** + * A duration to determine the initial delay for the fact retriever. Useful for cold start scenarios when e.g. the + * catalog backend is not yet available. Defaults to 5 seconds. + * + */ + initialDelay?: Duration | HumanDuration; }; diff --git a/plugins/tech-insights/CHANGELOG.md b/plugins/tech-insights/CHANGELOG.md index f605295d31..f60027e045 100644 --- a/plugins/tech-insights/CHANGELOG.md +++ b/plugins/tech-insights/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-tech-insights +## 0.3.2-next.0 + +### Patch Changes + +- 7095e8bc03: Fixed bug when sending data by Post in `runChecks` and `runBulkChecks` functions of the `TechInsightsClient` class, the default `Content-Type` used was `plain/text` +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-tech-insights-common@0.2.8-next.0 + +## 0.3.1 + +### Patch Changes + +- f3d272cf57: Make sure to reload score card contents when props change +- a60a6807bd: making available the search for the last FACTS executed +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7 + +## 0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + ## 0.3.1-next.1 ### Patch Changes diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index 8d9b225b29..cb9def285e 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights", - "version": "0.3.1-next.1", + "version": "0.3.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights/src/api/TechInsightsClient.ts b/plugins/tech-insights/src/api/TechInsightsClient.ts index 9bdeb119f9..6e85769b34 100644 --- a/plugins/tech-insights/src/api/TechInsightsClient.ts +++ b/plugins/tech-insights/src/api/TechInsightsClient.ts @@ -105,14 +105,18 @@ export class TechInsightsClient implements TechInsightsApi { const url = await this.discoveryApi.getBaseUrl('tech-insights'); const { token } = await this.identityApi.getCredentials(); - const request = new Request(`${url}${path}`, init); - if (!request.headers.has('content-type')) { - request.headers.set('content-type', 'application/json'); - } - if (token && !request.headers.has('authorization')) { - request.headers.set('authorization', `Bearer ${token}`); + const headers: HeadersInit = new Headers(init?.headers); + if (!headers.has('content-type')) + headers.set('content-type', 'application/json'); + if (token && !headers.has('authorization')) { + headers.set('authorization', `Bearer ${token}`); } + const request = new Request(`${url}${path}`, { + ...init, + headers, + }); + return fetch(request).then(async response => { if (!response.ok) { throw await ResponseError.fromResponse(response); diff --git a/plugins/tech-insights/src/components/ScorecardsCard/ScorecardsCard.tsx b/plugins/tech-insights/src/components/ScorecardsCard/ScorecardsCard.tsx index 52909acad9..4bbc283829 100644 --- a/plugins/tech-insights/src/components/ScorecardsCard/ScorecardsCard.tsx +++ b/plugins/tech-insights/src/components/ScorecardsCard/ScorecardsCard.tsx @@ -16,10 +16,9 @@ import React from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { Progress } from '@backstage/core-components'; +import { ErrorPanel, Progress } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { ScorecardInfo } from '../ScorecardsInfo'; -import Alert from '@material-ui/lab/Alert'; import { techInsightsApiRef } from '../../api/TechInsightsApi'; import { useEntity } from '@backstage/plugin-catalog-react'; import { getCompoundEntityRef } from '@backstage/catalog-model'; @@ -31,15 +30,16 @@ export const ScorecardsCard = (props: { }) => { const { title, description, checksId } = props; const api = useApi(techInsightsApiRef); - const { namespace, kind, name } = getCompoundEntityRef(useEntity().entity); + const { entity } = useEntity(); const { value, loading, error } = useAsync( - async () => await api.runChecks({ namespace, kind, name }, checksId), + async () => await api.runChecks(getCompoundEntityRef(entity), checksId), + [api, entity, JSON.stringify(checksId)], ); if (loading) { return ; } else if (error) { - return {error.message}; + return ; } return ( diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 24ff4c2f4f..df5bee360a 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-tech-radar +## 0.5.18-next.0 + +### Patch Changes + +- 1f888af5f6: Fixed bug in Tech Radar where, on hover, the tech list quadrant would rerender and scroll top +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/theme@0.2.16 + +## 0.5.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.5.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + ## 0.5.17-next.1 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 5dafe2de9b..a4dd1f0848 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-tech-radar", "description": "A Backstage plugin that lets you display a Tech Radar for your organization", - "version": "0.5.17-next.1", + "version": "0.5.18-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-radar/src/components/Radar/Radar.tsx b/plugins/tech-radar/src/components/Radar/Radar.tsx index e6ae6d385d..8ba5af9811 100644 --- a/plugins/tech-radar/src/components/Radar/Radar.tsx +++ b/plugins/tech-radar/src/components/Radar/Radar.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ -import React, { useState, useRef } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; +import type { Entry, Quadrant, Ring } from '../../utils/types'; import RadarPlot from '../RadarPlot'; -import type { Ring, Quadrant, Entry } from '../../utils/types'; -import { adjustQuadrants, adjustRings, adjustEntries } from './utils'; +import { adjustEntries, adjustQuadrants, adjustRings } from './utils'; export type Props = { width: number; @@ -28,17 +28,40 @@ export type Props = { svgProps?: object; }; -const Radar = (props: Props): JSX.Element => { - const { width, height, quadrants, rings, entries } = props; +const Radar = ({ + width, + height, + quadrants, + rings, + entries, + ...props +}: Props): JSX.Element => { const radius = Math.min(width, height) / 2; + // State const [activeEntry, setActiveEntry] = useState(); const node = useRef(null); - // TODO(dflemstr): most of this can be heavily memoized if performance becomes a problem - adjustQuadrants(quadrants, radius, width, height); - adjustRings(rings, radius); - adjustEntries(entries, quadrants, rings, radius, activeEntry); + // Adjusted props + const adjustedQuadrants = useMemo( + () => adjustQuadrants(quadrants, radius, width, height), + [quadrants, radius, width, height], + ); + const adjustedRings = useMemo( + () => adjustRings(rings, radius), + [radius, rings], + ); + const adjustedEntries = useMemo( + () => + adjustEntries( + entries, + adjustedQuadrants, + adjustedRings, + radius, + activeEntry, + ), + [entries, adjustedQuadrants, adjustedRings, radius, activeEntry], + ); return ( @@ -46,9 +69,9 @@ const Radar = (props: Props): JSX.Element => { width={width} height={height} radius={radius} - entries={entries} - quadrants={quadrants} - rings={rings} + entries={adjustedEntries} + quadrants={adjustedQuadrants} + rings={adjustedRings} activeEntry={activeEntry} onEntryMouseEnter={entry => setActiveEntry(entry)} onEntryMouseLeave={() => setActiveEntry(undefined)} diff --git a/plugins/tech-radar/src/components/Radar/utils.ts b/plugins/tech-radar/src/components/Radar/utils.ts index e41b0a27ab..7fff038023 100644 --- a/plugins/tech-radar/src/components/Radar/utils.ts +++ b/plugins/tech-radar/src/components/Radar/utils.ts @@ -17,7 +17,7 @@ import color from 'color'; import { forceCollide, forceSimulation } from 'd3-force'; import Segment from '../../utils/segment'; -import type { Ring, Quadrant, Entry } from '../../utils/types'; +import type { Entry, Quadrant, Ring } from '../../utils/types'; export const adjustQuadrants = ( quadrants: Quadrant[], @@ -81,18 +81,21 @@ export const adjustQuadrants = ( }, ]; - quadrants.forEach((quadrant, index) => { + return quadrants.map((quadrant, index) => { const legendParam = legendParams[index % 4]; - quadrant.index = index; - quadrant.radialMin = (index * Math.PI) / 2; - quadrant.radialMax = ((index + 1) * Math.PI) / 2; - quadrant.offsetX = index % 4 === 0 || index % 4 === 3 ? 1 : -1; - quadrant.offsetY = index % 4 === 0 || index % 4 === 1 ? 1 : -1; - quadrant.legendX = legendParam.x; - quadrant.legendY = legendParam.y; - quadrant.legendWidth = legendParam.width; - quadrant.legendHeight = legendParam.height; + return { + ...quadrant, + index, + radialMin: (index * Math.PI) / 2, + radialMax: ((index + 1) * Math.PI) / 2, + offsetX: index % 4 === 0 || index % 4 === 3 ? 1 : -1, + offsetY: index % 4 === 0 || index % 4 === 1 ? 1 : -1, + legendX: legendParam.x, + legendY: legendParam.y, + legendWidth: legendParam.width, + legendHeight: legendParam.height, + }; }); }; @@ -102,9 +105,9 @@ export const adjustEntries = ( rings: Ring[], radius: number, activeEntry?: Entry, -) => { +): Entry[] => { let seed = 42; - entries.forEach((entry, index) => { + const adjustedEntries = entries.map((entry, index) => { const quadrant = quadrants.find(q => { const match = typeof entry.quadrant === 'object' ? entry.quadrant.id : entry.quadrant; @@ -123,22 +126,26 @@ export const adjustEntries = ( if (!ring) { throw new Error(`Unknown ring ${entry.ring} for entry ${entry.id}!`); } + const segment = new Segment(quadrant, ring, radius, () => seed++); + const point = segment?.random(); - entry.index = index; - entry.quadrant = quadrant; - entry.ring = ring; - entry.segment = new Segment(quadrant, ring, radius, () => seed++); - const point = entry.segment.random(); - entry.x = point.x; - entry.y = point.y; - entry.active = activeEntry ? entry.id === activeEntry.id : false; - entry.color = entry.active - ? entry.ring.color - : color(entry.ring.color).desaturate(0.5).lighten(0.1).string(); + return { + ...entry, + index: index, + quadrant: quadrant, + ring: ring, + segment, + x: point.x, + y: point.y, + color: + activeEntry && entry.id === activeEntry?.id + ? entry.ring.color + : color(entry.ring.color).desaturate(0.5).lighten(0.1).string(), + }; }); const simulation = forceSimulation() - .nodes(entries) + .nodes(adjustedEntries) .velocityDecay(0.19) .force('collision', forceCollide().radius(12).strength(0.85)) .stop(); @@ -153,20 +160,21 @@ export const adjustEntries = ( ) { simulation.tick(); - for (const entry of entries) { + for (const entry of adjustedEntries) { if (entry.segment) { entry.x = entry.segment.clipx(entry); entry.y = entry.segment.clipy(entry); } } } + + return adjustedEntries; }; -export const adjustRings = (rings: Ring[], radius: number) => { - rings.forEach((ring, index) => { - ring.index = index; - ring.outerRadius = ((index + 2) / (rings.length + 1)) * radius; - ring.innerRadius = - ((index === 0 ? 0 : index + 1) / (rings.length + 1)) * radius; - }); -}; +export const adjustRings = (rings: Ring[], radius: number) => + rings.map((ring, index) => ({ + ...ring, + index, + outerRadius: ((index + 2) / (rings.length + 1)) * radius, + innerRadius: ((index === 0 ? 0 : index + 1) / (rings.length + 1)) * radius, + })); diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx index 1f74efe650..e0b5f09a63 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.test.tsx @@ -14,15 +14,16 @@ * limitations under the License. */ -import React from 'react'; -import { render } from '@testing-library/react'; -import { ThemeProvider } from '@material-ui/core'; import { lightTheme } from '@backstage/theme'; +import { ThemeProvider } from '@material-ui/core'; +import { render } from '@testing-library/react'; +import React from 'react'; import GetBBoxPolyfill from '../../utils/polyfills/getBBox'; -import RadarLegend, { Props } from './RadarLegend'; +import RadarLegend from './RadarLegend'; +import { RadarLegendProps } from './types'; -const minProps: Props = { +const minProps: RadarLegendProps = { quadrants: [{ id: 'languages', name: 'Languages' }], rings: [{ id: 'use', name: 'USE', color: '#93c47d' }], entries: [ diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx index a398113b98..6177041c70 100644 --- a/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegend.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2022 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,21 +15,9 @@ */ import { makeStyles, Theme } from '@material-ui/core'; import React from 'react'; -import { WithLink } from '../../utils/components'; -import type { Entry, Quadrant, Ring } from '../../utils/types'; -import { RadarDescription } from '../RadarDescription'; - -type Segments = { - [k: number]: { [k: number]: Entry[] }; -}; - -export type Props = { - quadrants: Quadrant[]; - rings: Ring[]; - entries: Entry[]; - onEntryMouseEnter?: (entry: Entry) => void; - onEntryMouseLeave?: (entry: Entry) => void; -}; +import { RadarLegendQuadrant } from './RadarLegendQuadrant'; +import { RadarLegendProps } from './types'; +import { setupSegments } from './utils'; const useStyles = makeStyles(theme => ({ quadrant: { @@ -85,209 +73,25 @@ const useStyles = makeStyles(theme => ({ }, })); -const RadarLegend = (props: Props): JSX.Element => { +const RadarLegend = ({ + quadrants, + rings, + entries, + onEntryMouseEnter, + onEntryMouseLeave, + ...props +}: RadarLegendProps): JSX.Element => { const classes = useStyles(props); - const getSegment = ( - segmented: Segments, - quadrant: Quadrant, - ring: Ring, - ringOffset = 0, - ) => { - const quadrantIndex = quadrant.index; - const ringIndex = ring.index; - const segmentedData = - quadrantIndex === undefined ? {} : segmented[quadrantIndex] || {}; - return ringIndex === undefined - ? [] - : segmentedData[ringIndex + ringOffset] || []; - }; - - type RadarLegendRingProps = { - ring: Ring; - entries: Entry[]; - onEntryMouseEnter?: Props['onEntryMouseEnter']; - onEntryMouseLeave?: Props['onEntryMouseEnter']; - }; - - type RadarLegendLinkProps = { - url?: string; - description?: string; - title?: string; - }; - - const RadarLegendLink = ({ - url, - description, - title, - }: RadarLegendLinkProps) => { - const [open, setOpen] = React.useState(false); - - const handleClickOpen = () => { - setOpen(true); - }; - - const handleClose = () => { - setOpen(false); - }; - - const toggle = () => { - setOpen(!open); - }; - - if (description) { - return ( - <> - - {title} - - {open && ( - - )} - - ); - } - return ( - - {title} - - ); - }; - - const RadarLegendRing = ({ - ring, - entries, - onEntryMouseEnter, - onEntryMouseLeave, - }: RadarLegendRingProps) => { - return ( -
-

{ring.name}

- {entries.length === 0 ? ( -

(empty)

- ) : ( -
    - {entries.map(entry => ( -
  1. onEntryMouseEnter(entry)) - } - onMouseLeave={ - onEntryMouseLeave && (() => onEntryMouseLeave(entry)) - } - > - -
  2. - ))} -
- )} -
- ); - }; - - type RadarLegendQuadrantProps = { - segments: Segments; - quadrant: Quadrant; - rings: Ring[]; - onEntryMouseEnter: Props['onEntryMouseEnter']; - onEntryMouseLeave: Props['onEntryMouseLeave']; - }; - - const RadarLegendQuadrant = ({ - segments, - quadrant, - rings, - onEntryMouseEnter, - onEntryMouseLeave, - }: RadarLegendQuadrantProps) => { - return ( - -
-

{quadrant.name}

-
- {rings.map(ring => ( - - ))} -
-
-
- ); - }; - - const setupSegments = (entries: Entry[]) => { - const segments: Segments = {}; - - for (const entry of entries) { - const quadrantIndex = entry.quadrant.index; - const ringIndex = entry.ring.index; - let quadrantData: { [k: number]: Entry[] } = {}; - if (quadrantIndex !== undefined) { - if (segments[quadrantIndex] === undefined) { - segments[quadrantIndex] = {}; - } - - quadrantData = segments[quadrantIndex]; - } - - let ringData = []; - if (ringIndex !== undefined) { - if (quadrantData[ringIndex] === undefined) { - quadrantData[ringIndex] = []; - } - - ringData = quadrantData[ringIndex]; - } - - ringData.push(entry); - } - - return segments; - }; - - const { quadrants, rings, entries, onEntryMouseEnter, onEntryMouseLeave } = - props; - - const segments: Segments = setupSegments(entries); - return ( {quadrants.map(quadrant => ( diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx new file mode 100644 index 0000000000..3ed1b7574a --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendLink.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { ClassNameMap } from '@material-ui/core/styles/withStyles'; +import React from 'react'; +import { WithLink } from '../../utils/components'; +import { RadarDescription } from '../RadarDescription'; + +type RadarLegendLinkProps = { + url?: string; + description?: string; + title?: string; + classes: ClassNameMap; +}; + +export const RadarLegendLink = ({ + url, + description, + title, + classes, +}: RadarLegendLinkProps) => { + const [open, setOpen] = React.useState(false); + + const handleClickOpen = () => { + setOpen(true); + }; + + const handleClose = () => { + setOpen(false); + }; + + const toggle = () => { + setOpen(!open); + }; + + if (description) { + return ( + <> + + {title} + + {open && ( + + )} + + ); + } + return ( + + {title} + + ); +}; diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx new file mode 100644 index 0000000000..60a5870348 --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendQuadrant.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { ClassNameMap } from '@material-ui/core/styles/withStyles'; +import React from 'react'; +import { Quadrant, Ring } from '../../utils/types'; +import { RadarLegendRing } from './RadarLegendRing'; +import { RadarLegendProps, Segments } from './types'; +import { getSegment } from './utils'; + +type RadarLegendQuadrantProps = { + segments: Segments; + quadrant: Quadrant; + rings: Ring[]; + classes: ClassNameMap; + onEntryMouseEnter: RadarLegendProps['onEntryMouseEnter']; + onEntryMouseLeave: RadarLegendProps['onEntryMouseLeave']; +}; + +export const RadarLegendQuadrant = ({ + segments, + quadrant, + rings, + classes, + onEntryMouseEnter, + onEntryMouseLeave, +}: RadarLegendQuadrantProps) => { + return ( + +
+

{quadrant.name}

+
+ {rings.map(ring => ( + + ))} +
+
+
+ ); +}; diff --git a/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx b/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx new file mode 100644 index 0000000000..f54e4ab3b3 --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/RadarLegendRing.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2022 The Backstage Authors + * + * 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 { ClassNameMap } from '@material-ui/core/styles/withStyles'; +import React from 'react'; +import { Entry, Ring } from '../../utils/types'; +import { RadarLegendLink } from './RadarLegendLink'; +import { RadarLegendProps } from './types'; + +type RadarLegendRingProps = { + ring: Ring; + entries: Entry[]; + classes: ClassNameMap; + onEntryMouseEnter?: RadarLegendProps['onEntryMouseEnter']; + onEntryMouseLeave?: RadarLegendProps['onEntryMouseEnter']; +}; + +export const RadarLegendRing = ({ + ring, + entries, + classes, + onEntryMouseEnter, + onEntryMouseLeave, +}: RadarLegendRingProps) => { + return ( +
+

{ring.name}

+ {entries.length === 0 ? ( +

(empty)

+ ) : ( +
    + {entries.map(entry => ( +
  1. onEntryMouseEnter(entry)) + } + onMouseLeave={ + onEntryMouseLeave && (() => onEntryMouseLeave(entry)) + } + > + +
  2. + ))} +
+ )} +
+ ); +}; diff --git a/plugins/tech-radar/src/components/RadarLegend/types.ts b/plugins/tech-radar/src/components/RadarLegend/types.ts new file mode 100644 index 0000000000..cdac1bfe9d --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/types.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 { Entry, Quadrant, Ring } from '../../utils/types'; + +export type Segments = { + [k: number]: { [k: number]: Entry[] }; +}; + +export type RadarLegendProps = { + quadrants: Quadrant[]; + rings: Ring[]; + entries: Entry[]; + onEntryMouseEnter?: (entry: Entry) => void; + onEntryMouseLeave?: (entry: Entry) => void; +}; diff --git a/plugins/tech-radar/src/components/RadarLegend/utils.ts b/plugins/tech-radar/src/components/RadarLegend/utils.ts new file mode 100644 index 0000000000..0c2d7eb8f0 --- /dev/null +++ b/plugins/tech-radar/src/components/RadarLegend/utils.ts @@ -0,0 +1,63 @@ +/* + * Copyright 2020 The Backstage Authors + * + * 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 { Entry, Quadrant, Ring } from '../../utils/types'; +import { Segments } from './types'; + +export const setupSegments = (entries: Entry[]) => { + const segments: Segments = {}; + + for (const entry of entries) { + const quadrantIndex = entry.quadrant.index; + const ringIndex = entry.ring.index; + let quadrantData: { [k: number]: Entry[] } = {}; + if (quadrantIndex !== undefined) { + if (segments[quadrantIndex] === undefined) { + segments[quadrantIndex] = {}; + } + + quadrantData = segments[quadrantIndex]; + } + + let ringData = []; + if (ringIndex !== undefined) { + if (quadrantData[ringIndex] === undefined) { + quadrantData[ringIndex] = []; + } + + ringData = quadrantData[ringIndex]; + } + + ringData.push(entry); + } + + return segments; +}; + +export const getSegment = ( + segmented: Segments, + quadrant: Quadrant, + ring: Ring, + ringOffset = 0, +) => { + const quadrantIndex = quadrant.index; + const ringIndex = ring.index; + const segmentedData = + quadrantIndex === undefined ? {} : segmented[quadrantIndex] || {}; + return ringIndex === undefined + ? [] + : segmentedData[ringIndex + ringOffset] || []; +}; diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 5f9f033435..baea1ab42f 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/plugin-techdocs@1.4.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/plugin-catalog@1.6.1-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/test-utils@1.2.2-next.0 + - @backstage/theme@0.2.16 + +## 1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/test-utils@1.2.1 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + ## 1.0.5-next.1 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index bdd971912f..a3da234606 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.5-next.1", + "version": "1.0.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index d2000fba1d..b038fa202f 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-techdocs-backend +## 1.4.1-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- a6d779d58a: Remove explicit default visibility at `config.d.ts` files. + + ```ts + /** + * @visibility backend + */ + ``` + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/plugin-techdocs-node@1.4.2-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-permission-common@0.7.1-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-catalog-common@1.0.8-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.4.0 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-techdocs-node@1.4.1 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 1.4.0-next.2 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 1.3.1-next.1 ### Patch Changes diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index cb9d4ceabf..ec35505a78 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -101,13 +101,11 @@ export interface Config { secretAccessKey?: string; /** * ARN of role to be assumed - * @visibility backend */ roleArn?: string; }; /** * (Required) Cloud Storage Bucket Name - * @visibility backend */ bucketName: string; /** @@ -128,7 +126,6 @@ export interface Config { * (Optional) Whether to use path style URLs when communicating with S3. * Defaults to false. * This allows providers like LocalStack, Minio and Wasabi (and possibly others) to be used to host tech docs. - * @visibility backend */ s3ForcePathStyle?: boolean; @@ -167,17 +164,14 @@ export interface Config { }; /** * (Required) Cloud Storage Container Name - * @visibility backend */ containerName: string; /** * (Required) Auth url sometimes OpenStack uses different port check your OpenStack apis. - * @visibility backend */ authUrl: string; /** * (Required) Swift URL - * @visibility backend */ swiftUrl: string; }; @@ -209,7 +203,6 @@ export interface Config { }; /** * (Required) Cloud Storage Container Name - * @visibility backend */ containerName: string; }; @@ -223,7 +216,6 @@ export interface Config { googleGcs?: { /** * (Required) Cloud Storage Bucket Name - * @visibility backend */ bucketName: string; /** @@ -237,7 +229,6 @@ export interface Config { * (Optional) GCP project ID that contains the bucket. Should be * set if credentials is not set, or if the service account in * the credentials belongs to a different project to the bucket. - * @visibility backend */ projectId?: string; }; diff --git a/plugins/techdocs-backend/examples/documented-component/docs/extensions.md b/plugins/techdocs-backend/examples/documented-component/docs/extensions.md index 0fda26302d..eb9abca74a 100644 --- a/plugins/techdocs-backend/examples/documented-component/docs/extensions.md +++ b/plugins/techdocs-backend/examples/documented-component/docs/extensions.md @@ -96,17 +96,17 @@ Animals: :tiger: :horse: :turtle: :wolf: :frog: ### MDX truly sane lists -- attributes +- `attributes` -- customer - - first_name - - test - - family_name - - email -- person - - first_name - - family_name - - birth_date -- subscription_id +- `customer` + - `first_name` + - `test` + - `family_name` + - `email` +- `person` + - `first_name` + - `family_name` + - `birth_date` +- `subscription_id` -- request +- `request` diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 2b85508c30..5e74c47d3c 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-backend", "description": "The Backstage backend plugin that renders technical documentation for your components", - "version": "1.3.1-next.1", + "version": "1.4.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts b/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts index 0a21eddc15..4dd993a163 100644 --- a/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts +++ b/plugins/techdocs-backend/src/cache/cacheMiddleware.test.ts @@ -17,7 +17,8 @@ import { getVoidLogger } from '@backstage/backend-common'; import express from 'express'; import request from 'supertest'; -import { createCacheMiddleware, TechDocsCache } from '.'; +import { createCacheMiddleware } from './cacheMiddleware'; +import { TechDocsCache } from './TechDocsCache'; /** * Mocks cached HTTP response. diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts index d458eeaba0..aaddb4fa51 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts @@ -90,7 +90,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { this.discovery = options.discovery; this.locationTemplate = options.locationTemplate || '/docs/:namespace/:kind/:name/:path'; - this.logger = options.logger; + this.logger = options.logger.child({ documentType: this.type }); this.catalogClient = options.catalogClient || new CatalogClient({ discoveryApi: options.discovery }); diff --git a/plugins/techdocs-backend/src/service/standaloneServer.ts b/plugins/techdocs-backend/src/service/standaloneServer.ts index f0ee42ba4a..5751a460bf 100644 --- a/plugins/techdocs-backend/src/service/standaloneServer.ts +++ b/plugins/techdocs-backend/src/service/standaloneServer.ts @@ -54,6 +54,7 @@ export async function startStandaloneServer( const discovery = SingleHostDiscovery.fromConfig(config); const mockUrlReader: jest.Mocked = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), }; diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index d220bc3815..c56e9cbf12 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.0.6-next.0 + +### Patch Changes + +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/theme@0.2.16 + +## 1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + ## 1.0.5-next.1 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 1e1fd0eede..782273e8c0 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", "description": "Plugin module for contributed TechDocs Addons", - "version": "1.0.5-next.1", + "version": "1.0.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts b/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts index 19cb47ec40..22dc359942 100644 --- a/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts +++ b/plugins/techdocs-module-addons-contrib/src/ReportIssue/hooks.ts @@ -18,7 +18,7 @@ import parseGitUrl from 'git-url-parse'; import { useApi } from '@backstage/core-plugin-api'; import { - replaceGitHubUrlType, + replaceGithubUrlType, replaceGitLabUrlType, } from '@backstage/integration'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; @@ -31,7 +31,7 @@ import { PAGE_EDIT_LINK_SELECTOR } from './constants'; const resolveBlobUrl = (url: string, type: string) => { if (type === 'github') { - return replaceGitHubUrlType(url, 'blob'); + return replaceGithubUrlType(url, 'blob'); } else if (type === 'gitlab') { return replaceGitLabUrlType(url, 'blob'); } diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 1503a665d6..cc8770c34b 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/plugin-techdocs-node +## 1.4.2-next.0 + +### Patch Changes + +- a7607b5413: Replace usage of deprecataed `UrlReader.read` with `UrlReader.readUrl`. +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.4.1 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 1.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 1.4.1-next.1 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index ab7267e10b..eef2da8ccb 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-node", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "1.4.1-next.1", + "version": "1.4.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/techdocs-node/src/helpers.test.ts b/plugins/techdocs-node/src/helpers.test.ts index 69f09d92ed..1b25abded3 100644 --- a/plugins/techdocs-node/src/helpers.test.ts +++ b/plugins/techdocs-node/src/helpers.test.ts @@ -16,6 +16,8 @@ import { ReadTreeResponse, + ReadUrlOptions, + ReadUrlResponse, SearchResponse, UrlReader, } from '@backstage/backend-common'; @@ -292,6 +294,13 @@ describe('getDocFilesFromRepository', () => { return Buffer.from('mock'); } + async readUrl( + _url: string, + _options?: ReadUrlOptions | undefined, + ): Promise { + throw new Error('Method not implemented.'); + } + async readTree(): Promise { return { dir: async () => { diff --git a/plugins/techdocs-node/src/stages/prepare/dir.test.ts b/plugins/techdocs-node/src/stages/prepare/dir.test.ts index 829c414ddb..ddefb40f03 100644 --- a/plugins/techdocs-node/src/stages/prepare/dir.test.ts +++ b/plugins/techdocs-node/src/stages/prepare/dir.test.ts @@ -46,6 +46,7 @@ const createMockEntity = (annotations: {}) => { const mockConfig = new ConfigReader({}); const mockUrlReader: jest.Mocked = { read: jest.fn(), + readUrl: jest.fn(), readTree: jest.fn(), search: jest.fn(), }; diff --git a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts index 896e528644..4418e00c81 100644 --- a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts +++ b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts @@ -219,13 +219,13 @@ describe('GoogleGCSPublish', () => { }, }; - beforeAll(() => { + beforeEach(() => { mockFs({ [directory]: files, }); }); - afterAll(() => { + afterEach(() => { mockFs.restore(); }); diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 869edb0a48..9418517a6c 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-techdocs-react +## 1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/version-bridge@1.0.1 + +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/version-bridge@1.0.1 + ## 1.0.5-next.1 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 38a2489782..d7ff40bed8 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-react", "description": "Shared frontend utilities for TechDocs and Addons", - "version": "1.0.5-next.1", + "version": "1.0.6-next.0", "publishConfig": { "access": "public", "alphaTypes": "dist/index.alpha.d.ts", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 0d8fdd369d..9a6c681a82 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,87 @@ # @backstage/plugin-techdocs +## 1.4.0-next.0 + +### Minor Changes + +- 5691baea69: Add ability to configure filters when using EntityListDocsGrid + + The following example will render two sections of cards grid: + + - One section for documentations tagged as `recommended` + - One section for documentations tagged as `runbook` + + ```js + + entity?.metadata?.tags?.includes('recommended') ?? false, + }, + { + title: "RunBooks Documentation", + filterPredicate: entity => + entity?.metadata?.tags?.includes('runbook') ?? false, + } + ]}} /> + ``` + +### Patch Changes + +- cbe11d1e23: Tweak README +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- c1784a4980: Replaces in-code uses of `GitHub` with `Github` and deprecates old versions. +- 3a1a999b7b: Include query parameters when navigating to relative links in documents +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/integration-react@1.1.6-next.0 + - @backstage/plugin-search-react@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.6-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.1.1-next.0 + +## 1.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 1.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + ## 1.3.3-next.1 ### Patch Changes diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index b2f0331a9b..d1c05f5b8e 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -59,6 +59,12 @@ export type DocsCardGridProps = { entities: Entity[] | undefined; }; +// @public +export type DocsGroupConfig = { + title: React_2.ReactNode; + filterPredicate: ((entity: Entity) => boolean) | string; +}; + // @public export const DocsTable: { (props: DocsTableProps): JSX.Element | null; @@ -112,7 +118,14 @@ export const EmbeddedDocsRouter: ( ) => JSX.Element | null; // @public -export const EntityListDocsGrid: () => JSX.Element; +export const EntityListDocsGrid: ({ + groups, +}: EntityListDocsGridPageProps) => JSX.Element; + +// @public +export type EntityListDocsGridPageProps = { + groups?: DocsGroupConfig[]; +}; // @public export const EntityListDocsTable: { diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index c915ac515f..05b841fd0c 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs", "description": "The Backstage plugin that renders technical documentation for your components", - "version": "1.3.3-next.1", + "version": "1.4.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx new file mode 100644 index 0000000000..effd8fe0c5 --- /dev/null +++ b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.test.tsx @@ -0,0 +1,203 @@ +/* + * Copyright 2021 The Backstage Authors + * + * 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 { ApiProvider, ConfigReader } from '@backstage/core-app-api'; +import { + ConfigApi, + configApiRef, + storageApiRef, +} from '@backstage/core-plugin-api'; +import { + CatalogApi, + catalogApiRef, + starredEntitiesApiRef, + MockEntityListContextProvider, + MockStarredEntitiesApi, +} from '@backstage/plugin-catalog-react'; +import { + MockStorageApi, + renderInTestApp, + TestApiRegistry, +} from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import React from 'react'; +import { rootDocsRouteRef } from '../../../routes'; +import { EntityListDocsGrid } from './EntityListDocsGrid'; + +const entities = [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Documentation #1', + namespace: 'default', + }, + spec: { + type: 'documentation', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'Documentation #2', + namespace: 'default', + }, + spec: { + type: 'documentation', + }, + }, +]; + +const mockCatalogApi = { + getEntityByRef: () => Promise.resolve(), + getEntities: async () => ({ + items: entities, + }), +} as Partial; + +describe('Entity List Docs Grid', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + const configApi: ConfigApi = new ConfigReader({ + organization: { + name: 'My Company', + }, + }); + + const storageApi = MockStorageApi.create(); + + const apiRegistry = TestApiRegistry.from( + [catalogApiRef, mockCatalogApi], + [configApiRef, configApi], + [storageApiRef, storageApi], + [starredEntitiesApiRef, new MockStarredEntitiesApi()], + ); + + it('should render all entitites without filtering', async () => { + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect(await screen.queryByText('All Documentation')).toBeInTheDocument(); + expect(await screen.queryByText('Documentation #1')).toBeInTheDocument(); + expect(await screen.queryByText('Documentation #2')).toBeInTheDocument(); + expect(await screen.queryByTestId('doc-not-found')).not.toBeInTheDocument(); + }); + + it('should render only filtered entities with filtering', async () => { + await renderInTestApp( + + + + entity.metadata.name === 'Documentation #1', + }, + ]} + /> + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + await screen.queryByText('Curated Documentation'), + ).toBeInTheDocument(); + expect(await screen.queryByText('Documentation #1')).toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #2'), + ).not.toBeInTheDocument(); + expect(await screen.queryByTestId('doc-not-found')).not.toBeInTheDocument(); + }); + + it('should render nothing with filtering yielding no result', async () => { + await renderInTestApp( + + + + entity.metadata.name === 'Documentation #3', + }, + ]} + /> + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + await screen.queryByText('Curated Documentation'), + ).not.toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #1'), + ).not.toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #2'), + ).not.toBeInTheDocument(); + expect(await screen.queryByTestId('doc-not-found')).not.toBeInTheDocument(); + }); + + it('should render an error without any documentation and without filtering', async () => { + await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/docs/:namespace/:kind/:name/*': rootDocsRouteRef, + }, + }, + ); + + expect( + await screen.queryByText('All Documentation'), + ).not.toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #1'), + ).not.toBeInTheDocument(); + expect( + await screen.queryByText('Documentation #2'), + ).not.toBeInTheDocument(); + expect(await screen.queryByTestId('doc-not-found')).toBeInTheDocument(); + }); +}); diff --git a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx index 348104f5cc..d6b2bf3391 100644 --- a/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx @@ -15,20 +15,95 @@ */ import { DocsCardGrid } from './DocsCardGrid'; +import { Entity } from '@backstage/catalog-model'; import { CodeSnippet, + Content, + ContentHeader, + Link, Progress, WarningPanel, } from '@backstage/core-components'; -import { useEntityList } from '@backstage/plugin-catalog-react'; +import { + useEntityList, + useEntityOwnership, +} from '@backstage/plugin-catalog-react'; +import { Typography } from '@material-ui/core'; import React from 'react'; +/** + * Props for {@link EntityListDocsGrid} + * + * @public + */ +export type DocsGroupConfig = { + title: React.ReactNode; + filterPredicate: ((entity: Entity) => boolean) | string; +}; + +/** + * Props for {@link EntityListDocsGrid} + * + * @public + */ +export type EntityListDocsGridPageProps = { + groups?: DocsGroupConfig[]; +}; + +const allEntitiesGroup: DocsGroupConfig = { + title: 'All Documentation', + filterPredicate: () => true, +}; + +const EntityListDocsGridGroup = ({ + entities, + group, +}: { + group: DocsGroupConfig; + entities: Entity[]; +}) => { + const { loading: loadingOwnership, isOwnedEntity } = useEntityOwnership(); + + const shownEntities = entities.filter(entity => { + if (group.filterPredicate === 'ownedByUser') { + if (loadingOwnership) { + return false; + } + return isOwnedEntity(entity); + } + + return ( + typeof group.filterPredicate === 'function' && + group.filterPredicate(entity) + ); + }); + + const titleComponent: React.ReactNode = (() => { + return typeof group.title === 'string' ? ( + + ) : ( + group.title + ); + })(); + + if (shownEntities.length === 0) { + return null; + } + + return ( + + {titleComponent} + + + ); +}; + /** * Component responsible to get entities from entity list context and pass down to DocsCardGrid * * @public */ -export const EntityListDocsGrid = () => { +export const EntityListDocsGrid = ({ groups }: EntityListDocsGridPageProps) => { const { loading, error, entities } = useEntityList(); if (error) { @@ -42,15 +117,39 @@ export const EntityListDocsGrid = () => { ); } - if (loading || !entities) { + if (loading) { return ; } + if (entities.length === 0) { + return ( +
+ + No documentation found that match your filter. Learn more about{' '} + + publishing documentation + + . + +
+ ); + } + entities.sort((a, b) => (a.metadata.title ?? a.metadata.name).localeCompare( b.metadata.title ?? b.metadata.name, ), ); - return ; + return ( + + {(groups || [allEntitiesGroup]).map((group, index: number) => ( + + ))} + + ); }; diff --git a/plugins/techdocs/src/reader/README.md b/plugins/techdocs/src/reader/README.md index 63464dfa81..fe1d1ff488 100644 --- a/plugins/techdocs/src/reader/README.md +++ b/plugins/techdocs/src/reader/README.md @@ -17,4 +17,4 @@ export const updateH1Text = (): Transformer => { }; ``` -The transformers are then registered in the Reader.tsx file. They are registered in two places, one place that runs before it's attached to the actual browser DOM (preTransformers) and once after (postTransfomers). Doing modifications is faster before it's attached, but doesn't allow us to do some things, such as attaching event listeners. +The transformers are then registered in the Reader.tsx file. They are registered in two places, one place that runs before it's attached to the actual browser DOM (`preTransformer`s) and once after (`postTransformer`s). Doing modifications is faster before it's attached, but doesn't allow us to do some things, such as attaching event listeners. diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx index 1c276b87a1..0e9b244d74 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage/TechDocsReaderPage.tsx @@ -35,6 +35,82 @@ import { useRouteRefParams, } from '@backstage/core-plugin-api'; +/* An explanation for the multiple ways of customizing the TechDocs reader page + +Please refer to this page on the microsite for the latest recommended approach: +https://backstage.io/docs/features/techdocs/how-to-guides#how-to-customize-the-techdocs-reader-page + +The component is responsible for rendering the and +its contained version of a , which in turn renders the . + +Historically, there have been different approaches on how this can be customized, and how the + inside could be exchanged for a custom implementation (which was not +possible before). Also, the current implementation supports every scenario to avoid breaking default +configurations of TechDocs. + +In particular, there are 4 different TechDocs page configurations: + +CONFIGURATION 1: only, no children + +} > + +This is the simplest way to use TechDocs. Only a full page is passed, assuming that it comes with +its content inside. Since we allowed customizing it, we started providing as +a default implementation (which contains ). + +CONFIGURATION 2 (not advised): with element children + + + {techdocsPage} + + } +/> + +Previously, there were two ways of passing children to : either as elements (as +shown above), or as a render function (described below in CONFIGURATION 3). The "techdocsPage" is +located in packages/app/src/components/techdocs and is the default implementation of the content +inside. + +CONFIGURATION 3 (not advised): with render function as child + + + {({ metadata, entityMetadata, onReady }) => ( + techdocsPage + )} + + } +/> + +Similar to CONFIGURATION 2, the direct children will be passed to the but in +this case interpreted as render prop. + +CONFIGURATION 4: and provided content in + +} +> + {techDocsPage} + + + + + + + +This is the current state in packages/app/src/App.tsx and moved the location of children from inside +the element prop in the to the children of the . Then, in they +are retrieved using the useOutlet hook from React Router. + +NOTE: Render functions are no longer supported in this approach. +*/ + /** * Props for {@link TechDocsReaderLayout} * @public @@ -77,6 +153,7 @@ export type TechDocsReaderPageProps = { /** * An addon-aware implementation of the TechDocsReaderPage. + * * @public */ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { @@ -88,21 +165,14 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { if (!children) { const childrenList = outlet ? Children.toArray(outlet.props.children) : []; - const page = childrenList.find(child => { - if (getComponentData(child, TECHDOCS_ADDONS_WRAPPER_KEY)) { - return false; - } - - // react-router 6 stable wraps children in a routing context provider, so check one level deeper - const nestedChildren = (child as ReactElement)?.props?.children; - if (nestedChildren) { - return !Children.toArray(nestedChildren).some(nested => - getComponentData(nested, TECHDOCS_ADDONS_WRAPPER_KEY), - ); - } - return true; - }); + const grandChildren = childrenList.flatMap( + child => (child as ReactElement)?.props?.children ?? [], + ); + const page: React.ReactNode = grandChildren.find( + grandChild => !getComponentData(grandChild, TECHDOCS_ADDONS_WRAPPER_KEY), + ); + // As explained above, "page" is configuration 4 and is 1 return ( {(page as JSX.Element) || } @@ -110,6 +180,7 @@ export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { ); } + // As explained above, a render function is configuration 3 and React element is 2 return ( {({ metadata, entityMetadata, onReady }) => ( diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index 0e811af6e0..e886454594 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -175,13 +175,14 @@ export const useTechDocsReaderDom = ( // detect if CTRL or META keys are pressed so that links can be opened in a new tab with `window.open` const modifierActive = event.ctrlKey || event.metaKey; const parsedUrl = new URL(url); + const fullPath = `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`; // hash exists when anchor is clicked on secondary sidebar if (parsedUrl.hash) { if (modifierActive) { - window.open(`${parsedUrl.pathname}${parsedUrl.hash}`, '_blank'); + window.open(fullPath, '_blank'); } else { - navigate(`${parsedUrl.pathname}${parsedUrl.hash}`); + navigate(fullPath); // Scroll to hash if it's on the current page transformedElement ?.querySelector(`[id="${parsedUrl.hash.slice(1)}"]`) @@ -189,9 +190,9 @@ export const useTechDocsReaderDom = ( } } else { if (modifierActive) { - window.open(parsedUrl.pathname, '_blank'); + window.open(fullPath, '_blank'); } else { - navigate(parsedUrl.pathname); + navigate(fullPath); } } }, diff --git a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts index 9c7865936c..9239573bd1 100644 --- a/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts +++ b/plugins/techdocs/src/reader/transformers/addGitFeedbackLink.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import type { Transformer } from './index'; +import type { Transformer } from './transformer'; import { - replaceGitHubUrlType, + replaceGithubUrlType, ScmIntegrationRegistry, } from '@backstage/integration'; import FeedbackOutlinedIcon from '@material-ui/icons/FeedbackOutlined'; @@ -59,7 +59,7 @@ export const addGitFeedbackLink = ( // Convert GitHub edit url to blob type so it can be parsed by git-url-parse correctly const gitUrl = integration?.type === 'github' - ? replaceGitHubUrlType(sourceURL.href, 'blob') + ? replaceGithubUrlType(sourceURL.href, 'blob') : sourceURL.href; const gitInfo = parseGitUrl(gitUrl); const repoPath = `/${gitInfo.organization}/${gitInfo.name}`; diff --git a/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts b/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts index 69256d3ed6..789c9179d5 100644 --- a/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts +++ b/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import type { Transformer } from './index'; +import type { Transformer } from './transformer'; import MenuIcon from '@material-ui/icons/Menu'; import React from 'react'; import ReactDOM from 'react-dom'; diff --git a/plugins/techdocs/src/reader/transformers/scrollIntoNavigation.test.ts b/plugins/techdocs/src/reader/transformers/scrollIntoNavigation.test.ts index 965f2d4771..e57b256fb8 100644 --- a/plugins/techdocs/src/reader/transformers/scrollIntoNavigation.test.ts +++ b/plugins/techdocs/src/reader/transformers/scrollIntoNavigation.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { scrollIntoNavigation } from '.'; +import { scrollIntoNavigation } from './scrollIntoNavigation'; import { createTestShadowDom, FIXTURES } from '../../test-utils'; jest.useFakeTimers(); diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index 753ab2da44..f4827591df 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-todo-backend +## 0.1.35-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/integration@1.4.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/catalog-client@1.1.2-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 0.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + ## 0.1.34-next.1 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 94f9628c5b..ae227478b6 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo-backend", "description": "A Backstage backend plugin that lets you browse TODO comments in your source code", - "version": "0.1.34-next.1", + "version": "0.1.35-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.test.ts b/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.test.ts index 3e074f78a6..1f2b79a55f 100644 --- a/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.test.ts +++ b/plugins/todo-backend/src/lib/TodoReader/TodoScmReader.test.ts @@ -30,6 +30,7 @@ function mockReader(): jest.Mocked { read: jest.fn(), readTree: jest.fn(), search: jest.fn(), + readUrl: jest.fn(), } as jest.Mocked; } diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md index 26080264ec..c97576555d 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-todo +## 0.2.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.2.12-next.1 ### Patch Changes diff --git a/plugins/todo/package.json b/plugins/todo/package.json index d360992e04..c5cd8df216 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo", "description": "A Backstage plugin that lets you browse TODO comments in your source code", - "version": "0.2.12-next.1", + "version": "0.2.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/todo/src/components/TodoList/TodoList.test.tsx b/plugins/todo/src/components/TodoList/TodoList.test.tsx index 2759fe40f8..15d4334610 100644 --- a/plugins/todo/src/components/TodoList/TodoList.test.tsx +++ b/plugins/todo/src/components/TodoList/TodoList.test.tsx @@ -38,7 +38,10 @@ describe('TodoList', () => { offset: 0, }), }; - const mockEntity = { metadata: { name: 'mock' } } as Entity; + const mockEntity = { + metadata: { name: 'mock' }, + kind: 'MockKind', + } as Entity; const rendered = await renderWithEffects( diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 09c44c7878..54a75f3888 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-user-settings-backend +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/plugin-auth-node@0.2.7-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.1.1 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 82ac9bcfe5: Fix wrong import statement in `README.md`. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.1.1-next.2 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 377b8bfe4f..5015b60456 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings-backend", "description": "The Backstage backend plugin to manage user settings", - "version": "0.1.1-next.1", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 357f865b9d..d73d533cba 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-user-settings +## 0.5.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-app-api@1.2.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/types@1.0.1-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.5.0 + +### Minor Changes + +- 5543e86660: **BREAKING**: The `apiRef` passed to `ProviderSettingsItem` now needs to + implement `ProfileInfoApi & SessionApi`, rather than just the latter. This is + unlikely to have an effect on most users though, since the builtin auth + providers generally implement both. + + Fixed settings page showing providers as logged out when the user is using more + than one provider, and displayed some additional login information. + +### Patch Changes + +- 06d61d1266: Handle errors that may occur when the user logs out +- 44c9a95dcf: Prevent `.set()` to execute a request to the StorageClient if the user is `guest` +- 174f02a00a: Update installation instructions +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 0.5.0-next.1 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 439c05cd6f..744cf40eec 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings", "description": "A Backstage plugin that provides a settings page", - "version": "0.5.0-next.1", + "version": "0.5.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/vault-backend/CHANGELOG.md b/plugins/vault-backend/CHANGELOG.md index ef09111b07..6f3270dba1 100644 --- a/plugins/vault-backend/CHANGELOG.md +++ b/plugins/vault-backend/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-vault-backend +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.16.0-next.0 + - @backstage/backend-tasks@0.3.7-next.0 + - @backstage/backend-test-utils@0.1.30-next.0 + - @backstage/config@1.0.4-next.0 + - @backstage/errors@1.1.3-next.0 + +## 0.2.3 + +### Patch Changes + +- 9c595302cb: Normalize on winston version ^3.2.1 +- dae0bbe522: VaultBuilder.tsx renamed to VaultBuilder in order for module to be correctly loaded. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + ## 0.2.3-next.1 ### Patch Changes diff --git a/plugins/vault-backend/package.json b/plugins/vault-backend/package.json index ff4d173cef..2cc170337a 100644 --- a/plugins/vault-backend/package.json +++ b/plugins/vault-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-vault-backend", "description": "A Backstage backend plugin that integrates towards Vault", - "version": "0.2.3-next.1", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -46,7 +46,7 @@ "helmet": "^6.0.0", "node-fetch": "^2.6.7", "p-limit": "^3.1.0", - "winston": "^3.7.2", + "winston": "^3.2.1", "yn": "^5.0.0" }, "devDependencies": { diff --git a/plugins/vault/CHANGELOG.md b/plugins/vault/CHANGELOG.md index 2bdb197722..ce0f6c41cf 100644 --- a/plugins/vault/CHANGELOG.md +++ b/plugins/vault/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-vault +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.1-next.0 + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/catalog-model@1.1.3-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.1.4-next.1 ### Patch Changes diff --git a/plugins/vault/package.json b/plugins/vault/package.json index 17ee68761d..dc5e7b0c13 100644 --- a/plugins/vault/package.json +++ b/plugins/vault/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-vault", "description": "A Backstage plugin that integrates towards Vault", - "version": "0.1.4-next.1", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/xcmetrics/CHANGELOG.md b/plugins/xcmetrics/CHANGELOG.md index f231d3d77e..8c68404127 100644 --- a/plugins/xcmetrics/CHANGELOG.md +++ b/plugins/xcmetrics/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-xcmetrics +## 0.2.31-next.0 + +### Patch Changes + +- 7573b65232: Internal refactor of imports to avoid circular dependencies +- 43afded227: Updated recharts to v2.0.0 and fixed typing issues +- dcf9e728de: Removed an unused and hidden build details route. +- Updated dependencies + - @backstage/core-components@0.12.0-next.0 + - @backstage/core-plugin-api@1.1.0-next.0 + - @backstage/errors@1.1.3-next.0 + - @backstage/theme@0.2.16 + +## 0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + ## 0.2.30-next.1 ### Patch Changes diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index b1e2fe1475..6946095a01 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-xcmetrics", "description": "A Backstage plugin that shows XCode build metrics for your components", - "version": "0.2.30-next.1", + "version": "0.2.31-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "lodash": "^4.17.21", "luxon": "^3.0.0", "react-use": "^17.2.4", - "recharts": "^1.8.5" + "recharts": "^2.0.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0" diff --git a/plugins/xcmetrics/src/components/BuildTimeline/BuildTimeline.test.tsx b/plugins/xcmetrics/src/components/BuildTimeline/BuildTimeline.test.tsx index bb7e43a43c..4a5aff2f04 100644 --- a/plugins/xcmetrics/src/components/BuildTimeline/BuildTimeline.test.tsx +++ b/plugins/xcmetrics/src/components/BuildTimeline/BuildTimeline.test.tsx @@ -20,14 +20,45 @@ import { BuildTimeline } from './BuildTimeline'; jest.mock('../../api/XcmetricsClient'); const client = require('../../api/XcmetricsClient'); +jest.mock('recharts', () => { + const OriginalModule = jest.requireActual('recharts'); + + return { + ...OriginalModule, + ResponsiveContainer: ({ children }: any) => ( + + {children} + + ), + }; +}); + describe('BuildTimeline', () => { + const { ResizeObserver } = window; + + beforeEach(() => { + // @ts-expect-error + delete window.ResizeObserver; + window.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + })); + }); + + afterEach(() => { + window.ResizeObserver = ResizeObserver; + jest.restoreAllMocks(); + }); + it('should render', async () => { const rendered = await renderInTestApp( , ); - expect( - await rendered.findByText(client.mockTarget.name), - ).toBeInTheDocument(); + + const [element] = await rendered.findAllByText(client.mockTarget.name); + + expect(element).toBeInTheDocument(); }); it('should render a message if no targets are provided', async () => { diff --git a/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.test.tsx b/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.test.tsx deleted file mode 100644 index 75e31427ae..0000000000 --- a/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { renderInTestApp } from '@backstage/test-utils'; -import { BuildsPage } from './BuildsPage'; - -jest.mock('../BuildDetails', () => ({ - withRequest: (component: any) => component, - BuildDetails: () => 'BuildDetails', -})); - -jest.mock('../BuildList', () => ({ - BuildList: () => 'BuildList', -})); - -describe('BuildPage', () => { - it('should render BuildDetails if build id is provided in path', async () => { - const rendered = await renderInTestApp(, { - routeEntries: [`/buildId`], - }); - - expect(rendered.getByText('BuildDetails')).toBeInTheDocument(); - }); - - it('should render BuildList if no build id is provided in path', async () => { - const rendered = await renderInTestApp(); - - expect(rendered.getByText('BuildList')).toBeInTheDocument(); - }); -}); diff --git a/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.tsx b/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.tsx deleted file mode 100644 index 152337707d..0000000000 --- a/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2021 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import React from 'react'; -import { useRouteRefParams } from '@backstage/core-plugin-api'; -import { buildsRouteRef } from '../../routes'; -import { BuildList } from '../BuildList'; -import { BuildDetails, withRequest } from '../BuildDetails'; -import { InfoCard } from '@backstage/core-components'; - -export const BuildsPage = () => { - const { '*': buildId } = useRouteRefParams(buildsRouteRef) ?? { '*': '' }; - - if (buildId) { - const BuildDetailsWithRequest = withRequest(BuildDetails); - - return ( - - - - ); - } - - return ; -}; diff --git a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx index a21918e322..b2c6ca6ec8 100644 --- a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx +++ b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx @@ -23,7 +23,7 @@ import { } from '@backstage/core-components'; import { Overview } from '../Overview'; import { buildsRouteRef } from '../../routes'; -import { BuildsPage } from '../BuildsPage'; +import { BuildList } from '../BuildList'; export interface TabConfig { path: string; @@ -40,7 +40,7 @@ const TABS: TabConfig[] = [ { path: buildsRouteRef.path, title: 'Builds', - component: , + component: , }, ]; diff --git a/plugins/xcmetrics/src/utils/buildData.ts b/plugins/xcmetrics/src/utils/buildData.ts index df6fad996e..bbfcbcbf57 100644 --- a/plugins/xcmetrics/src/utils/buildData.ts +++ b/plugins/xcmetrics/src/utils/buildData.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { formatDuration } from '.'; +import { formatDuration } from './format'; import { BuildCount, BuildTime } from '../api'; export const getErrorRatios = (buildCounts?: BuildCount[]) => { diff --git a/storybook/package.json b/storybook/package.json index 4a994eedfe..d7cb2bda98 100644 --- a/storybook/package.json +++ b/storybook/package.json @@ -8,7 +8,7 @@ "build-storybook": "build-storybook --output-dir dist" }, "dependencies": { - "@swc/core": "^1.2.239", + "@swc/core": "^1.3.9", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hot-loader": "^4.13.0", diff --git a/storybook/yarn.lock b/storybook/yarn.lock index db03614312..be9a98e42a 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -1812,17 +1812,17 @@ __metadata: linkType: hard "@storybook/addon-a11y@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-a11y@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-a11y@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/theming": 6.5.12 + "@storybook/theming": 6.5.13 axe-core: ^4.2.0 core-js: ^3.8.2 global: ^4.4.0 @@ -1839,21 +1839,21 @@ __metadata: optional: true react-dom: optional: true - checksum: f93f3c4f4dd9f2f8cfc79200d6201a385d19a3d1bb71ed4b253347db2628f7f414d3479d5545302383a8ff7580bfa5f542ddc9c22ee95ff1abc005e152193580 + checksum: 6f1ba1f0d97d652a5346a33e051e1ff79aa07786532c6450c5c3dd677c2195e3ee8792cda756d4556e5fbaa2a72a0631843aec2880121b3439bad3e401c25359 languageName: node linkType: hard "@storybook/addon-actions@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-actions@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-actions@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/theming": 6.5.12 + "@storybook/theming": 6.5.13 core-js: ^3.8.2 fast-deep-equal: ^3.1.3 global: ^4.4.0 @@ -1874,23 +1874,23 @@ __metadata: optional: true react-dom: optional: true - checksum: 94f433a6b0956e4301e5b46c68eb56f6a9b01b5ec314099611d584542369d1aec4878a5353052f963e462b1b5f3ce74070f0d03eadf30e275b0b88ef7b713dd8 + checksum: 2679174b1467281860cd6f11fac5ba505e44629eb11ee90713d1c954569cfb54d80aa822a0ed1be2f92f11ca62889fec3dca96f61b89cf238503e0cea4b1db9a languageName: node linkType: hard "@storybook/addon-controls@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-controls@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-controls@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-common": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-common": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/node-logger": 6.5.12 - "@storybook/store": 6.5.12 - "@storybook/theming": 6.5.12 + "@storybook/node-logger": 6.5.13 + "@storybook/store": 6.5.13 + "@storybook/theming": 6.5.13 core-js: ^3.8.2 lodash: ^4.17.21 ts-dedent: ^2.0.0 @@ -1902,19 +1902,19 @@ __metadata: optional: true react-dom: optional: true - checksum: 27ee396ae4ab411b1bd99eacb0ebe747aa36300dc3b787d48eb685a446e64e33ff7df3b8713943132b6dbe3c78af3d2d6115b4da4c6196ec351d843690ab8a55 + checksum: a4f86332686b5681366ad1f1eebb50cb9939ce3424ade64c0043b94827df15a34da0190101c8dc9a2b4b9af67fafd9d3fb50a5b98ff7a24fed6a37a2f8f37f27 languageName: node linkType: hard "@storybook/addon-links@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-links@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-links@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/router": 6.5.12 + "@storybook/router": 6.5.13 "@types/qs": ^6.9.5 core-js: ^3.8.2 global: ^4.4.0 @@ -1930,21 +1930,21 @@ __metadata: optional: true react-dom: optional: true - checksum: 9e1394bc2fed9019537f3fee9f60abc4ea866d1688df72a435052dd07f25a8bac848d6acc2beef92af0cebc3715e9db9ae1bd95c87311a8b9fad7dee988bbd1d + checksum: 0bbe14652320c77dbcbcd0a6f45e4776de35475cca43b9825c36424e6ccc250fac4a172cdf01304debdc3c93d74ad2ea3bafb8a752e9a52a5e860f5bb9a397c2 languageName: node linkType: hard "@storybook/addon-storysource@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/addon-storysource@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/addon-storysource@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/router": 6.5.12 - "@storybook/source-loader": 6.5.12 - "@storybook/theming": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/router": 6.5.13 + "@storybook/source-loader": 6.5.13 + "@storybook/theming": 6.5.13 core-js: ^3.8.2 estraverse: ^5.2.0 loader-utils: ^2.0.0 @@ -1959,7 +1959,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 7ebaeffc1646229bebf7c9e918c225c73838b8972c00937e60928a5988440b3e6bf19225cf5f1d21c80aef59b2e20c6f473f6b5c386f5cebf7c9780d74b6ef18 + checksum: 35ad7b940df64b036c36edc1d203da8016203c2430ae98a380b33fef4ffbfe040651db61f54ef5a9e6edec298e58e660ddd933eb67a1563ce1e39faedbc56cba languageName: node linkType: hard @@ -1985,7 +1985,29 @@ __metadata: languageName: node linkType: hard -"@storybook/addons@npm:6.5.12, @storybook/addons@npm:^6.0.0, @storybook/addons@npm:^6.5.9": +"@storybook/addons@npm:6.5.13, @storybook/addons@npm:^6.5.9": + version: 6.5.13 + resolution: "@storybook/addons@npm:6.5.13" + dependencies: + "@storybook/api": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/router": 6.5.13 + "@storybook/theming": 6.5.13 + "@types/webpack-env": ^1.16.0 + core-js: ^3.8.2 + global: ^4.4.0 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 28589da00e8a26b44d4ed8a1938fe934187a85b187e2a0dcd3e6d114460ed5a07fbfe0a89a4d899739767b379015cbabadd47c5c266457922a8c4255e3d769a4 + languageName: node + linkType: hard + +"@storybook/addons@npm:^6.0.0": version: 6.5.12 resolution: "@storybook/addons@npm:6.5.12" dependencies: @@ -2063,27 +2085,55 @@ __metadata: languageName: node linkType: hard -"@storybook/builder-webpack4@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/builder-webpack4@npm:6.5.12" +"@storybook/api@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/api@npm:6.5.13" + dependencies: + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/router": 6.5.13 + "@storybook/semver": ^7.3.2 + "@storybook/theming": 6.5.13 + core-js: ^3.8.2 + fast-deep-equal: ^3.1.3 + global: ^4.4.0 + lodash: ^4.17.21 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + store2: ^2.12.0 + telejson: ^6.0.8 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: dd7c8db0cdea2a47ab835c02217f10f99c54bfbf6d826deadf0b160ece4c94b1cb2558cfbaff4e4244c5c776095028a164762bd8de19fcfe10ae318fe0a3fbb4 + languageName: node + linkType: hard + +"@storybook/builder-webpack4@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/builder-webpack4@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/core-events": 6.5.12 - "@storybook/node-logger": 6.5.12 - "@storybook/preview-web": 6.5.12 - "@storybook/router": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/node-logger": 6.5.13 + "@storybook/preview-web": 6.5.13 + "@storybook/router": 6.5.13 "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.12 - "@storybook/theming": 6.5.12 - "@storybook/ui": 6.5.12 + "@storybook/store": 6.5.13 + "@storybook/theming": 6.5.13 + "@storybook/ui": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 "@types/webpack": ^4.41.26 autoprefixer: ^9.8.6 @@ -2120,30 +2170,30 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 3cb72ade60fc0767480c424cd5da6659027c35759852198936530c555d7fa1ae18326b3d696def06a54a55f7f26a6eb0246e675ea566c5bbfc63db54e0ad8880 + checksum: a95fea3951479d7724155a2ddbf2b04a8bfc0e7fddbf8415caed508b94ad71f7dc8d5d25464061ef26f3b4670f49f6ed40198b48b3f646d7af17d8daee5e89cb languageName: node linkType: hard "@storybook/builder-webpack5@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/builder-webpack5@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/builder-webpack5@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/core-events": 6.5.12 - "@storybook/node-logger": 6.5.12 - "@storybook/preview-web": 6.5.12 - "@storybook/router": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/node-logger": 6.5.13 + "@storybook/preview-web": 6.5.13 + "@storybook/router": 6.5.13 "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.12 - "@storybook/theming": 6.5.12 + "@storybook/store": 6.5.13 + "@storybook/theming": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 babel-loader: ^8.0.0 babel-plugin-named-exports-order: ^0.0.2 @@ -2172,35 +2222,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 60387a186defc3b40ceae8c41376da5de65d52cc652a203bce8ef8733e54bec552143440ce5fb466916a87b0ef67b98932a96985f3ee371a3f74a9049277092f + checksum: f980ab5c832bac9584f80eb0229934bb58ceb4cf24f756043acea4cb397f52f7af49c1fd53ac897aefb5ffc9c808bcb8a0c4867afd2c2a21b7ba2f0a56ae0089 languageName: node linkType: hard -"@storybook/channel-postmessage@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/channel-postmessage@npm:6.5.12" +"@storybook/channel-postmessage@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/channel-postmessage@npm:6.5.13" dependencies: - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 core-js: ^3.8.2 global: ^4.4.0 qs: ^6.10.0 telejson: ^6.0.8 - checksum: c225f848f4774e8159b9fd8bd904520ab2755f46ac6ef5a8ed7193b5cd79856e0bb797d10adfa0a1db9b9df075c41b4487d195cc451fb65b04737db70e5db6db + checksum: 8d6ccfff2aeafaae30b5fc1af856be8d06b3703b96841ecc0d70959e51542514901763e1a291e1d0278afe31b23cc5c0a5b351994f321e9bd03490be5b51e2d0 languageName: node linkType: hard -"@storybook/channel-websocket@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/channel-websocket@npm:6.5.12" +"@storybook/channel-websocket@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/channel-websocket@npm:6.5.13" dependencies: - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 core-js: ^3.8.2 global: ^4.4.0 telejson: ^6.0.8 - checksum: 03d4ed3f2b67daceea06325c8705b95013b65d014dfda769a923b2c1c890d1eca1fe8eea09e3386cc572c738ce43bc8951225ebd92de4205ad80438bb9e36ebe + checksum: 16e3b1a51a1af093f6c78ab7ca9c4c69ed05b45fd9bbdefb3050809e92064ccbf7a46f6800d21df2555e5d110d64735dd8d35155e54c3118fa7b4efe6b3b0457 languageName: node linkType: hard @@ -2226,17 +2276,28 @@ __metadata: languageName: node linkType: hard -"@storybook/client-api@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/client-api@npm:6.5.12" +"@storybook/channels@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/channels@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + core-js: ^3.8.2 + ts-dedent: ^2.0.0 + util-deprecate: ^1.0.2 + checksum: 5b8881a2799a4c5ceafea40bc2c8bad1a31649036341eec8da5a77acf79a9d610afeaa5b4ed5d06022ed3c74cb9562dcfc5046d62fd8d27cd65bcba09aa5e903 + languageName: node + linkType: hard + +"@storybook/client-api@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/client-api@npm:6.5.13" + dependencies: + "@storybook/addons": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/store": 6.5.12 + "@storybook/store": 6.5.13 "@types/qs": ^6.9.5 "@types/webpack-env": ^1.16.0 core-js: ^3.8.2 @@ -2253,7 +2314,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 6a103cdf1c0499e238e6a652f192b3287b7bce2a96c194c48854d1e6d8e470568aa36307aa49650e8322965295b10d601c4a087e2cb7e3515bb9ba8281aeda35 + checksum: b0af25786b9144a55ebaa7754dd1b3701f5f8796770eaf59e7bc6d21ada12911fcbe4bf0da037d01bdda2c46138f265a948befe1f3de356fdc0ae3af80973388 languageName: node linkType: hard @@ -2277,7 +2338,36 @@ __metadata: languageName: node linkType: hard -"@storybook/components@npm:6.5.12, @storybook/components@npm:^6.0.0": +"@storybook/client-logger@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/client-logger@npm:6.5.13" + dependencies: + core-js: ^3.8.2 + global: ^4.4.0 + checksum: 0252d9364a0b2a8faae588fdb29aaf458f660904c330ec7af790f63a668710926ece8f087f58f9b1bebb052e2fe517b8b74867e7500567499cc710ab71ccbbab + languageName: node + linkType: hard + +"@storybook/components@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/components@npm:6.5.13" + dependencies: + "@storybook/client-logger": 6.5.13 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/theming": 6.5.13 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + util-deprecate: ^1.0.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 5d01c0f445f6574ccadcfa79afd99c078bd1f81d65e59186361100dc57bd73ccbb877e5a8bbc49dd6551bce1b32fbe6f135c2bea15c0126a83faf222cfaed878 + languageName: node + linkType: hard + +"@storybook/components@npm:^6.0.0": version: 6.5.12 resolution: "@storybook/components@npm:6.5.12" dependencies: @@ -2296,20 +2386,20 @@ __metadata: languageName: node linkType: hard -"@storybook/core-client@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/core-client@npm:6.5.12" +"@storybook/core-client@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core-client@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/channel-websocket": 6.5.12 - "@storybook/client-api": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/channel-websocket": 6.5.13 + "@storybook/client-api": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/preview-web": 6.5.12 - "@storybook/store": 6.5.12 - "@storybook/ui": 6.5.12 + "@storybook/preview-web": 6.5.13 + "@storybook/store": 6.5.13 + "@storybook/ui": 6.5.13 airbnb-js-shims: ^2.2.1 ansi-to-html: ^0.6.11 core-js: ^3.8.2 @@ -2327,13 +2417,13 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 4fb567964a6c15526ee6ee882e20d72c650ad0c74504ee2a058c13856efd25a0c9c7f666d36c6b4e70a75ece73c1ed812f554bbbf87771cd6171cd09ebf31410 + checksum: c4350b1b579f0781a239fdede79f1d0975e297ecb61ba4096834d62bd553420615231dc9146c446d0178088e83863fd9dc720fbb4485b5779fac7d99ce3eeb9e languageName: node linkType: hard -"@storybook/core-common@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/core-common@npm:6.5.12" +"@storybook/core-common@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core-common@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@babel/plugin-proposal-class-properties": ^7.12.1 @@ -2357,7 +2447,7 @@ __metadata: "@babel/preset-react": ^7.12.10 "@babel/preset-typescript": ^7.12.7 "@babel/register": ^7.12.1 - "@storybook/node-logger": 6.5.12 + "@storybook/node-logger": 6.5.13 "@storybook/semver": ^7.3.2 "@types/node": ^14.0.10 || ^16.0.0 "@types/pretty-hrtime": ^1.0.0 @@ -2391,7 +2481,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: d12b276718d3bb527084135882abc35fcdc4690896579b9f5e0417236a0d02f2791424ac62a891a0353af635be10c22db7dfe04d0db644ab0757ae3981f0ff1a + checksum: 369fbe41e9ac657410a8e7fb4668be0e77c50b84c29b352397cc26b72d79397a7e84dcbf7a94f2d02d819d395a66e30a3915de40e85936d7b7dc50bb426aeabb languageName: node linkType: hard @@ -2413,22 +2503,31 @@ __metadata: languageName: node linkType: hard -"@storybook/core-server@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/core-server@npm:6.5.12" +"@storybook/core-events@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core-events@npm:6.5.13" + dependencies: + core-js: ^3.8.2 + checksum: 2afeaf5fd658a4e9eedb9ad458ba0bcc73ad4ae5ba0e9971434818258db01d9b48b604d4db396ebfc1e1571dace3f6659e9ed61ac35428a792a4e24bbc08b29c + languageName: node + linkType: hard + +"@storybook/core-server@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core-server@npm:6.5.13" dependencies: "@discoveryjs/json-ext": ^0.5.3 - "@storybook/builder-webpack4": 6.5.12 - "@storybook/core-client": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/builder-webpack4": 6.5.13 + "@storybook/core-client": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/csf-tools": 6.5.12 - "@storybook/manager-webpack4": 6.5.12 - "@storybook/node-logger": 6.5.12 + "@storybook/csf-tools": 6.5.13 + "@storybook/manager-webpack4": 6.5.13 + "@storybook/node-logger": 6.5.13 "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.12 - "@storybook/telemetry": 6.5.12 + "@storybook/store": 6.5.13 + "@storybook/telemetry": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 "@types/node-fetch": ^2.5.7 "@types/pretty-hrtime": ^1.0.0 @@ -2472,16 +2571,16 @@ __metadata: optional: true typescript: optional: true - checksum: 1e7e8de948012eb126f30261d23a552e18e671ad7796c46051e17be545c37a70c72437cbb24249d2e88b3525ed6a4b7205ddeeb167aca6b9478df85b128a57d3 + checksum: 142b13ef4fef21a68c8255f35f42ca5c3b9f636b51986f61dc6ae95485788d0f581552604b8a4a796e7cd2ad9548b87317ac9647ea44be58f64b91c440bb71ea languageName: node linkType: hard -"@storybook/core@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/core@npm:6.5.12" +"@storybook/core@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/core@npm:6.5.13" dependencies: - "@storybook/core-client": 6.5.12 - "@storybook/core-server": 6.5.12 + "@storybook/core-client": 6.5.13 + "@storybook/core-server": 6.5.13 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -2493,13 +2592,13 @@ __metadata: optional: true typescript: optional: true - checksum: 82606be8f89ad34a662d366e64d85af5a61270e2cff4dab8c3c3b0ec17ae3e34e560b54a1e2dd3fdd52eb072f8101b409e762f8581e65abcf07097e768baaaf0 + checksum: e0dbe5d8d52f2a12ab63db965d5d15ee671029a03b7204756954e2bc3253b560c8e430aaab5559ccbddcfd3e97d2bc1c0c58ed370aa593912724402aa86996b8 languageName: node linkType: hard -"@storybook/csf-tools@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/csf-tools@npm:6.5.12" +"@storybook/csf-tools@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/csf-tools@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@babel/generator": ^7.12.11 @@ -2520,7 +2619,7 @@ __metadata: peerDependenciesMeta: "@storybook/mdx2-csf": optional: true - checksum: 21da554c88f22ee583cd1956cf440506212d9e8727c7f0a493a92804e58b83d3fcfa18d3081a9fd1b5e8da07a1cfbee15bfa638a13a8fad585eac04fe26f5112 + checksum: 2b8a5bed04ea89084334742e1095c4565b0b7367b5126e3a9b6648224b59c2136a9d57cbb9067264fc3951e9db58df40b23b975170180d171cce35dfabf2a090 languageName: node linkType: hard @@ -2533,18 +2632,18 @@ __metadata: languageName: node linkType: hard -"@storybook/docs-tools@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/docs-tools@npm:6.5.12" +"@storybook/docs-tools@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/docs-tools@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/store": 6.5.12 + "@storybook/store": 6.5.13 core-js: ^3.8.2 doctrine: ^3.0.0 lodash: ^4.17.21 regenerator-runtime: ^0.13.7 - checksum: 9433b0bc74e739f37d4be857e366d74e56566cdf27f72f462eb09a6e84713aadc8c768e075fbe7a062be104bb1536c14b8dea1e890917ca94b1580e71dbe60be + checksum: d3ad4674922025aaf6e4e2b7c2ac6f4eaec8f5692dc9a792a15d2d5e38dcd2e1c138daffb31a3da04da1e04c645b3cd8d921890f9ba318bfee7b6a12f263b48a languageName: node linkType: hard @@ -2561,19 +2660,19 @@ __metadata: languageName: node linkType: hard -"@storybook/manager-webpack4@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/manager-webpack4@npm:6.5.12" +"@storybook/manager-webpack4@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/manager-webpack4@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@babel/plugin-transform-template-literals": ^7.12.1 "@babel/preset-react": ^7.12.10 - "@storybook/addons": 6.5.12 - "@storybook/core-client": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/node-logger": 6.5.12 - "@storybook/theming": 6.5.12 - "@storybook/ui": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/core-client": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/node-logger": 6.5.13 + "@storybook/theming": 6.5.13 + "@storybook/ui": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 "@types/webpack": ^4.41.26 babel-loader: ^8.0.0 @@ -2606,23 +2705,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 89c6ab508a930def13403275201e1e7efd667a25f0e04a6a4fe83e83d8a77065309f11c6ba183bb412072b80580e232365789d2317ae9c1e92df92c4061752be + checksum: 6645f30b6199d0badb2097aca16478e4d8bc88210b55e60c4da86962e8b0a3441128f1e696cddf8a535591920617a7abf5f1f9771dabc486479e76b8d4b20fbd languageName: node linkType: hard "@storybook/manager-webpack5@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/manager-webpack5@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/manager-webpack5@npm:6.5.13" dependencies: "@babel/core": ^7.12.10 "@babel/plugin-transform-template-literals": ^7.12.1 "@babel/preset-react": ^7.12.10 - "@storybook/addons": 6.5.12 - "@storybook/core-client": 6.5.12 - "@storybook/core-common": 6.5.12 - "@storybook/node-logger": 6.5.12 - "@storybook/theming": 6.5.12 - "@storybook/ui": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/core-client": 6.5.13 + "@storybook/core-common": 6.5.13 + "@storybook/node-logger": 6.5.13 + "@storybook/theming": 6.5.13 + "@storybook/ui": 6.5.13 "@types/node": ^14.0.10 || ^16.0.0 babel-loader: ^8.0.0 case-sensitive-paths-webpack-plugin: ^2.3.0 @@ -2652,7 +2751,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 701768cee510e9de024259c88ebd85ebc212d37d9f913fc7a1e13ab8751d7fb579bb99e494a439d6d5388c02e204eea8c8d91d1490b138105b414df8a384b7ae + checksum: 95e720d00e8869f8ec5e8a36f50e5da6aaecdc9a1ea306ae3d7b50f2f07b60a9eb5cb3787e88262c664c958b1f56b7f34e9bf027eb627a96d127b5771ff6a137 languageName: node linkType: hard @@ -2675,29 +2774,29 @@ __metadata: languageName: node linkType: hard -"@storybook/node-logger@npm:6.5.12, @storybook/node-logger@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/node-logger@npm:6.5.12" +"@storybook/node-logger@npm:6.5.13, @storybook/node-logger@npm:^6.5.9": + version: 6.5.13 + resolution: "@storybook/node-logger@npm:6.5.13" dependencies: "@types/npmlog": ^4.1.2 chalk: ^4.1.0 core-js: ^3.8.2 npmlog: ^5.0.1 pretty-hrtime: ^1.0.3 - checksum: 7589477486a25e67d9119e9c363e8bde23e52601043a506ac0d28f4d353f3a228face79b40a2eb0cc0c7c8b05ed084336fef5dcc3213ed4484527c6631eafeb0 + checksum: bcd1d98822687580e39f27003e16c73e3c775cdfe6e9f8fd8fbe9f4626a82f3f63fe281f9c894f3917faa52202ccb8217916978032d27ba6dbfa9720064e7739 languageName: node linkType: hard -"@storybook/preview-web@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/preview-web@npm:6.5.12" +"@storybook/preview-web@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/preview-web@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/channel-postmessage": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/channel-postmessage": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/store": 6.5.12 + "@storybook/store": 6.5.13 ansi-to-html: ^0.6.11 core-js: ^3.8.2 global: ^4.4.0 @@ -2711,7 +2810,7 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: e11671fd136042a0ac19be6749f40bcdf858adb6fbc36998eb2c1737fc622c15df233eb04ff3cf555a61a31d32e5218cf9ff3ff9b941b457a8159a8ca54ab2e8 + checksum: d66d29667a936ee80d15de07bdeec0c6cb2a476fdaa59f262297f5c7dc774acb57e4779c2dd77e61f5a6d9974ac3359babe587a2cd9baf6aa7673ec949b3234d languageName: node linkType: hard @@ -2734,22 +2833,22 @@ __metadata: linkType: hard "@storybook/react@npm:^6.5.9": - version: 6.5.12 - resolution: "@storybook/react@npm:6.5.12" + version: 6.5.13 + resolution: "@storybook/react@npm:6.5.13" dependencies: "@babel/preset-flow": ^7.12.1 "@babel/preset-react": ^7.12.10 "@pmmmwh/react-refresh-webpack-plugin": ^0.5.3 - "@storybook/addons": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core": 6.5.12 - "@storybook/core-common": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core": 6.5.13 + "@storybook/core-common": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 - "@storybook/docs-tools": 6.5.12 - "@storybook/node-logger": 6.5.12 + "@storybook/docs-tools": 6.5.13 + "@storybook/node-logger": 6.5.13 "@storybook/react-docgen-typescript-plugin": 1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0 "@storybook/semver": ^7.3.2 - "@storybook/store": 6.5.12 + "@storybook/store": 6.5.13 "@types/estree": ^0.0.51 "@types/node": ^14.14.20 || ^16.0.0 "@types/webpack-env": ^1.16.0 @@ -2794,7 +2893,7 @@ __metadata: build-storybook: bin/build.js start-storybook: bin/index.js storybook-server: bin/index.js - checksum: 7b762f5b0db2b94d3e492ed45a7566009dc9ff008c9b29db278d6404e1c7c9f419a0114090bf23fc5b3e193a83d74e21f0fe6ba04105eb30f59be1d4c87d652d + checksum: 5a21e4e49a0aba7376dbaef5408e03537cf937d3025e735b45848bce7c9f476a025ca9af4260aa32982a3da668e14a217f66a0ec465ed820369ff2fab49b2ab3 languageName: node linkType: hard @@ -2830,6 +2929,22 @@ __metadata: languageName: node linkType: hard +"@storybook/router@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/router@npm:6.5.13" + dependencies: + "@storybook/client-logger": 6.5.13 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + qs: ^6.10.0 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: ca144b2f6e3a46d5ac9d449b068d0905e9c72939c8574f095d8d7f7307b172a0c5c13f56ff08d5d5bff540292d9ed13eeecc3a4e600e282ea31e70ed763b735a + languageName: node + linkType: hard + "@storybook/semver@npm:^7.3.2": version: 7.3.2 resolution: "@storybook/semver@npm:7.3.2" @@ -2842,12 +2957,12 @@ __metadata: languageName: node linkType: hard -"@storybook/source-loader@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/source-loader@npm:6.5.12" +"@storybook/source-loader@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/source-loader@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/client-logger": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/client-logger": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 core-js: ^3.8.2 estraverse: ^5.2.0 @@ -2859,17 +2974,17 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: ad6b0774877678d8495e7afaeb820adbc2d1b091df5678da39123eca875cc30fe41f013c2db0a4c5f7614529dc46be00cb165484e65a960d3fe7657b04cf418f + checksum: 93da14a367a954f664d233f64bd9e7d983475e489b8fbe5c90b723ff793279ab899d86f967eb6882f7997296f4286f6f5c82c17ca83e9294c91f8e4aa0ec0a1d languageName: node linkType: hard -"@storybook/store@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/store@npm:6.5.12" +"@storybook/store@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/store@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/core-events": 6.5.12 + "@storybook/addons": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/core-events": 6.5.13 "@storybook/csf": 0.0.2--canary.4566f4d.1 core-js: ^3.8.2 fast-deep-equal: ^3.1.3 @@ -2885,16 +3000,16 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 7fab43471c692cda33e9cbb7abf8a932bf922bd01bf26c56a3dd909f57ff0f26a80c6456a41c5be01191bb818536f16f5c98617d263b49a60e432b0acca07949 + checksum: 69f55927bd3569ec9d87f4351879fd07654d51524a0f9da05c64c7f7f3b50e33024f1554fa59668997e47189e9838b85b691de422ab539bd71126b56922d9381 languageName: node linkType: hard -"@storybook/telemetry@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/telemetry@npm:6.5.12" +"@storybook/telemetry@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/telemetry@npm:6.5.13" dependencies: - "@storybook/client-logger": 6.5.12 - "@storybook/core-common": 6.5.12 + "@storybook/client-logger": 6.5.13 + "@storybook/core-common": 6.5.13 chalk: ^4.1.0 core-js: ^3.8.2 detect-package-manager: ^2.0.1 @@ -2905,7 +3020,7 @@ __metadata: nanoid: ^3.3.1 read-pkg-up: ^7.0.1 regenerator-runtime: ^0.13.7 - checksum: fe465e31e20bc271b1b066a1c1fc4ea8b7cdca1858bc875e19d7ad4e7988c0cff084ce822dc0657ecdefafa02a416dd239753c9c197f6758b39d9260964d631c + checksum: 94ad6fb58b09c8073600ad95b2a48f476524ea7bc6155aee8e9682966a99ac875a9923ee6512252238e8c56eeef725a712e94ba87e1060d7dca9ab196a9051a6 languageName: node linkType: hard @@ -2952,19 +3067,34 @@ __metadata: languageName: node linkType: hard -"@storybook/ui@npm:6.5.12": - version: 6.5.12 - resolution: "@storybook/ui@npm:6.5.12" +"@storybook/theming@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/theming@npm:6.5.13" dependencies: - "@storybook/addons": 6.5.12 - "@storybook/api": 6.5.12 - "@storybook/channels": 6.5.12 - "@storybook/client-logger": 6.5.12 - "@storybook/components": 6.5.12 - "@storybook/core-events": 6.5.12 - "@storybook/router": 6.5.12 + "@storybook/client-logger": 6.5.13 + core-js: ^3.8.2 + memoizerific: ^1.11.3 + regenerator-runtime: ^0.13.7 + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: f7a59c7d81b87f3fbf65c5eb72f5db5a5c1707236c92350d46bc7e1dcf848d57522ca5dcdd4fe19336d4611bd20727e0d900c4b591d2e8e1dd8a754cb9c56aa3 + languageName: node + linkType: hard + +"@storybook/ui@npm:6.5.13": + version: 6.5.13 + resolution: "@storybook/ui@npm:6.5.13" + dependencies: + "@storybook/addons": 6.5.13 + "@storybook/api": 6.5.13 + "@storybook/channels": 6.5.13 + "@storybook/client-logger": 6.5.13 + "@storybook/components": 6.5.13 + "@storybook/core-events": 6.5.13 + "@storybook/router": 6.5.13 "@storybook/semver": ^7.3.2 - "@storybook/theming": 6.5.12 + "@storybook/theming": 6.5.13 core-js: ^3.8.2 memoizerific: ^1.11.3 qs: ^6.10.0 @@ -2973,130 +3103,130 @@ __metadata: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - checksum: 026ddc42d00773ad711824a5374b040b589e8a87b8500db4d6a8cb67263eba0789d6e6afe7fd8e0fcb798380eba210b4225af197f064d3e88ec0bda97a83b28b + checksum: d2866987f51d945246776d42628bc2b79e701f1e59fd511bb1e590c83c4b9d9adee5c7e1a11ecb2ef12b9192613e2d576694bbc9aeb1df5545567f2aa44c0145 languageName: node linkType: hard -"@swc/core-android-arm-eabi@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-android-arm-eabi@npm:1.3.4" +"@swc/core-android-arm-eabi@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-android-arm-eabi@npm:1.3.9" dependencies: "@swc/wasm": 1.2.122 conditions: os=android & cpu=arm languageName: node linkType: hard -"@swc/core-android-arm64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-android-arm64@npm:1.3.4" +"@swc/core-android-arm64@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-android-arm64@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-darwin-arm64@npm:1.3.4" +"@swc/core-darwin-arm64@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-darwin-arm64@npm:1.3.9" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-darwin-x64@npm:1.3.4" +"@swc/core-darwin-x64@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-darwin-x64@npm:1.3.9" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-freebsd-x64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-freebsd-x64@npm:1.3.4" +"@swc/core-freebsd-x64@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-freebsd-x64@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.4" +"@swc/core-linux-arm-gnueabihf@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.4" +"@swc/core-linux-arm64-gnu@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.9" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.4" +"@swc/core-linux-arm64-musl@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.9" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.4" +"@swc/core-linux-x64-gnu@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.9" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-x64-musl@npm:1.3.4" +"@swc/core-linux-x64-musl@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-x64-musl@npm:1.3.9" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.4" +"@swc/core-win32-arm64-msvc@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.4" +"@swc/core-win32-ia32-msvc@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.4" +"@swc/core-win32-x64-msvc@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.9" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:^1.2.239": - version: 1.3.4 - resolution: "@swc/core@npm:1.3.4" +"@swc/core@npm:^1.3.9": + version: 1.3.9 + resolution: "@swc/core@npm:1.3.9" dependencies: - "@swc/core-android-arm-eabi": 1.3.4 - "@swc/core-android-arm64": 1.3.4 - "@swc/core-darwin-arm64": 1.3.4 - "@swc/core-darwin-x64": 1.3.4 - "@swc/core-freebsd-x64": 1.3.4 - "@swc/core-linux-arm-gnueabihf": 1.3.4 - "@swc/core-linux-arm64-gnu": 1.3.4 - "@swc/core-linux-arm64-musl": 1.3.4 - "@swc/core-linux-x64-gnu": 1.3.4 - "@swc/core-linux-x64-musl": 1.3.4 - "@swc/core-win32-arm64-msvc": 1.3.4 - "@swc/core-win32-ia32-msvc": 1.3.4 - "@swc/core-win32-x64-msvc": 1.3.4 + "@swc/core-android-arm-eabi": 1.3.9 + "@swc/core-android-arm64": 1.3.9 + "@swc/core-darwin-arm64": 1.3.9 + "@swc/core-darwin-x64": 1.3.9 + "@swc/core-freebsd-x64": 1.3.9 + "@swc/core-linux-arm-gnueabihf": 1.3.9 + "@swc/core-linux-arm64-gnu": 1.3.9 + "@swc/core-linux-arm64-musl": 1.3.9 + "@swc/core-linux-x64-gnu": 1.3.9 + "@swc/core-linux-x64-musl": 1.3.9 + "@swc/core-win32-arm64-msvc": 1.3.9 + "@swc/core-win32-ia32-msvc": 1.3.9 + "@swc/core-win32-x64-msvc": 1.3.9 dependenciesMeta: "@swc/core-android-arm-eabi": optional: true @@ -3126,7 +3256,7 @@ __metadata: optional: true bin: swcx: run_swcx.js - checksum: 24b4ca4a096fea53056ed227a8aa09aa38cfe5eef344451397e66a2d183ded119872cf4fc8c671c0a6eb34985537cbf8d8a7e742b9e27dad0735426693d11dc8 + checksum: 761918f1bca5d494eaaafd49720717e3b3071df5bc6ef8b298a778ba4e4d20bc5a78c939a0b1c98623f9fe23e535a16a359179b72390cd1f5cbc891ea53c22fa languageName: node linkType: hard @@ -10734,7 +10864,7 @@ __metadata: "@storybook/node-logger": ^6.5.9 "@storybook/react": ^6.5.9 "@storybook/testing-library": ^0.0.13 - "@swc/core": ^1.2.239 + "@swc/core": ^1.3.9 react: ^17.0.2 react-dom: ^17.0.2 react-hot-loader: ^4.13.0 diff --git a/yarn.lock b/yarn.lock index 8be2e5c8a6..2332db24c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -339,6 +339,33 @@ __metadata: languageName: node linkType: hard +"@azure/arm-appservice@npm:^13.0.1": + version: 13.0.2 + resolution: "@azure/arm-appservice@npm:13.0.2" + dependencies: + "@azure/abort-controller": ^1.0.0 + "@azure/core-auth": ^1.3.0 + "@azure/core-client": ^1.5.0 + "@azure/core-lro": ^2.2.0 + "@azure/core-paging": ^1.2.0 + "@azure/core-rest-pipeline": ^1.8.0 + tslib: ^2.2.0 + checksum: 6d604c492a70a6cdd4d3ad3f634a74228dc081ed4f49128873e76bfa5e65ec8cce18b7b19319fd2b1b4edbd390dbd66c9ac48646f0ed80cae09cd2f1d05fb279 + languageName: node + linkType: hard + +"@azure/arm-resourcegraph@npm:^4.2.1": + version: 4.2.1 + resolution: "@azure/arm-resourcegraph@npm:4.2.1" + dependencies: + "@azure/core-auth": ^1.1.4 + "@azure/ms-rest-azure-js": ^2.1.0 + "@azure/ms-rest-js": ^2.2.0 + tslib: ^1.10.0 + checksum: a5afea9faef84ed4576fda430301039524aefe6976a54204fe4453e672aa3d13edc7ef5c996ad6293e9afa3a0833549174aaac18ce236c4ec1242698e2c22bd2 + languageName: node + linkType: hard + "@azure/core-asynciterator-polyfill@npm:^1.0.0": version: 1.0.0 resolution: "@azure/core-asynciterator-polyfill@npm:1.0.0" @@ -346,6 +373,16 @@ __metadata: languageName: node linkType: hard +"@azure/core-auth@npm:^1.1.4, @azure/core-auth@npm:^1.4.0": + version: 1.4.0 + resolution: "@azure/core-auth@npm:1.4.0" + dependencies: + "@azure/abort-controller": ^1.0.0 + tslib: ^2.2.0 + checksum: 1c76c296fe911ad39fc780b033c25a92c41c5a15f011b816d42c13584f627a4dd153dfb4334900ec93eb5b006e14bdda37e8412a7697c5a9636a0abaccffad39 + languageName: node + linkType: hard + "@azure/core-auth@npm:^1.3.0": version: 1.3.2 resolution: "@azure/core-auth@npm:1.3.2" @@ -371,6 +408,21 @@ __metadata: languageName: node linkType: hard +"@azure/core-client@npm:^1.5.0": + version: 1.6.1 + resolution: "@azure/core-client@npm:1.6.1" + dependencies: + "@azure/abort-controller": ^1.0.0 + "@azure/core-auth": ^1.4.0 + "@azure/core-rest-pipeline": ^1.9.1 + "@azure/core-tracing": ^1.0.0 + "@azure/core-util": ^1.0.0 + "@azure/logger": ^1.0.0 + tslib: ^2.2.0 + checksum: 400890a9b5f0c8801ff92005c3cba7bb5124634e321735406731bef33688a79f23d28b49fccdfab90814dade41a13f3d3cb99f80151a2bff17628416e811afb6 + languageName: node + linkType: hard + "@azure/core-http@npm:^2.0.0": version: 2.2.4 resolution: "@azure/core-http@npm:2.2.4" @@ -415,6 +467,15 @@ __metadata: languageName: node linkType: hard +"@azure/core-paging@npm:^1.2.0": + version: 1.3.0 + resolution: "@azure/core-paging@npm:1.3.0" + dependencies: + tslib: ^2.2.0 + checksum: 3fbf3d6474e2346f7c3ea8344a41bf41e053789efbbd29df78365e9c9ca66e143da3e5407d8c9dd5ddc82a65680185cd6f0ec851c48635776d18a6a605a98e78 + languageName: node + linkType: hard + "@azure/core-rest-pipeline@npm:^1.1.0, @azure/core-rest-pipeline@npm:^1.5.0": version: 1.5.0 resolution: "@azure/core-rest-pipeline@npm:1.5.0" @@ -432,6 +493,24 @@ __metadata: languageName: node linkType: hard +"@azure/core-rest-pipeline@npm:^1.8.0, @azure/core-rest-pipeline@npm:^1.9.1": + version: 1.9.2 + resolution: "@azure/core-rest-pipeline@npm:1.9.2" + dependencies: + "@azure/abort-controller": ^1.0.0 + "@azure/core-auth": ^1.4.0 + "@azure/core-tracing": ^1.0.1 + "@azure/core-util": ^1.0.0 + "@azure/logger": ^1.0.0 + form-data: ^4.0.0 + http-proxy-agent: ^5.0.0 + https-proxy-agent: ^5.0.0 + tslib: ^2.2.0 + uuid: ^8.3.0 + checksum: cdfdaf8bdb0778f40ecfeb8b5da0b4f8643d641b75bb5869485afd38f7c3127ab088ac2fe2980df03d28fb6715928e04560343ee6416246aebe75443c4dd2906 + languageName: node + linkType: hard + "@azure/core-tracing@npm:1.0.0-preview.13": version: 1.0.0-preview.13 resolution: "@azure/core-tracing@npm:1.0.0-preview.13" @@ -442,7 +521,7 @@ __metadata: languageName: node linkType: hard -"@azure/core-tracing@npm:^1.0.0": +"@azure/core-tracing@npm:^1.0.0, @azure/core-tracing@npm:^1.0.1": version: 1.0.1 resolution: "@azure/core-tracing@npm:1.0.1" dependencies: @@ -493,6 +572,34 @@ __metadata: languageName: node linkType: hard +"@azure/ms-rest-azure-js@npm:^2.1.0": + version: 2.1.0 + resolution: "@azure/ms-rest-azure-js@npm:2.1.0" + dependencies: + "@azure/core-auth": ^1.1.4 + "@azure/ms-rest-js": ^2.2.0 + tslib: ^1.10.0 + checksum: 0e6d6700f4ee0ad14383642b02fe86b33f4034aaff38499374ed5be59154e869ee231790d20b7557622ebcdfa9eed45dc2559391a79dee51c849a8ced0c86fed + languageName: node + linkType: hard + +"@azure/ms-rest-js@npm:^2.2.0": + version: 2.6.2 + resolution: "@azure/ms-rest-js@npm:2.6.2" + dependencies: + "@azure/core-auth": ^1.1.4 + abort-controller: ^3.0.0 + form-data: ^2.5.0 + node-fetch: ^2.6.7 + tough-cookie: ^3.0.1 + tslib: ^1.10.0 + tunnel: 0.0.6 + uuid: ^8.3.2 + xml2js: ^0.4.19 + checksum: de650dfc3928a2036923fb3bf50e0324f9f36f5d320ca6d128005b1345ad80b39ab3410073ac35fb137dec4c7d799c602b1cafdf9573b3432b6b8eee8a1e39f8 + languageName: node + linkType: hard + "@azure/msal-browser@npm:^2.26.0": version: 2.27.0 resolution: "@azure/msal-browser@npm:2.27.0" @@ -521,8 +628,8 @@ __metadata: linkType: hard "@azure/storage-blob@npm:^12.5.0": - version: 12.11.0 - resolution: "@azure/storage-blob@npm:12.11.0" + version: 12.12.0 + resolution: "@azure/storage-blob@npm:12.12.0" dependencies: "@azure/abort-controller": ^1.0.0 "@azure/core-http": ^2.0.0 @@ -532,7 +639,7 @@ __metadata: "@azure/logger": ^1.0.0 events: ^3.0.0 tslib: ^2.2.0 - checksum: f43501d66f10fb3f8e63b0e1e6e90827240e0ff41d722181c52b3abb13fa1b1b89dd985d0b6e488f6ffeee030e4afc1b6a9b6e2f18078ff6472dc494c5b199cb + checksum: bb7f6514a251f8ec337718088b8c9ba80e27b34c786b7fb380fdca19eb3735a0da917dcfcdacef27d0eb256d007a37f89f3d6b08c55405cbcc04685f2a51da3b languageName: node linkType: hard @@ -3057,6 +3164,7 @@ __metadata: "@types/minimist": ^1.2.0 "@types/mock-fs": ^4.13.0 "@types/morgan": ^1.9.0 + "@types/node-forge": ^1.3.0 "@types/recursive-readdir": ^2.2.0 "@types/stoppable": ^1.1.0 "@types/supertest": ^2.0.8 @@ -3094,6 +3202,7 @@ __metadata: mysql2: ^2.2.5 node-abort-controller: ^3.0.1 node-fetch: ^2.6.7 + node-forge: ^1.3.1 raw-body: ^2.4.1 recursive-readdir: ^2.2.2 request: ^2.88.2 @@ -3182,14 +3291,14 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-client@npm:^1.1.0": - version: 1.1.0 - resolution: "@backstage/catalog-client@npm:1.1.0" +"@backstage/catalog-client@npm:^1.1.1": + version: 1.1.1 + resolution: "@backstage/catalog-client@npm:1.1.1" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/errors": ^1.1.1 + "@backstage/catalog-model": ^1.1.2 + "@backstage/errors": ^1.1.2 cross-fetch: ^3.1.5 - checksum: 54ee5c193bbebd46d71dbfc259e2ba6958d1f492e1b5f31a9ca17add95b7daaaa0a6f51b03a2a1bb7207c9e5f36a16cc40bd2f3cce7381e577f61249625c5b20 + checksum: 788c357e5a783ce46e4dcd805b302ffe2b407ad9bb04e49ee6b6aa8fb42adfd9cae8a57e2d18cd4c81ef22011990a5eb6518f3f44fb812485d7c99226db26e1d languageName: node linkType: hard @@ -3205,18 +3314,18 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@npm:^1.1.0-next.2, @backstage/catalog-model@npm:^1.1.1": - version: 1.1.1 - resolution: "@backstage/catalog-model@npm:1.1.1" +"@backstage/catalog-model@npm:^1.1.1, @backstage/catalog-model@npm:^1.1.2": + version: 1.1.2 + resolution: "@backstage/catalog-model@npm:1.1.2" dependencies: - "@backstage/config": ^1.0.2 - "@backstage/errors": ^1.1.1 + "@backstage/config": ^1.0.3 + "@backstage/errors": ^1.1.2 "@backstage/types": ^1.0.0 ajv: ^8.10.0 json-schema: ^0.4.0 lodash: ^4.17.21 uuid: ^8.0.0 - checksum: 32b38c59b40b8ebbabbced27fe996305ba739015eb20fad20c6e106d9058a81f05fee4878fac801e074a8f316702fd2c5c16af35c4b827defe83400320070658 + checksum: d53e5db9f1662b6c2bd48b3b1fed97751b5bf8805575feb12ed2b6f600048d676f954fe4cfa214536d09f984535f0524a24c6330e08c4d255064d0eb62678bc1 languageName: node linkType: hard @@ -3276,11 +3385,11 @@ __metadata: "@spotify/eslint-config-typescript": ^14.0.0 "@sucrase/jest-plugin": ^2.1.1 "@sucrase/webpack-loader": ^2.0.0 - "@svgr/plugin-jsx": 6.3.x - "@svgr/plugin-svgo": 6.3.x - "@svgr/rollup": 6.3.x - "@svgr/webpack": 6.3.x - "@swc/core": ^1.2.239 + "@svgr/plugin-jsx": 6.5.x + "@svgr/plugin-svgo": 6.5.x + "@svgr/rollup": 6.5.x + "@svgr/webpack": 6.5.x + "@swc/core": ^1.3.9 "@swc/helpers": ^0.4.7 "@swc/jest": ^0.2.22 "@types/diff": ^5.0.0 @@ -3434,13 +3543,13 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@npm:^1.0.1, @backstage/config@npm:^1.0.2": - version: 1.0.2 - resolution: "@backstage/config@npm:1.0.2" +"@backstage/config@npm:^1.0.3": + version: 1.0.3 + resolution: "@backstage/config@npm:1.0.3" dependencies: "@backstage/types": ^1.0.0 lodash: ^4.17.21 - checksum: 452c9fd47247974552bb8d53cfebcab120d37f60a209da74ec06b1408b1ae6464c8f61404d0d071eb7b70ece0aaa5462d02e9cc5713a21744795a8b9db3f80ce + checksum: 1026f741ac019043f46965eeb93c796110b7b98add181562a975e3ef9bf27332ea7841cd174a358a55c1edced892fc8b644263caf14f6c6d244b550a1176e1ad languageName: node linkType: hard @@ -3490,13 +3599,13 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@npm:^0.11.0, @backstage/core-components@npm:^0.11.1": - version: 0.11.1 - resolution: "@backstage/core-components@npm:0.11.1" +"@backstage/core-components@npm:^0.11.1, @backstage/core-components@npm:^0.11.2": + version: 0.11.2 + resolution: "@backstage/core-components@npm:0.11.2" dependencies: - "@backstage/config": ^1.0.2 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/errors": ^1.1.1 + "@backstage/config": ^1.0.3 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/errors": ^1.1.2 "@backstage/theme": ^0.2.16 "@backstage/version-bridge": ^1.0.1 "@material-table/core": ^3.1.0 @@ -3537,7 +3646,7 @@ __metadata: react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 91f185253d6fba47d3c7c93f37cc318f2b59f790efc518c1f207c976b0bc3153993d87365802e97ba66fb12f373fd2fa61eb99f2390a133a6e1ae69396cad346 + checksum: 510a0c2594a9aa820293d79fbaeee72bef9ac5e6161645d05e2c64b7176050386984befa07921391f9758bfbf07f96170662f7881e894fb0b0f2787378b19182 languageName: node linkType: hard @@ -3613,11 +3722,11 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@npm:^1.0.3, @backstage/core-plugin-api@npm:^1.0.6": - version: 1.0.6 - resolution: "@backstage/core-plugin-api@npm:1.0.6" +"@backstage/core-plugin-api@npm:^1.0.6, @backstage/core-plugin-api@npm:^1.0.7": + version: 1.0.7 + resolution: "@backstage/core-plugin-api@npm:1.0.7" dependencies: - "@backstage/config": ^1.0.2 + "@backstage/config": ^1.0.3 "@backstage/types": ^1.0.0 "@backstage/version-bridge": ^1.0.1 history: ^5.0.0 @@ -3627,7 +3736,7 @@ __metadata: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: ee44e5b1c6fe59f67ca55f7d0777873937a9c2ec6d33c6bf9c30f7d3063f3366b8680578854f5abdecee4aa1f71d1844c82100c87abd20b637ea385c8dd89618 + checksum: ce07e5ec1f4f21a5e879e3ab2eb271721cc0eadd685a7457098afca1d8106140883fbc7c3cf7b436c594614152a88fcba7466df0900d9fa8e958baab2f8b1a6a languageName: node linkType: hard @@ -3672,7 +3781,6 @@ __metadata: "@types/node": ^16.11.26 "@types/recursive-readdir": ^2.2.0 chalk: ^4.0.0 - command-exists: ^1.2.9 commander: ^9.1.0 fs-extra: 10.1.0 handlebars: ^4.7.3 @@ -3718,14 +3826,14 @@ __metadata: languageName: unknown linkType: soft -"@backstage/errors@npm:^1.1.1": - version: 1.1.1 - resolution: "@backstage/errors@npm:1.1.1" +"@backstage/errors@npm:^1.1.2": + version: 1.1.2 + resolution: "@backstage/errors@npm:1.1.2" dependencies: "@backstage/types": ^1.0.0 cross-fetch: ^3.1.5 serialize-error: ^8.0.1 - checksum: 8983eba72212a545bd3543d71f796b9836aacecfd85d6ac67a5a37da4af5566b8267533547ffc9caa01969c6c08a2197569626206aa84cb18fbfe4bf03ca8328 + checksum: 1e8b58b8059af8e83ebc1c8fea94227ec887a55e3adc32ab959fa86ac20bed745703a5e046b17505c05e7306c1835834756d27cfc0cbeea2021cb9631656ed22 languageName: node linkType: hard @@ -3740,14 +3848,14 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@npm:^1.1.4": - version: 1.1.4 - resolution: "@backstage/integration-react@npm:1.1.4" +"@backstage/integration-react@npm:^1.1.5": + version: 1.1.5 + resolution: "@backstage/integration-react@npm:1.1.5" dependencies: - "@backstage/config": ^1.0.2 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/integration": ^1.3.1 + "@backstage/config": ^1.0.3 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/integration": ^1.3.2 "@backstage/theme": ^0.2.16 "@material-ui/core": ^4.12.2 "@material-ui/icons": ^4.9.1 @@ -3755,7 +3863,7 @@ __metadata: react-use: ^17.2.4 peerDependencies: react: ^16.13.1 || ^17.0.0 - checksum: c17e7e8a545e551fb246a6e879c37975a8dc3bdfe0db04998cbb940bea5ece7df328353f8193764ce68e9f0df14af8cb1a6549bfe8f15a2160312b7339040be6 + checksum: 2f50416e0ca7635a59b5bf4bf89b4cf8d91a755e7ba2e4be75fb338011f98533213f984ac2def7ff65fccd51d7a94ba064c59b47d0d1d41c2a4b51f54ed68992 languageName: node linkType: hard @@ -3786,19 +3894,19 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration@npm:^1.3.1": - version: 1.3.1 - resolution: "@backstage/integration@npm:1.3.1" +"@backstage/integration@npm:^1.3.2": + version: 1.3.2 + resolution: "@backstage/integration@npm:1.3.2" dependencies: - "@backstage/config": ^1.0.2 - "@backstage/errors": ^1.1.1 + "@backstage/config": ^1.0.3 + "@backstage/errors": ^1.1.2 "@octokit/auth-app": ^4.0.0 "@octokit/rest": ^19.0.3 cross-fetch: ^3.1.5 git-url-parse: ^13.0.0 lodash: ^4.17.21 luxon: ^3.0.0 - checksum: e684f6ab52e8ef25db49f8fda38407a6d3153a30d1fbea7615a39af9cdfa8ba064a0b13a09658a28edc3364202bd673d8ceee34f7ec46567a302cfbed3ea945b + checksum: 960f0ea564021082f8b6e10a59782f6ffb80550dbd8a1b62e52d8b03bf72345759f0d744ef7c41434d32ad3987d4dd89e18645f818be6c00cd53e541c0a041a1 languageName: node linkType: hard @@ -4289,6 +4397,66 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-azure-sites-backend@workspace:^, @backstage/plugin-azure-sites-backend@workspace:plugins/azure-sites-backend": + version: 0.0.0-use.local + resolution: "@backstage/plugin-azure-sites-backend@workspace:plugins/azure-sites-backend" + dependencies: + "@azure/arm-appservice": ^13.0.1 + "@azure/arm-resourcegraph": ^4.2.1 + "@azure/identity": ^2.1.0 + "@backstage/backend-common": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/plugin-azure-sites-common": "workspace:^" + "@types/express": ^4.17.6 + "@types/supertest": ^2.0.8 + express: ^4.17.1 + express-promise-router: ^4.1.0 + msw: ^0.47.0 + node-fetch: ^2.6.7 + winston: ^3.2.1 + yn: ^4.0.0 + languageName: unknown + linkType: soft + +"@backstage/plugin-azure-sites-common@workspace:^, @backstage/plugin-azure-sites-common@workspace:plugins/azure-sites-common": + version: 0.0.0-use.local + resolution: "@backstage/plugin-azure-sites-common@workspace:plugins/azure-sites-common" + dependencies: + "@backstage/cli": "workspace:^" + languageName: unknown + linkType: soft + +"@backstage/plugin-azure-sites@workspace:^, @backstage/plugin-azure-sites@workspace:plugins/azure-sites": + version: 0.0.0-use.local + resolution: "@backstage/plugin-azure-sites@workspace:plugins/azure-sites" + dependencies: + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-components": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/dev-utils": "workspace:^" + "@backstage/plugin-azure-sites-common": "workspace:^" + "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/theme": "workspace:^" + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.57 + "@testing-library/jest-dom": ^5.10.1 + "@testing-library/react": ^12.1.3 + "@testing-library/user-event": ^14.0.0 + "@types/node": ^16.11.26 + cross-fetch: ^3.1.5 + luxon: ^3.0.0 + msw: ^0.47.0 + react-use: ^17.2.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 + languageName: unknown + linkType: soft + "@backstage/plugin-badges-backend@workspace:^, @backstage/plugin-badges-backend@workspace:plugins/badges-backend": version: 0.0.0-use.local resolution: "@backstage/plugin-badges-backend@workspace:plugins/badges-backend" @@ -4429,7 +4597,7 @@ __metadata: msw: ^0.47.0 qs: ^6.9.6 react-use: ^17.2.4 - recharts: ^1.8.5 + recharts: ^2.0.0 peerDependencies: react: ^16.13.1 || ^17.0.0 languageName: unknown @@ -4440,18 +4608,22 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-aws@workspace:plugins/catalog-backend-module-aws" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@types/lodash": ^4.14.151 aws-sdk: ^2.840.0 aws-sdk-mock: ^5.2.1 lodash: ^4.17.21 + luxon: ^3.0.0 p-limit: ^3.0.2 uuid: ^8.0.0 winston: ^3.2.1 @@ -4464,6 +4636,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-azure@workspace:plugins/catalog-backend-module-azure" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -4472,9 +4645,11 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@types/lodash": ^4.14.151 lodash: ^4.17.21 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4508,6 +4683,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-bitbucket-server@workspace:plugins/catalog-backend-module-bitbucket-server" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -4516,7 +4692,9 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@types/node-fetch": ^2.5.12 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4551,6 +4729,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-gerrit@workspace:plugins/catalog-backend-module-gerrit" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -4559,8 +4738,10 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@types/fs-extra": ^9.0.1 fs-extra: 10.1.0 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4590,6 +4771,7 @@ __metadata: "@types/lodash": ^4.14.151 git-url-parse: ^13.0.0 lodash: ^4.17.21 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4602,6 +4784,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-gitlab@workspace:plugins/catalog-backend-module-gitlab" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -4610,10 +4793,12 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@types/lodash": ^4.14.151 "@types/uuid": ^8.0.0 lodash: ^4.17.21 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4647,16 +4832,19 @@ __metadata: dependencies: "@azure/identity": ^2.1.0 "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@microsoft/microsoft-graph-types": ^2.6.0 "@types/lodash": ^4.14.151 "@types/node-fetch": ^2.5.12 lodash: ^4.17.21 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 p-limit: ^3.0.2 @@ -4739,13 +4927,14 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@npm:^1.0.4-next.0, @backstage/plugin-catalog-common@npm:^1.0.6": - version: 1.0.6 - resolution: "@backstage/plugin-catalog-common@npm:1.0.6" +"@backstage/plugin-catalog-common@npm:^1.0.7": + version: 1.0.7 + resolution: "@backstage/plugin-catalog-common@npm:1.0.7" dependencies: - "@backstage/plugin-permission-common": ^0.6.4 - "@backstage/plugin-search-common": ^1.0.1 - checksum: a4f227cc0339ce41a954f024f62752fdda148331ca94dc6d5e90186e67b66e3dd2182467709478309d9eb3031309549afd179742ce182a6dd16f9cbd58550e5c + "@backstage/catalog-model": ^1.1.2 + "@backstage/plugin-permission-common": ^0.7.0 + "@backstage/plugin-search-common": ^1.1.0 + checksum: 8a229ccbea8ce2bd0863dbf3694ac7f59690097d2607b92ea38aff78d6e5d2ac21f6d16c1a9b5bf7c09901ca7eb52d21e73480f4b1646d0aa94db8c589a3dcd5 languageName: node linkType: hard @@ -4877,19 +5066,19 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@npm:^1.1.2-next.2, @backstage/plugin-catalog-react@npm:^1.1.4": - version: 1.1.4 - resolution: "@backstage/plugin-catalog-react@npm:1.1.4" +"@backstage/plugin-catalog-react@npm:^1.1.4, @backstage/plugin-catalog-react@npm:^1.2.0": + version: 1.2.0 + resolution: "@backstage/plugin-catalog-react@npm:1.2.0" dependencies: - "@backstage/catalog-client": ^1.1.0 - "@backstage/catalog-model": ^1.1.1 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/errors": ^1.1.1 - "@backstage/integration": ^1.3.1 - "@backstage/plugin-catalog-common": ^1.0.6 - "@backstage/plugin-permission-common": ^0.6.4 - "@backstage/plugin-permission-react": ^0.4.5 + "@backstage/catalog-client": ^1.1.1 + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/errors": ^1.1.2 + "@backstage/integration": ^1.3.2 + "@backstage/plugin-catalog-common": ^1.0.7 + "@backstage/plugin-permission-common": ^0.7.0 + "@backstage/plugin-permission-react": ^0.4.6 "@backstage/theme": ^0.2.16 "@backstage/types": ^1.0.0 "@backstage/version-bridge": ^1.0.1 @@ -4907,7 +5096,7 @@ __metadata: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 - checksum: 6e0f0405d734a5d8316b490ceee761aadac5cf8e037f4532339b736ddfc61b434ed3b761ed6027c52e492d4592fbbbd0ad0fee260d3e9f6bcd756cedb43cc930 + checksum: c2875ac0af1e3e051b2faf018bd46b9a1843c2fd67fcfd1a09a7931ad7fbd6adf591200d184f40194702aa6f21757c7bb70636861d25ef2f1e670fca0cb0a7cf languageName: node linkType: hard @@ -5193,7 +5382,7 @@ __metadata: luxon: ^3.0.0 msw: ^0.47.0 react-use: ^17.2.4 - recharts: ^1.8.5 + recharts: ^2.0.0 peerDependencies: react: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 @@ -5305,7 +5494,7 @@ __metadata: pluralize: ^8.0.0 qs: ^6.9.4 react-use: ^17.2.4 - recharts: ^1.8.5 + recharts: ^2.0.0 regression: ^2.0.1 yup: ^0.32.9 peerDependencies: @@ -5370,6 +5559,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-explore-react": "workspace:^" "@backstage/test-utils": "workspace:^" @@ -5542,7 +5732,7 @@ __metadata: msw: ^0.47.0 qs: ^6.10.1 react-use: ^17.2.4 - recharts: ^1.8.5 + recharts: ^2.0.0 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 @@ -5673,8 +5863,9 @@ __metadata: "@types/node": ^16.11.26 "@types/react": ^16.13.1 || ^17.0.0 cross-fetch: ^3.1.5 - moment: ^2.29.1 + luxon: ^3.0.0 msw: ^0.47.0 + p-limit: ^4.0.0 react-use: ^17.2.4 peerDependencies: react: ^16.13.1 || ^17.0.0 @@ -5800,16 +5991,16 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-home@npm:^0.4.25": - version: 0.4.25 - resolution: "@backstage/plugin-home@npm:0.4.25" +"@backstage/plugin-home@npm:^0.4.26": + version: 0.4.26 + resolution: "@backstage/plugin-home@npm:0.4.26" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/config": ^1.0.2 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/plugin-catalog-react": ^1.1.4 - "@backstage/plugin-stack-overflow": ^0.1.5 + "@backstage/catalog-model": ^1.1.2 + "@backstage/config": ^1.0.3 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 + "@backstage/plugin-stack-overflow": ^0.1.6 "@backstage/theme": ^0.2.16 "@material-ui/core": ^4.12.2 "@material-ui/icons": ^4.9.1 @@ -5820,7 +6011,7 @@ __metadata: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 - checksum: 76610e6caa8db980c37aa01bb39e6bdb6254a7c765155de2195b377a0d2b45aa3fbaa197c7429405f4fa67c617b3ba10353d0bcd1b820744024462feed206ec4 + checksum: bb20f2538a1e155bbb7c5587fe953ecaacd6f0384b951dcd862be7a2fa277ed80d793336953cc778b6a574ee898064a5aab436b3c61d3695944084c748449095 languageName: node linkType: hard @@ -6326,16 +6517,17 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-permission-common@npm:^0.6.4": - version: 0.6.4 - resolution: "@backstage/plugin-permission-common@npm:0.6.4" +"@backstage/plugin-permission-common@npm:^0.7.0": + version: 0.7.0 + resolution: "@backstage/plugin-permission-common@npm:0.7.0" dependencies: - "@backstage/config": ^1.0.2 - "@backstage/errors": ^1.1.1 + "@backstage/config": ^1.0.3 + "@backstage/errors": ^1.1.2 + "@backstage/types": ^1.0.0 cross-fetch: ^3.1.5 uuid: ^8.0.0 zod: ^3.11.6 - checksum: 35e2b43e822a08c5e51cbfef1d797b1e8795bf252e65bab9fb2683d90778edc68586223fc1b714229743943732db4a9abbe9a44d8b3dba383e01ee9075efbc90 + checksum: e71705494b55efacf234a46abe162f241ebecb231a6a4c76d7134bb629c1b7d1ffdead8f9725ea515a818ce376a99d2121dc7ef1c8d0badd955fdb1d4cd405cf languageName: node linkType: hard @@ -6346,6 +6538,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/types": "workspace:^" cross-fetch: ^3.1.5 msw: ^0.47.0 uuid: ^8.0.0 @@ -6371,16 +6564,17 @@ __metadata: msw: ^0.47.0 supertest: ^6.1.3 zod: ^3.11.6 + zod-to-json-schema: ^3.18.1 languageName: unknown linkType: soft -"@backstage/plugin-permission-react@npm:^0.4.5": - version: 0.4.5 - resolution: "@backstage/plugin-permission-react@npm:0.4.5" +"@backstage/plugin-permission-react@npm:^0.4.6": + version: 0.4.6 + resolution: "@backstage/plugin-permission-react@npm:0.4.6" dependencies: - "@backstage/config": ^1.0.2 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/plugin-permission-common": ^0.6.4 + "@backstage/config": ^1.0.3 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-permission-common": ^0.7.0 cross-fetch: ^3.1.5 react-use: ^17.2.4 swr: ^1.1.2 @@ -6388,7 +6582,7 @@ __metadata: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 - checksum: bfe7827a7e0a4cc91884ff4943bc0810f9fe3bc992f9cb5784687523df5a0ff68c6e2e68db4b6cc2e3bbbd3bc72db5126688c8b9f63d9a3ae402d7353f363456 + checksum: 7c8c619d1c0465ac6f48a5f841127f75fb3689c6b7948777f093b11a7a65ac1308cb30ab46658591e8463ccd8dac7bdbc098c4922b9fa858b307f2113ef2acef languageName: node linkType: hard @@ -6439,6 +6633,7 @@ __metadata: uuid: ^8.2.0 winston: ^3.2.1 yn: ^4.0.0 + zod: ^3.11.6 languageName: unknown linkType: soft @@ -6692,6 +6887,7 @@ __metadata: octokit: ^2.0.0 octokit-plugin-create-pull-request: ^3.10.0 p-limit: ^3.1.0 + prom-client: ^14.0.1 supertest: ^6.1.3 uuid: ^8.2.0 vm2: ^3.9.11 @@ -6743,7 +6939,11 @@ __metadata: "@material-ui/lab": 4.0.0-alpha.57 "@react-hookz/web": ^15.0.0 "@rjsf/core": ^3.2.1 + "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.10" "@rjsf/material-ui": ^3.2.1 + "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.10" + "@rjsf/utils": ^5.0.0-beta.10 + "@rjsf/validator-ajv8": ^5.0.0-beta.10 "@testing-library/jest-dom": ^5.10.1 "@testing-library/react": ^12.1.3 "@testing-library/react-hooks": ^8.0.0 @@ -6864,13 +7064,13 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-search-common@npm:^1.0.1": - version: 1.0.1 - resolution: "@backstage/plugin-search-common@npm:1.0.1" +"@backstage/plugin-search-common@npm:^1.1.0": + version: 1.1.0 + resolution: "@backstage/plugin-search-common@npm:1.1.0" dependencies: - "@backstage/plugin-permission-common": ^0.6.4 + "@backstage/plugin-permission-common": ^0.7.0 "@backstage/types": ^1.0.0 - checksum: 62f9d2d3175f9433f0c4f6cf29a5c3462af91ab95bf01bb6450604993ed2704a6ea1db91e114b601e450f1dddcdd44601cefdd6be1dfdb9d4863a16a23cbba1b + checksum: c14e91b5e180c345d47077e678a5b1b9a86620350171f5cafe799e24a815c07952bf817e2b8759019227c07ea93dcf0608ba321afa7ead68c4281e01f6b3b3de languageName: node linkType: hard @@ -7035,7 +7235,7 @@ __metadata: msw: ^0.47.0 node-fetch: ^2.6.7 supertest: ^6.2.4 - winston: ^3.8.1 + winston: ^3.2.1 yn: ^5.0.0 languageName: unknown linkType: soft @@ -7115,15 +7315,15 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-stack-overflow@npm:^0.1.5": - version: 0.1.5 - resolution: "@backstage/plugin-stack-overflow@npm:0.1.5" +"@backstage/plugin-stack-overflow@npm:^0.1.6": + version: 0.1.6 + resolution: "@backstage/plugin-stack-overflow@npm:0.1.6" dependencies: - "@backstage/config": ^1.0.2 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/plugin-home": ^0.4.25 - "@backstage/plugin-search-common": ^1.0.1 + "@backstage/config": ^1.0.3 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-home": ^0.4.26 + "@backstage/plugin-search-common": ^1.1.0 "@backstage/theme": ^0.2.16 "@material-ui/core": ^4.12.2 "@material-ui/icons": ^4.9.1 @@ -7135,7 +7335,7 @@ __metadata: peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 react: ^16.13.1 || ^17.0.0 - checksum: 0f005664f5c7f75a67c6aece733ec47ca8d256859a9502659ae97a4d4cb57b3feeae1d18a4b8ef04340fe49d4915a2b8d84f9119f923cb6d908a1d275a384a89 + checksum: e7900ed037f34746e4156106d4579f0474f272fa46b5320ece2e5a7fec967aacc8fc90194802369460648a15b4a78103f68745261b666668cde6957b255ba166 languageName: node linkType: hard @@ -7202,6 +7402,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/plugin-tech-insights-common": "workspace:^" "@backstage/plugin-tech-insights-node": "workspace:^" + "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/luxon": ^3.0.0 "@types/semver": ^7.3.8 @@ -7658,7 +7859,7 @@ __metadata: node-fetch: ^2.6.7 p-limit: ^3.1.0 supertest: ^6.1.6 - winston: ^3.7.2 + winston: ^3.2.1 yn: ^5.0.0 languageName: unknown linkType: soft @@ -7717,7 +7918,7 @@ __metadata: luxon: ^3.0.0 msw: ^0.47.0 react-use: ^17.2.4 - recharts: ^1.8.5 + recharts: ^2.0.0 peerDependencies: react: ^16.13.1 || ^17.0.0 languageName: unknown @@ -7765,7 +7966,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/theme@^0.2.16, @backstage/theme@^0.2.16-next.1, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": +"@backstage/theme@^0.2.16, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -7774,12 +7975,20 @@ __metadata: languageName: unknown linkType: soft -"@backstage/types@^1.0.0, @backstage/types@workspace:^, @backstage/types@workspace:packages/types": +"@backstage/types@npm:^1.0.0": + version: 1.0.0 + resolution: "@backstage/types@npm:1.0.0" + checksum: 03a9809a8666a9caef2d14d6a521bfdbcde734e9af734f1918ebd25715c6a9d6ba99094a509b7fc59f19bac2c50bcc99fdd5216e64523f2831ada44859f63b38 + languageName: node + linkType: hard + +"@backstage/types@workspace:^, @backstage/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@backstage/types@workspace:packages/types" dependencies: "@backstage/cli": "workspace:^" "@types/zen-observable": ^0.8.0 + luxon: ^3.0.0 zen-observable: ^0.8.15 languageName: unknown linkType: soft @@ -7819,15 +8028,15 @@ __metadata: languageName: node linkType: hard -"@changesets/apply-release-plan@npm:^6.1.0": - version: 6.1.0 - resolution: "@changesets/apply-release-plan@npm:6.1.0" +"@changesets/apply-release-plan@npm:^6.1.1": + version: 6.1.1 + resolution: "@changesets/apply-release-plan@npm:6.1.1" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/config": ^2.1.1 + "@changesets/config": ^2.2.0 "@changesets/get-version-range-type": ^0.3.2 - "@changesets/git": ^1.4.1 - "@changesets/types": ^5.1.0 + "@changesets/git": ^1.5.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 detect-indent: ^6.0.0 fs-extra: ^7.0.1 @@ -7836,51 +8045,51 @@ __metadata: prettier: ^2.7.1 resolve-from: ^5.0.0 semver: ^5.4.1 - checksum: dbbe5301ba6b3651c788c25e9ae73a765ce8332afb013b417b91004b63be608ee898cbf1916a1e40d9d4ef11bfbe46eefe3e39524f42ef54d8a6eb615fffb292 + checksum: 34da2d52eced00bc5f51c8e8ce16c0e487743219f057da2b3e6c6597bb2d50498f3996c779e4fc29b19d357c9b700d82310d723395ba09350b9a139c9e0e0d23 languageName: node linkType: hard -"@changesets/assemble-release-plan@npm:^5.2.1": - version: 5.2.1 - resolution: "@changesets/assemble-release-plan@npm:5.2.1" +"@changesets/assemble-release-plan@npm:^5.2.2": + version: 5.2.2 + resolution: "@changesets/assemble-release-plan@npm:5.2.2" dependencies: "@babel/runtime": ^7.10.4 "@changesets/errors": ^0.1.4 - "@changesets/get-dependents-graph": ^1.3.3 - "@changesets/types": ^5.1.0 + "@changesets/get-dependents-graph": ^1.3.4 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 semver: ^5.4.1 - checksum: 34e00f9ac98163cd81338962ce5cf27e7a8ecbd60a5040e245976fba87336aa7efdd6c54cc2112db0cda2099fefc3ad37bd24d29307a30e49a515a3279022d74 + checksum: 4f4a2108537ecbfbf9a554e441f4899f9c6d7e2b933deddd186d6f670ea1e52ebbbef3da01bc61b5cb735470536258b94d7bbac1035fe98dc277615d5017c61a languageName: node linkType: hard -"@changesets/changelog-git@npm:^0.1.12": - version: 0.1.12 - resolution: "@changesets/changelog-git@npm:0.1.12" +"@changesets/changelog-git@npm:^0.1.13": + version: 0.1.13 + resolution: "@changesets/changelog-git@npm:0.1.13" dependencies: - "@changesets/types": ^5.1.0 - checksum: 68ae2ccb7e18f4559c954bcc104cd2e311c6daf5a39e9339290c0e1db8db926a1432e54485c593d977083253736147d855e923053d50daaef1041aa486172538 + "@changesets/types": ^5.2.0 + checksum: c0e3b11a0a63794304e064ef01444503a864a1fbfc4c592bd3aec91ba6aa5c24fac91a47f7f0159f449cd6ce99c334290b465d84b1c98b6577937ff55974cc7e languageName: node linkType: hard "@changesets/cli@npm:^2.14.0": - version: 2.24.4 - resolution: "@changesets/cli@npm:2.24.4" + version: 2.25.0 + resolution: "@changesets/cli@npm:2.25.0" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/apply-release-plan": ^6.1.0 - "@changesets/assemble-release-plan": ^5.2.1 - "@changesets/changelog-git": ^0.1.12 - "@changesets/config": ^2.1.1 + "@changesets/apply-release-plan": ^6.1.1 + "@changesets/assemble-release-plan": ^5.2.2 + "@changesets/changelog-git": ^0.1.13 + "@changesets/config": ^2.2.0 "@changesets/errors": ^0.1.4 - "@changesets/get-dependents-graph": ^1.3.3 - "@changesets/get-release-plan": ^3.0.14 - "@changesets/git": ^1.4.1 + "@changesets/get-dependents-graph": ^1.3.4 + "@changesets/get-release-plan": ^3.0.15 + "@changesets/git": ^1.5.0 "@changesets/logger": ^0.0.5 - "@changesets/pre": ^1.0.12 - "@changesets/read": ^0.5.7 - "@changesets/types": ^5.1.0 - "@changesets/write": ^0.2.0 + "@changesets/pre": ^1.0.13 + "@changesets/read": ^0.5.8 + "@changesets/types": ^5.2.0 + "@changesets/write": ^0.2.1 "@manypkg/get-packages": ^1.1.3 "@types/is-ci": ^3.0.0 "@types/semver": ^6.0.0 @@ -7902,22 +8111,22 @@ __metadata: tty-table: ^4.1.5 bin: changeset: bin.js - checksum: 899e0f3b075e12d28f71ed05e385c642ce9d56de06f9f60da346c15004612c8629c4619bcfaca860c9a947cfb3f0df904ea6cc080ff4de5c60c814665771f4ab + checksum: 54439bdfa7ca115964482f6323e9475b5917d68c2e0752a272ed133b863fce51eeba86366d8740a275dc0b891af8e272602da69d04144f40e4bf3531f48fe8fb languageName: node linkType: hard -"@changesets/config@npm:^2.1.1": - version: 2.1.1 - resolution: "@changesets/config@npm:2.1.1" +"@changesets/config@npm:^2.2.0": + version: 2.2.0 + resolution: "@changesets/config@npm:2.2.0" dependencies: "@changesets/errors": ^0.1.4 - "@changesets/get-dependents-graph": ^1.3.3 + "@changesets/get-dependents-graph": ^1.3.4 "@changesets/logger": ^0.0.5 - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 fs-extra: ^7.0.1 micromatch: ^4.0.2 - checksum: 12b47efd06f9ca19f00a170517dad0e2041c2f285eff308c37e955893d24e981addc941ae545e23f9507628674ef2b9321d7423fc495bc17d00ea09066d68fc7 + checksum: 18a6ae52150a7426bcaeb4018f7eb4e330dec69a448b093d604f1cd73d89b689eb791777cee5af57f9e572403ccbf196a572b33dbb702bae4bccfbbbd3be5ffc languageName: node linkType: hard @@ -7930,31 +8139,31 @@ __metadata: languageName: node linkType: hard -"@changesets/get-dependents-graph@npm:^1.3.3": - version: 1.3.3 - resolution: "@changesets/get-dependents-graph@npm:1.3.3" +"@changesets/get-dependents-graph@npm:^1.3.4": + version: 1.3.4 + resolution: "@changesets/get-dependents-graph@npm:1.3.4" dependencies: - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 chalk: ^2.1.0 fs-extra: ^7.0.1 semver: ^5.4.1 - checksum: 0ccde4d7e233fef875447e08219da45eb3cd9b073124b6ce051b35ed3fac89ce2cb185fce41eae1ed2a41aa741cd9f999f5384c5c76086c72c5b5e7609716fd9 + checksum: 584852e17fd102271dea520f851c85130605f232f7f52126d202b0041269af12d5681744bfa7fecf41048e5fd62a71da726be471207832060408d9cfb35ec3fb languageName: node linkType: hard -"@changesets/get-release-plan@npm:^3.0.14": - version: 3.0.14 - resolution: "@changesets/get-release-plan@npm:3.0.14" +"@changesets/get-release-plan@npm:^3.0.15": + version: 3.0.15 + resolution: "@changesets/get-release-plan@npm:3.0.15" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/assemble-release-plan": ^5.2.1 - "@changesets/config": ^2.1.1 - "@changesets/pre": ^1.0.12 - "@changesets/read": ^0.5.7 - "@changesets/types": ^5.1.0 + "@changesets/assemble-release-plan": ^5.2.2 + "@changesets/config": ^2.2.0 + "@changesets/pre": ^1.0.13 + "@changesets/read": ^0.5.8 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 - checksum: 32ea990b58b9c138ec0e335b0ee69e4c4e7ef802adcf28a5b843d25cb1155d68d7621063fbb18ea200bcc20112575df902722792b6428431b8ee30b8bb6b8b77 + checksum: f2d33982dfeabe12e0eba976a3cc68e05bdd834b4dd6283f42464d028852e46f90cb14b84f0b5425813c787494d0490dc12df5d25d63559d07f47f9a7cb87c12 languageName: node linkType: hard @@ -7965,17 +8174,17 @@ __metadata: languageName: node linkType: hard -"@changesets/git@npm:^1.4.1": - version: 1.4.1 - resolution: "@changesets/git@npm:1.4.1" +"@changesets/git@npm:^1.5.0": + version: 1.5.0 + resolution: "@changesets/git@npm:1.5.0" dependencies: "@babel/runtime": ^7.10.4 "@changesets/errors": ^0.1.4 - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 is-subdir: ^1.1.1 spawndamnit: ^2.0.0 - checksum: 9b710d532a1816a2540fe4bc4212e13acb102f39f3b49d23396cad9b3fcae8bfe3f838902a9279a5b0b3e4533ca0965ffb169f0bebeb8b4933a48965679ed991 + checksum: 7208d5bff9c584aa752ca6f647ba5e97356213d00c88cce65c24c6bab1b3837c14f8977c2384a7fecb81e20a0586d4cc1fddb408a38e7dd818ef18377d01ee54 languageName: node linkType: hard @@ -7988,42 +8197,42 @@ __metadata: languageName: node linkType: hard -"@changesets/parse@npm:^0.3.14": - version: 0.3.14 - resolution: "@changesets/parse@npm:0.3.14" +"@changesets/parse@npm:^0.3.15": + version: 0.3.15 + resolution: "@changesets/parse@npm:0.3.15" dependencies: - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 js-yaml: ^3.13.1 - checksum: 4234e93ba3081fc30ec6ed3425cb2f4ac9e69fec0fc90d6d0438d850840c247f7bc9365b78a5295a6084c4cdba1c657c1bc4acd2f666dc0097741f0933111cf7 + checksum: 1e17f494954140d7885f4be76ac7708c19930f08ecf0b58bcee09160ad6e59223da8899df0709a78e5b5fa419f0d60eccbb34bf0c11d564e24f766c4654e3384 languageName: node linkType: hard -"@changesets/pre@npm:^1.0.12": - version: 1.0.12 - resolution: "@changesets/pre@npm:1.0.12" +"@changesets/pre@npm:^1.0.13": + version: 1.0.13 + resolution: "@changesets/pre@npm:1.0.13" dependencies: "@babel/runtime": ^7.10.4 "@changesets/errors": ^0.1.4 - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 "@manypkg/get-packages": ^1.1.3 fs-extra: ^7.0.1 - checksum: 6a81027feaca4727b2809410ec246481a324b4230e0f06b6add52ac2f28f8d7cb9b72cea56b2cfb64a15eb016fb6923bad49c3f2e767ecd2b30b13357a75a0da + checksum: f1cc5721546c66977a2fc62428aae9d62f78a04aefac224ac7014172807afa9d07f6da1e326d0a14b1ff12840b0379a7ec24e7984839deb337cd203b63c24b1d languageName: node linkType: hard -"@changesets/read@npm:^0.5.7": - version: 0.5.7 - resolution: "@changesets/read@npm:0.5.7" +"@changesets/read@npm:^0.5.8": + version: 0.5.8 + resolution: "@changesets/read@npm:0.5.8" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/git": ^1.4.1 + "@changesets/git": ^1.5.0 "@changesets/logger": ^0.0.5 - "@changesets/parse": ^0.3.14 - "@changesets/types": ^5.1.0 + "@changesets/parse": ^0.3.15 + "@changesets/types": ^5.2.0 chalk: ^2.1.0 fs-extra: ^7.0.1 p-filter: ^2.1.0 - checksum: 5c9ea7f4ad1391e971ba7eea4e64fd2c1ad2b00db2884a35ec262fa356fc9c72f813ea1c077c7ce904cd4ce2e97b3599d21116df1c0452f7d240c0af71a34f84 + checksum: cc32c5a3366be58c5b00988940eb6677898814de7ce60a3c4785d064e0df7563627f8dcfa0620f3ef9cea6f328ce538b2ba7d430c35c883f212b71a94026b2d8 languageName: node linkType: hard @@ -8034,23 +8243,23 @@ __metadata: languageName: node linkType: hard -"@changesets/types@npm:^5.1.0": - version: 5.1.0 - resolution: "@changesets/types@npm:5.1.0" - checksum: fa903b53d5cc0ce1bd6054fd6c0053af54f604ade2c6436e735a7a66e4f31f7757ec324ceed3355ccd7f0653174fe8b51e1b3850a0b0d80315d13d92a45185b2 +"@changesets/types@npm:^5.2.0": + version: 5.2.0 + resolution: "@changesets/types@npm:5.2.0" + checksum: 579cf8bd2d3a03f293871976d8641531667527f248dc29310a70928d6400cef5df3d09e75beeb2ccf5d384fa1f294f0a2db243c6ebf53913d1f67e283e826f91 languageName: node linkType: hard -"@changesets/write@npm:^0.2.0": - version: 0.2.0 - resolution: "@changesets/write@npm:0.2.0" +"@changesets/write@npm:^0.2.1": + version: 0.2.1 + resolution: "@changesets/write@npm:0.2.1" dependencies: "@babel/runtime": ^7.10.4 - "@changesets/types": ^5.1.0 + "@changesets/types": ^5.2.0 fs-extra: ^7.0.1 human-id: ^1.0.2 prettier: ^2.7.1 - checksum: 0cc09ddc0669752f6856d3fcc1d609ad7dcd5f464c94501be7b406f2c00013bcec486a422cd09006a2eb0c2bdfe78dd1e44b37202fc7925dea9b855fead8c88b + checksum: 98b4d9c12fe13177860407557979d475361076a596103895440f52b2724f7004d6c98af39105fda0eaa52ceca8c0dc0ec9c8ab10eec7a0cb9bf83301f2ca48b3 languageName: node linkType: hard @@ -8084,8 +8293,8 @@ __metadata: linkType: hard "@codemirror/language@npm:^6.0.0": - version: 6.2.1 - resolution: "@codemirror/language@npm:6.2.1" + version: 6.3.0 + resolution: "@codemirror/language@npm:6.3.0" dependencies: "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 @@ -8093,16 +8302,16 @@ __metadata: "@lezer/highlight": ^1.0.0 "@lezer/lr": ^1.0.0 style-mod: ^4.0.0 - checksum: e483eacf3346ef3d8db5c4c730e059790e6bf717b8148d79fa000082841f889f3a7867cc5df36f31503fcd7b2ca935b60c3e710c5637985f7ef0613728678fa3 + checksum: 996d6aad1a4cfb455f3459951d5ecfb67ff5240cc29397b3b801e569fcda24f87364aa7e42b63e85ecf62af80eaecd01258303dbf5b5f0b1eba31daa9a5bd258 languageName: node linkType: hard "@codemirror/legacy-modes@npm:^6.1.0": - version: 6.1.0 - resolution: "@codemirror/legacy-modes@npm:6.1.0" + version: 6.2.0 + resolution: "@codemirror/legacy-modes@npm:6.2.0" dependencies: "@codemirror/language": ^6.0.0 - checksum: bc0e3b771360de435735e0203c47feef8336749738da364c744da50e838d1ad5b36402da00131323238b579f657868210659029d44a1935baf3de8cb964903a6 + checksum: c4449ad4e9b80fad982956ace705af5e21cd8c8bb248930bcb394ec019babf1a7b23e1e2894428b67d28a0c5bee99592320673690d19039392ddbc77075ef132 languageName: node linkType: hard @@ -8148,13 +8357,13 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0": - version: 6.2.5 - resolution: "@codemirror/view@npm:6.2.5" + version: 6.4.0 + resolution: "@codemirror/view@npm:6.4.0" dependencies: "@codemirror/state": ^6.0.0 style-mod: ^4.0.0 w3c-keyname: ^2.2.4 - checksum: 0db9c3475790531912a86d5bb60f895ea11d19786a4c877703a602c840d45c2fd92ad6aea6b8648c7dccf88b7250ee1cbc5ca1c1797e5410440e7eb17a970a62 + checksum: 57ed7d9d51907f1ea549a2a158872fb7affa6cdff72e29e214f8437b7142ea2a2431ac3c780cbc0187dfff7cb5fd1055a45a887ea9b8c2a09d8430c60ebe9083 languageName: node linkType: hard @@ -8443,23 +8652,23 @@ __metadata: linkType: hard "@google-cloud/container@npm:^4.0.0": - version: 4.1.3 - resolution: "@google-cloud/container@npm:4.1.3" + version: 4.3.0 + resolution: "@google-cloud/container@npm:4.3.0" dependencies: google-gax: ^3.3.0 - checksum: d9554ed344e065ec16b9d281eeecd2796bf5c90149358e724596c6e2bc97c6b9ea16a9dbcf4805e29003e4bcaf3aaa85c70609b7b9b1db0fbae6c9018e55b6d8 + checksum: cc1dc2ca7f6b47bac0d374013e78b05372f25dce7a3f84d799899af274d26fa22f23d2554a66ca1db528c3bbbec1cfbac86dfa9fc72cc78f175bcb737900b3ed languageName: node linkType: hard "@google-cloud/firestore@npm:^6.0.0": - version: 6.3.0 - resolution: "@google-cloud/firestore@npm:6.3.0" + version: 6.4.1 + resolution: "@google-cloud/firestore@npm:6.4.1" dependencies: fast-deep-equal: ^3.1.1 functional-red-black-tree: ^1.0.1 google-gax: ^3.5.1 protobufjs: ^7.0.0 - checksum: 50f52dc06746fe3af1e9abb1063244015f968b71387104f65bd752ce6522406fe987291791c40fd40e84a11033c993e7847e5b8619173b7202c70382e55e853b + checksum: 7bd79d7444cefd8ffe8f768c28dbd1f0b32628ff5b611b4bdaa9b415428436f5368a8e258a002948d084381762b64501a574828c51c5cea7c054cc604f93b6d6 languageName: node linkType: hard @@ -8488,14 +8697,13 @@ __metadata: linkType: hard "@google-cloud/storage@npm:^6.0.0": - version: 6.5.2 - resolution: "@google-cloud/storage@npm:6.5.2" + version: 6.5.3 + resolution: "@google-cloud/storage@npm:6.5.3" dependencies: "@google-cloud/paginator": ^3.0.7 "@google-cloud/projectify": ^3.0.0 "@google-cloud/promisify": ^3.0.0 abort-controller: ^3.0.0 - arrify: ^2.0.0 async-retry: ^1.3.3 compressible: ^2.0.12 duplexify: ^4.0.0 @@ -8509,7 +8717,7 @@ __metadata: retry-request: ^5.0.0 teeny-request: ^8.0.0 uuid: ^8.0.0 - checksum: 31e8abea01e5160434a23c11c7219c83469c97a0c3bed9dc29d5ff1441ba1f42aa75a99df6e0b95db1501cf4b7f4c896c79b13bb597a35ddd2924408b584f802 + checksum: 9b88a5bff4e8401dbd18223fe570882df632cbb0e515e7f3eca33dbea16568168b2c0397b02ad8030e6d114b6d73109283d17a49762f1833f077424bf90a676d languageName: node linkType: hard @@ -8547,8 +8755,8 @@ __metadata: linkType: hard "@graphql-codegen/cli@npm:^2.3.1": - version: 2.13.5 - resolution: "@graphql-codegen/cli@npm:2.13.5" + version: 2.13.7 + resolution: "@graphql-codegen/cli@npm:2.13.7" dependencies: "@babel/generator": ^7.18.13 "@babel/template": ^7.18.10 @@ -8580,6 +8788,7 @@ __metadata: listr2: ^4.0.5 log-symbols: ^4.0.0 mkdirp: ^1.0.4 + shell-quote: ^1.7.3 string-env-interpolation: ^1.0.1 ts-log: ^2.2.3 tslib: ^2.4.0 @@ -8592,7 +8801,7 @@ __metadata: graphql-code-generator: cjs/bin.js graphql-codegen: cjs/bin.js graphql-codegen-esm: esm/bin.js - checksum: aa80a4440657bad9ebc3dce6c7b22f843bff97b78c4b37808b6605bf7e2f3d7a227afa66d641284ffb914554147d07aab61ca27eec4551308a3909360191500b + checksum: c025e311c072c1d992451f27625c863eef1236e5f01e2aeb6f00e67a60e99c7812a13481481a8fc6ea2967fffd77433d582f10365244d677608fdc7aa13bbe29 languageName: node linkType: hard @@ -8611,18 +8820,18 @@ __metadata: linkType: hard "@graphql-codegen/graphql-modules-preset@npm:^2.3.2": - version: 2.5.1 - resolution: "@graphql-codegen/graphql-modules-preset@npm:2.5.1" + version: 2.5.4 + resolution: "@graphql-codegen/graphql-modules-preset@npm:2.5.4" dependencies: "@graphql-codegen/plugin-helpers": ^2.6.2 - "@graphql-codegen/visitor-plugin-common": 2.12.1 + "@graphql-codegen/visitor-plugin-common": 2.13.0 "@graphql-tools/utils": ^8.8.0 change-case-all: 1.0.14 parse-filepath: ^1.0.2 tslib: ~2.4.0 peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 185bd63081cdeba9ee332cd22fb1f640853f0f7bf60a70d4af864047465641b7ed79287413840202e317726ffad5bdd61382a4578ba88efd0b46a2d9b3acd32d + checksum: d3e9b90c475c1a093149038b0dea448ebbb25bf9cf853d1947427bed741d739f140daca8f3861da63bfee6f6958c25f1b1081ce4365c7db1efe6527bb9b9c1dc languageName: node linkType: hard @@ -8656,39 +8865,54 @@ __metadata: linkType: hard "@graphql-codegen/typescript-resolvers@npm:^2.4.3": - version: 2.7.3 - resolution: "@graphql-codegen/typescript-resolvers@npm:2.7.3" + version: 2.7.5 + resolution: "@graphql-codegen/typescript-resolvers@npm:2.7.5" dependencies: "@graphql-codegen/plugin-helpers": ^2.6.2 - "@graphql-codegen/typescript": ^2.7.3 - "@graphql-codegen/visitor-plugin-common": 2.12.1 + "@graphql-codegen/typescript": ^2.7.5 + "@graphql-codegen/visitor-plugin-common": 2.13.0 "@graphql-tools/utils": ^8.8.0 auto-bind: ~4.0.0 tslib: ~2.4.0 peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 28672b86e62ad7d0bcf244eebce7a4ee3b4dcd0055570d872a135167880030f27434c512e6bcd9d9144e5a6e4cd757268677dd61c7fb43aea2981290a5de9b2e + checksum: 7384bbb42bdf43157b6133812c2e84bb162ab5b52add05bed86a4f3c149bf3ab1f178bb5a7d51c661ba354296b2fa9e4983ee42b373c3815fdc6ee391a9e5307 languageName: node linkType: hard -"@graphql-codegen/typescript@npm:^2.4.2, @graphql-codegen/typescript@npm:^2.7.3": - version: 2.7.3 - resolution: "@graphql-codegen/typescript@npm:2.7.3" +"@graphql-codegen/typescript@npm:^2.4.2": + version: 2.8.0 + resolution: "@graphql-codegen/typescript@npm:2.8.0" dependencies: "@graphql-codegen/plugin-helpers": ^2.6.2 "@graphql-codegen/schema-ast": ^2.5.1 - "@graphql-codegen/visitor-plugin-common": 2.12.1 + "@graphql-codegen/visitor-plugin-common": 2.13.0 auto-bind: ~4.0.0 tslib: ~2.4.0 peerDependencies: graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: fa08ad7a379cfc05cf80d71509d1f8154919b8a0e89e8bf2c95a41856e0b42e3788b9225faf084a94cf900dab8893e42aa296bdb4725d5d20ce00c9e2e0bd707 + checksum: 45550613384c930d046f1ca8bfd73c25e1219fc45315b1885017e08577a517547d5af86908162f61eee0670689df5c926b5e394b318d5c3d94ee5eef5ac1bc52 languageName: node linkType: hard -"@graphql-codegen/visitor-plugin-common@npm:2.12.1": - version: 2.12.1 - resolution: "@graphql-codegen/visitor-plugin-common@npm:2.12.1" +"@graphql-codegen/typescript@npm:^2.7.5": + version: 2.7.5 + resolution: "@graphql-codegen/typescript@npm:2.7.5" + dependencies: + "@graphql-codegen/plugin-helpers": ^2.6.2 + "@graphql-codegen/schema-ast": ^2.5.1 + "@graphql-codegen/visitor-plugin-common": 2.13.0 + auto-bind: ~4.0.0 + tslib: ~2.4.0 + peerDependencies: + graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + checksum: 0eb51d2bb826284cfed29fdaee0fc57694932627f0146394711d3f012377bccb19592270f6c04f775ec06ec87e1a05a0c85064ca186ea0e50a306ec058674fad + languageName: node + linkType: hard + +"@graphql-codegen/visitor-plugin-common@npm:2.13.0": + version: 2.13.0 + resolution: "@graphql-codegen/visitor-plugin-common@npm:2.13.0" dependencies: "@graphql-codegen/plugin-helpers": ^2.6.2 "@graphql-tools/optimize": ^1.3.0 @@ -8702,7 +8926,7 @@ __metadata: tslib: ~2.4.0 peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 846d1c698b12863a25d36da761ff7c1904405e6a8ead450f95658db407bc007582eb9328f4ba57fe33129ff3e6ebce2b55347125c0bf980370480ffd47299d2e + checksum: 1d7491d626059529faca2eda5466aa8b05bf2cc9a768175043461d0ec4793b4cbda2fb762ef44e6c8d094f951960569f2adf177af74156f4a74f33c6d5ae77bf languageName: node linkType: hard @@ -10361,11 +10585,11 @@ __metadata: linkType: hard "@keyv/redis@npm:^2.2.3": - version: 2.3.8 - resolution: "@keyv/redis@npm:2.3.8" + version: 2.5.2 + resolution: "@keyv/redis@npm:2.5.2" dependencies: - ioredis: ^5.1.0 - checksum: 7927d870094161db5bf90677008f6a584235162a0981f875a8616d244d2ba1da525858f656f434aea2a923fb39c5ce0cf88fd9720061e5f025c9db59146e6f48 + ioredis: ^5.2.3 + checksum: 2439e5097c6d4bf14e316b7a19d532287d43c5860f0614569f125a3c0a0a8c322610ed26b86b941b9aa9243ebf1e265c1d3f99a43da42d2150d02fc8fd2d94dd languageName: node linkType: hard @@ -11170,20 +11394,20 @@ __metadata: linkType: hard "@octokit/auth-app@npm:^4.0.0": - version: 4.0.6 - resolution: "@octokit/auth-app@npm:4.0.6" + version: 4.0.7 + resolution: "@octokit/auth-app@npm:4.0.7" dependencies: "@octokit/auth-oauth-app": ^5.0.0 "@octokit/auth-oauth-user": ^2.0.0 "@octokit/request": ^6.0.0 "@octokit/request-error": ^3.0.0 - "@octokit/types": ^7.0.0 + "@octokit/types": ^8.0.0 "@types/lru-cache": ^5.1.0 deprecation: ^2.3.1 lru-cache: ^6.0.0 universal-github-app-jwt: ^1.0.1 universal-user-agent: ^6.0.0 - checksum: 342fece6db4470ee489e710af8aa14ffd3a89e666815a15fadbabe3a382932a714d5bece24375f0ca39f3310f9d10bd28bc348210282cbc6701d27dd9b2ff7ed + checksum: 880de7341f47c5a48822612f50ae453fb0990a188b29afdb2f80a1f7e703c88e068d05a4edc1aa9a8204c730b6f70cf2772fbd5da032fee4986a5266ebdda4c7 languageName: node linkType: hard @@ -11438,6 +11662,13 @@ __metadata: languageName: node linkType: hard +"@octokit/openapi-types@npm:^14.0.0": + version: 14.0.0 + resolution: "@octokit/openapi-types@npm:14.0.0" + checksum: 0a1f8f3be998cd82c5a640e9166d43fd183b33d5d36f5e1a9b81608e94d0da87c01ec46c9988f69cd26585d4e2ffc4d3ec99ee4f75e5fe997fc86dad0aa8293c + languageName: node + linkType: hard + "@octokit/openapi-types@npm:^7.3.2": version: 7.4.0 resolution: "@octokit/openapi-types@npm:7.4.0" @@ -11579,16 +11810,16 @@ __metadata: linkType: hard "@octokit/request@npm:^6.0.0": - version: 6.2.1 - resolution: "@octokit/request@npm:6.2.1" + version: 6.2.2 + resolution: "@octokit/request@npm:6.2.2" dependencies: "@octokit/endpoint": ^7.0.0 "@octokit/request-error": ^3.0.0 - "@octokit/types": ^7.0.0 + "@octokit/types": ^8.0.0 is-plain-object: ^5.0.0 node-fetch: ^2.6.7 universal-user-agent: ^6.0.0 - checksum: f0a3e878de8c2e6930da5af835d9a3750800eff9ba66af02400dc75238475a9b9c2c5473047792c0f37c2c371095a36485c0729c419873bdccb6058bb8637685 + checksum: adbeb38807c60b53d32d9b69be0c1f861c26698bc6f5f3f7e05d26972290dc4867827dd333bdd801818c347e5723efd049a2b9848c6c8bf74a2032968dede0ff languageName: node linkType: hard @@ -11661,6 +11892,15 @@ __metadata: languageName: node linkType: hard +"@octokit/types@npm:^8.0.0": + version: 8.0.0 + resolution: "@octokit/types@npm:8.0.0" + dependencies: + "@octokit/openapi-types": ^14.0.0 + checksum: 1a0197b2c4c522ac90f145e02b3f8cb048a47f71c2c6bdbf021a03db7dd30ca92a899c0186acb401337f218efe44e60d33cc1cc68715b622bb75bc1a4e79515d + languageName: node + linkType: hard + "@octokit/webhooks-methods@npm:^3.0.0": version: 3.0.0 resolution: "@octokit/webhooks-methods@npm:3.0.0" @@ -11668,22 +11908,22 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks-types@npm:6.3.6": - version: 6.3.6 - resolution: "@octokit/webhooks-types@npm:6.3.6" - checksum: 974db34258cbdb0d02f3bed202595822c1799804cbd46e3375571537e0ba879350f30a3e27b156480a0be4eaa924bbb353a578ae86cb58e495c524478f052f6b +"@octokit/webhooks-types@npm:6.6.0": + version: 6.6.0 + resolution: "@octokit/webhooks-types@npm:6.6.0" + checksum: 27d5ebb43bbcdaeacb34c1f8d5c5a8c1dfab8ca3b7173e3016c18365c5c832f3f53a1611fbc86371a8159ef2bc030b7c22c2901ec819134a71855d66e52762f9 languageName: node linkType: hard "@octokit/webhooks@npm:^10.0.0": - version: 10.1.5 - resolution: "@octokit/webhooks@npm:10.1.5" + version: 10.3.1 + resolution: "@octokit/webhooks@npm:10.3.1" dependencies: "@octokit/request-error": ^3.0.0 "@octokit/webhooks-methods": ^3.0.0 - "@octokit/webhooks-types": 6.3.6 + "@octokit/webhooks-types": 6.6.0 aggregate-error: ^3.1.0 - checksum: ea34d0e9431550d94cc2421b7afee56515745d8df3a61592cfeba3087ad7541c5b3bd64dba77fe0da554dc34e4e5438061cc94b80ba7e57ee65b74024929792e + checksum: 2601528d67d9de25fa8d6b9c7b88a75de7887862f0f36569ce15592a35a02281f7f35870fcc3ff2ca2b9933905c676be3ada747efb88a1084cf3c2cdab1518b6 languageName: node linkType: hard @@ -11749,16 +11989,16 @@ __metadata: linkType: hard "@oriflame/backstage-plugin-score-card@npm:^0.5.1": - version: 0.5.3 - resolution: "@oriflame/backstage-plugin-score-card@npm:0.5.3" + version: 0.5.4 + resolution: "@oriflame/backstage-plugin-score-card@npm:0.5.4" dependencies: - "@backstage/catalog-model": ^1.1.0-next.2 - "@backstage/config": ^1.0.1 - "@backstage/core-components": ^0.11.0 - "@backstage/core-plugin-api": ^1.0.3 - "@backstage/plugin-catalog-common": ^1.0.4-next.0 - "@backstage/plugin-catalog-react": ^1.1.2-next.2 - "@backstage/theme": ^0.2.16-next.1 + "@backstage/catalog-model": ^1.1.2 + "@backstage/config": ^1.0.3 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-common": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 + "@backstage/theme": ^0.2.16 "@backstage/types": ^1.0.0 "@backstage/version-bridge": ^1.0.1 "@material-ui/core": ^4.12.2 @@ -11768,7 +12008,7 @@ __metadata: peerDependencies: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 - checksum: 2f567d8838ecd938215899ce92e5569438bad8cdc521a938864a565497d4133f41f3710516abf3e375aaba881808de68ac41b249672943bda76fa54aa0352b06 + checksum: 1c9e08ec4bac5487941d2823ccd923c474ec18a99d62b97047d4bd0cdd1c33660339cfba1085dc8e756675a3f03cfff9e33cb395afa8fb74cacd11ab1251547c languageName: node linkType: hard @@ -11806,12 +12046,12 @@ __metadata: linkType: hard "@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.7": - version: 0.5.7 - resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.7" + version: 0.5.8 + resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.8" dependencies: ansi-html-community: ^0.0.8 common-path-prefix: ^3.0.0 - core-js-pure: ^3.8.1 + core-js-pure: ^3.23.3 error-stack-parser: ^2.0.6 find-up: ^5.0.0 html-entities: ^2.1.0 @@ -11822,7 +12062,7 @@ __metadata: "@types/webpack": 4.x || 5.x react-refresh: ">=0.10.0 <1.0.0" sockjs-client: ^1.4.0 - type-fest: ">=0.17.0 <3.0.0" + type-fest: ">=0.17.0 <4.0.0" webpack: ">=4.43.0 <6.0.0" webpack-dev-server: 3.x || 4.x webpack-hot-middleware: 2.x @@ -11840,7 +12080,7 @@ __metadata: optional: true webpack-plugin-serve: optional: true - checksum: 3490649181878cc8808fb91f3870ef095e5a1fb9647b3ac83740df07379c9d1cf540f24bf2b09d5f26a3a8c805b2c6b9c5be7192bdb9317d0ffffa67426e9f66 + checksum: 48d8b2813dfba7d482e58a2b0161b79e3a5d608603f1a3c34d709ecc2e6e08f8b14f79934c57849d06f153eb327f18e3d8e1539f978e40ca91539c342f27b8ae languageName: node linkType: hard @@ -11940,6 +12180,21 @@ __metadata: languageName: node linkType: hard +"@rjsf/core-v5@npm:@rjsf/core@^5.0.0-beta.10": + version: 5.0.0-beta.10 + resolution: "@rjsf/core@npm:5.0.0-beta.10" + dependencies: + lodash: ^4.17.15 + lodash-es: ^4.17.15 + nanoid: ^3.3.4 + prop-types: ^15.7.2 + peerDependencies: + "@rjsf/utils": ^5.0.0-beta.1 + react: ^16.14.0 || >=17 + checksum: c3e8852c8fedc6ae23da610033e0bded1bd198cebf7f54a5cdcf3b42d22a3b3864da9c735300cadc7d756f26055a7e62408d51a5ebdb906be9b1bf85bf3712ed + languageName: node + linkType: hard + "@rjsf/core@npm:^3.2.1": version: 3.2.1 resolution: "@rjsf/core@npm:3.2.1" @@ -11959,6 +12214,19 @@ __metadata: languageName: node linkType: hard +"@rjsf/material-ui-v5@npm:@rjsf/material-ui@^5.0.0-beta.10": + version: 5.0.0-beta.10 + resolution: "@rjsf/material-ui@npm:5.0.0-beta.10" + peerDependencies: + "@material-ui/core": ^4.12.3 + "@material-ui/icons": ^4.11.2 + "@rjsf/core": ^5.0.0-beta.1 + "@rjsf/utils": ^5.0.0-beta.1 + react: ^16.14.0 || >=17 + checksum: c6e602fc331ec37524837123f42e605389c8ea1a9207ed3e9052de380b91fb90365aa962b0020197f0fb2bcbd87baac34bef7dbbb31ea4b57b6587d0fcd81a09 + languageName: node + linkType: hard + "@rjsf/material-ui@npm:^3.2.1": version: 3.2.1 resolution: "@rjsf/material-ui@npm:3.2.1" @@ -11971,14 +12239,43 @@ __metadata: languageName: node linkType: hard -"@roadiehq/backstage-plugin-buildkite@npm:^2.0.8": - version: 2.0.9 - resolution: "@roadiehq/backstage-plugin-buildkite@npm:2.0.9" +"@rjsf/utils@npm:^5.0.0-beta.10": + version: 5.0.0-beta.11 + resolution: "@rjsf/utils@npm:5.0.0-beta.11" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/plugin-catalog-react": ^1.1.4 + json-schema-merge-allof: ^0.8.1 + jsonpointer: ^5.0.1 + lodash: ^4.17.15 + lodash-es: ^4.17.15 + react-is: ^18.2.0 + peerDependencies: + react: ^16.14.0 || >=17 + checksum: 4e77616023b5cf193e1631a4d5fb8d54122d9f48e30ce2fcb1b22f057e2dcf37aae174f25f1afddb664b0114e461f9d738fa282995b07e9e3eb30f7469d77145 + languageName: node + linkType: hard + +"@rjsf/validator-ajv8@npm:^5.0.0-beta.10": + version: 5.0.0-beta.11 + resolution: "@rjsf/validator-ajv8@npm:5.0.0-beta.11" + dependencies: + ajv: ^8.11.0 + ajv-formats: ^2.1.1 + lodash: ^4.17.15 + lodash-es: ^4.17.15 + peerDependencies: + "@rjsf/utils": ^5.0.0-beta.1 + checksum: e9aec2e77c69ee55fd35cc82b2c3fe0c78cf021145500bc86f7e0a5bae794163dfae198df0543ce635e22cd4235880dcbb7c73487ae0baae6b03e1bdea2955c4 + languageName: node + linkType: hard + +"@roadiehq/backstage-plugin-buildkite@npm:^2.0.8": + version: 2.1.0 + resolution: "@roadiehq/backstage-plugin-buildkite@npm:2.1.0" + dependencies: + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 "@backstage/theme": ^0.2.16 "@material-ui/core": ^4.12.1 "@material-ui/icons": ^4.11.2 @@ -11991,19 +12288,19 @@ __metadata: react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 45a7f04969be1896919709311e38218f642d754cf0ae32c608b87d8965147e048d1537854278d97b5bcd777f84861bd65386ffcb5a8831d004cbdc2f5f16dbed + checksum: 0322733ccc62dfd5ec23aabea9d3d82b9a8c49ee8d9668f1d95a67bbb1a6b5d863b0b4148bbeb479bfc59f2ee42d566b298870fdaecea6d8ebee864f0a257afc languageName: node linkType: hard "@roadiehq/backstage-plugin-github-insights@npm:^2.0.5": - version: 2.0.6 - resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.0.6" + version: 2.1.0 + resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.1.0" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/integration-react": ^1.1.4 - "@backstage/plugin-catalog-react": ^1.1.4 + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/integration-react": ^1.1.5 + "@backstage/plugin-catalog-react": ^1.2.0 "@backstage/theme": ^0.2.16 "@date-io/core": 2.10.7 "@material-ui/core": ^4.11.0 @@ -12019,19 +12316,19 @@ __metadata: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 - checksum: ff9701f4e904c44c1031616f59837f9a3bca1448f846791e6b6c2b1eb35ae78fbfc2e7161d233a620d227fc076ba71663af6380eaede9e8cda8930fa008d92ec + checksum: 33ba1f1cf2370bd0af8cd3b9ed117f432de13be7b7a17dc23d427195826a66f696d0dcf916e6105c02b595d6c1eb151e4d280c8f248f75e8cfff05e8480c7cd8 languageName: node linkType: hard "@roadiehq/backstage-plugin-github-pull-requests@npm:^2.2.7": - version: 2.2.8 - resolution: "@roadiehq/backstage-plugin-github-pull-requests@npm:2.2.8" + version: 2.3.0 + resolution: "@roadiehq/backstage-plugin-github-pull-requests@npm:2.3.0" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/plugin-catalog-react": ^1.1.4 - "@backstage/plugin-home": ^0.4.25 + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 + "@backstage/plugin-home": ^0.4.26 "@material-ui/core": ^4.11.0 "@material-ui/icons": ^4.9.1 "@material-ui/lab": ^4.0.0-alpha.60 @@ -12048,18 +12345,18 @@ __metadata: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 - checksum: d334641a6eb1a464a930ef81ff3fd418d7ac96e15ff2aa9c9542d724dceac394d505335b53c77f4ef859791c347a8a70964e36e1e9b094ab289074e2c5b70bcf + checksum: bafb8645333b8477e4f87dff2c7e5bdd4008dc808577e7e05ff56e3b68ad9e5ff8d90cc746b46bc96267faefe6ad4082b9a15ae84cd02edaf5a15cd126aa6733 languageName: node linkType: hard "@roadiehq/backstage-plugin-travis-ci@npm:^2.0.5": - version: 2.0.6 - resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.0.6" + version: 2.1.0 + resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.1.0" dependencies: - "@backstage/catalog-model": ^1.1.1 - "@backstage/core-components": ^0.11.1 - "@backstage/core-plugin-api": ^1.0.6 - "@backstage/plugin-catalog-react": ^1.1.4 + "@backstage/catalog-model": ^1.1.2 + "@backstage/core-components": ^0.11.2 + "@backstage/core-plugin-api": ^1.0.7 + "@backstage/plugin-catalog-react": ^1.2.0 "@backstage/theme": ^0.2.16 "@material-ui/core": ^4.11.3 "@material-ui/icons": ^4.11.2 @@ -12074,7 +12371,7 @@ __metadata: react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: dfa6645c58640eabd60c5ea520c5e4cf9bba1001d40342bde87ad583b129a4a0c1f9ae1497aa52d3e975a76352251f716cbab927b29ae0cb63d3800129367234 + checksum: 99bd8d242bd954f9fd3c75910353d6cce6672ba605a819828a336316c0c7fd0d2f36bfdf7a82b702e284b8f636c06f50b93ae53af6afc3b12e8f766e7fe2ce9c languageName: node linkType: hard @@ -12352,147 +12649,149 @@ __metadata: languageName: node linkType: hard -"@svgr/babel-plugin-add-jsx-attribute@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.3.1" +"@svgr/babel-plugin-add-jsx-attribute@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-add-jsx-attribute@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3a04515743af5f67c3c38cf414f225cb4c266db29fbf37f4bd970be0ab5b6a2c18e9e8c7de3303a70168909106077860b0fdfb9ee4de9c50d994181b4850e615 + checksum: f65ca26905240b685929a7766411618700bda233673cebd74eb9a8da45af8ce8e0536074a178b37762cd23db8868db494e15067e74b2d73e377b2d247895d054 languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-attribute@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:6.3.1" +"@svgr/babel-plugin-remove-jsx-attribute@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-remove-jsx-attribute@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: ea78848a1d987a30320f84263399769d80064a593cf8af41bb5d4e1699869f9395d3ed18c7d35a06c85d4c46f93df3a9864981d6844296c7a26d19b6bfc39098 + checksum: 7a4dfc1345f5855b010684e9c5301731842bf91d72b82ce5cc4c82c80b94de1036e447a8a00fb306a6dd575cb4c640d8ce3cfee6607ddbb804796a77284c7f22 languageName: node linkType: hard -"@svgr/babel-plugin-remove-jsx-empty-expression@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:6.3.1" +"@svgr/babel-plugin-remove-jsx-empty-expression@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-remove-jsx-empty-expression@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 3975ee4ca649fde5acba30748f7766c1362b7b39b54d6164b8f27a13cee0b0f2b2cf05e8eda476a4c833be42697a1e0b47b0c8fae8a66563ba23ac9537fdd502 + checksum: 3e173f720d530f9f71f8506f3eb78583eec3d87d66e385efe1ef3b3ebfc4e3680ec30f36414726de6a163e99ca69f54886022967e49476dea522267e1986936e languageName: node linkType: hard -"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.3.1" +"@svgr/babel-plugin-replace-jsx-attribute-value@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-replace-jsx-attribute-value@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 8a65eb8aa99e3c3e4710aff34d20099a4f2a610d79a5ef705ce4050ff28a25c1f22d813c5021a6c9399725559aba28580674f68b4b5a202028754541e3243453 + checksum: e8e77e4026f2e2f910a3495be8bd283f413865449b6e2f639318d76edc05b373e18d86e1210c808038a5477ae273858d3b313b2a7df8e6929450ed902c1441bc languageName: node linkType: hard -"@svgr/babel-plugin-svg-dynamic-title@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.3.1" +"@svgr/babel-plugin-svg-dynamic-title@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-svg-dynamic-title@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 026f440d2e609532b1a40434dbd97cae54d0ed9090a6f4069d75523611f6d45ac9983a5c69c10cfd4a6ab76bc854c529c99c327e1a11fd8e65b6f59a930181b9 + checksum: 55f36f6e3ef986f2d0ba4cd9e2ebf6b17d68c5c2cf98821abc0dbd551d4fd7d92cf3cda83a91898d988ad7118a9768042ac5afe534ad594bdac024fe0009bae1 languageName: node linkType: hard -"@svgr/babel-plugin-svg-em-dimensions@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.3.1" +"@svgr/babel-plugin-svg-em-dimensions@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-svg-em-dimensions@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 02aa7fa0afd6def11af7f00401918926626faba861f9869b7359d532d524dcf5062810728bf5e8117275dd4c340dc34a24d55c8c705c7a6d678988db8619428b + checksum: af6508c042a7d256081c09520e79e2d3278ecf361a74707dcc1bc61713845ec7fd6eeb52bbc3a2e114ecbbb1df49b16674caf8b97345570a6c5f0a631118cb5e languageName: node linkType: hard -"@svgr/babel-plugin-transform-react-native-svg@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.3.1" +"@svgr/babel-plugin-transform-react-native-svg@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-transform-react-native-svg@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 2cbe20f7016eab8de3515c2bf9887a6399e20d8078614b1316952794ec03c331ea0127c689a258c115b5ca29c279fafef972238c8b491841c49a86b84f408088 + checksum: 0e7f1d85a25ef0c49b2bfacdc9ae80520959a0925304e030edc739684c75d41a7d4173ac4a1cd6ec8dee8bc618e7465da49ae59dd5638b87e75dccee05498f4c languageName: node linkType: hard -"@svgr/babel-plugin-transform-svg-component@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.3.1" +"@svgr/babel-plugin-transform-svg-component@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-plugin-transform-svg-component@npm:6.5.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 76113730f5cbcc58d42e2254168db98bc40201cd7e90d58cd3137f332fd8328ae113ce64a59c2a60e9ca92730030eb7e4ab8476fdbc31cf9ec0cc8221a7ffb96 + checksum: 8613ef673b7e881d661057188729419b9a9d0b3802247954283293698a9910d76414b4fe106b5255e06fb2329c41f9da147ac5e153149b3b82024e06ab87a2b3 languageName: node linkType: hard -"@svgr/babel-preset@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/babel-preset@npm:6.3.1" +"@svgr/babel-preset@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/babel-preset@npm:6.5.0" dependencies: - "@svgr/babel-plugin-add-jsx-attribute": ^6.3.1 - "@svgr/babel-plugin-remove-jsx-attribute": ^6.3.1 - "@svgr/babel-plugin-remove-jsx-empty-expression": ^6.3.1 - "@svgr/babel-plugin-replace-jsx-attribute-value": ^6.3.1 - "@svgr/babel-plugin-svg-dynamic-title": ^6.3.1 - "@svgr/babel-plugin-svg-em-dimensions": ^6.3.1 - "@svgr/babel-plugin-transform-react-native-svg": ^6.3.1 - "@svgr/babel-plugin-transform-svg-component": ^6.3.1 + "@svgr/babel-plugin-add-jsx-attribute": ^6.5.0 + "@svgr/babel-plugin-remove-jsx-attribute": ^6.5.0 + "@svgr/babel-plugin-remove-jsx-empty-expression": ^6.5.0 + "@svgr/babel-plugin-replace-jsx-attribute-value": ^6.5.0 + "@svgr/babel-plugin-svg-dynamic-title": ^6.5.0 + "@svgr/babel-plugin-svg-em-dimensions": ^6.5.0 + "@svgr/babel-plugin-transform-react-native-svg": ^6.5.0 + "@svgr/babel-plugin-transform-svg-component": ^6.5.0 peerDependencies: "@babel/core": ^7.0.0-0 - checksum: c9cdb0889d63d8fa178c90b016e36515e6803ba5c96e980f0233798f63cac29a1e871effa2c17a40e3eaffdb150ad8e98676cc14c97dd8f978f67541c594a05f + checksum: 987f6eafebc347b061bfab7d15f87b48601eecdf7cde9ff0e4e99c44acad3bb126e554996b0c48f1ccf24d5e6785226b1662850bb4e2182927b2ded24f226ae8 languageName: node linkType: hard -"@svgr/core@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/core@npm:6.3.1" +"@svgr/core@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/core@npm:6.5.0" dependencies: - "@svgr/plugin-jsx": ^6.3.1 + "@babel/core": ^7.18.5 + "@svgr/babel-preset": ^6.5.0 + "@svgr/plugin-jsx": ^6.5.0 camelcase: ^6.2.0 cosmiconfig: ^7.0.1 - checksum: 753b043f48d5bfef8aa02976d5ed5a885c60e74e5ac0cdf2b00045e7867443ae722a57160ae46e7a2db563d1ecbe1fda4585a21e84ac1337b6b94113f25b9925 + checksum: 235747a1d1c0e8918aa16da7e44c9dd2024a9ebaadc6bdff00001756d604566437b25e42d61e521f6f38a32c386d44a18dee57a77a92aedef1116f2410b504e6 languageName: node linkType: hard -"@svgr/hast-util-to-babel-ast@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/hast-util-to-babel-ast@npm:6.3.1" +"@svgr/hast-util-to-babel-ast@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/hast-util-to-babel-ast@npm:6.5.0" dependencies: "@babel/types": ^7.18.4 entities: ^4.3.0 - checksum: e54b48a85795e103cfe918fa7102bf4603e6541dd35ee04061fad62edffa5a60d8aa210f709fe81b50c9fb6041f8e62fabf093443266323c649903200aaea604 + checksum: 77dcadb467eded0ce5cba71dbd4cbb4c7ffd7961f351828a4066ab6105d466d55533506bc3bc7db78f938af6692008ceda9fa2ea3dda75fd54e2b736b81ae458 languageName: node linkType: hard -"@svgr/plugin-jsx@npm:6.3.x, @svgr/plugin-jsx@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/plugin-jsx@npm:6.3.1" +"@svgr/plugin-jsx@npm:6.5.x, @svgr/plugin-jsx@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/plugin-jsx@npm:6.5.0" dependencies: "@babel/core": ^7.18.5 - "@svgr/babel-preset": ^6.3.1 - "@svgr/hast-util-to-babel-ast": ^6.3.1 + "@svgr/babel-preset": ^6.5.0 + "@svgr/hast-util-to-babel-ast": ^6.5.0 svg-parser: ^2.0.4 peerDependencies: "@svgr/core": ^6.0.0 - checksum: a2e487dc28d2b69b94b7d96e5cb2593857e559e64c8cb4f818035b8a43ba84e5ddb67f966d15f173b544e807e48a1cda1065da8f9064b94b8d62bbe8cb8c4d73 + checksum: dec7cd47f16cc1b23dd37e333594d596401e7e49825545f4d808c74fe0a3a12a56c1bd55f507794addd6675e1b81dd58346bac4f752daa04016d3edac03e1625 languageName: node linkType: hard -"@svgr/plugin-svgo@npm:6.3.x, @svgr/plugin-svgo@npm:^6.3.1": - version: 6.3.1 - resolution: "@svgr/plugin-svgo@npm:6.3.1" +"@svgr/plugin-svgo@npm:6.5.x, @svgr/plugin-svgo@npm:^6.5.0": + version: 6.5.0 + resolution: "@svgr/plugin-svgo@npm:6.5.0" dependencies: cosmiconfig: ^7.0.1 deepmerge: ^4.2.2 svgo: ^2.8.0 peerDependencies: "@svgr/core": ^6.0.0 - checksum: 037d6f91ba7f362764527408661f7fc4a4a296e9dc142a5f2e33fc88dc63dafd305452caae3091e9adb63adf029e0ce20c604d5af787b968f98aad261a834679 + checksum: d1a0ee79283a997aa4af2c2848f502dff71a9fd99623b47b0b2476effd7fe53077456afd1ff54283512a7dd10f30df91736f5d20eb931a462b34f281c90347e0 languageName: node linkType: hard -"@svgr/rollup@npm:6.3.x": - version: 6.3.1 - resolution: "@svgr/rollup@npm:6.3.1" +"@svgr/rollup@npm:6.5.x": + version: 6.5.0 + resolution: "@svgr/rollup@npm:6.5.0" dependencies: "@babel/core": ^7.18.5 "@babel/plugin-transform-react-constant-elements": ^7.17.12 @@ -12500,149 +12799,149 @@ __metadata: "@babel/preset-react": ^7.17.12 "@babel/preset-typescript": ^7.17.12 "@rollup/pluginutils": ^4.2.1 - "@svgr/core": ^6.3.1 - "@svgr/plugin-jsx": ^6.3.1 - "@svgr/plugin-svgo": ^6.3.1 - checksum: 8d96f95a4c89d96a14bc0095469a4ceb39a2ae4ea3517d08573ed8ca11b05416a505723ef09d00c3cee056adc2d95ddcea6c8055a116bbb461ba33d601bfced8 + "@svgr/core": ^6.5.0 + "@svgr/plugin-jsx": ^6.5.0 + "@svgr/plugin-svgo": ^6.5.0 + checksum: a3a043034689a335aa9657580251a8636225002439a4667c31fe2b0faa043980182b28aac7da0219804609ef19c0b3b397626850f056a7065c20f61c2f17b4f8 languageName: node linkType: hard -"@svgr/webpack@npm:6.3.x": - version: 6.3.1 - resolution: "@svgr/webpack@npm:6.3.1" +"@svgr/webpack@npm:6.5.x": + version: 6.5.0 + resolution: "@svgr/webpack@npm:6.5.0" dependencies: "@babel/core": ^7.18.5 "@babel/plugin-transform-react-constant-elements": ^7.17.12 "@babel/preset-env": ^7.18.2 "@babel/preset-react": ^7.17.12 "@babel/preset-typescript": ^7.17.12 - "@svgr/core": ^6.3.1 - "@svgr/plugin-jsx": ^6.3.1 - "@svgr/plugin-svgo": ^6.3.1 - checksum: 36784eacf80601462ede7eab66347423a8635e68aa9f152308c81878b071807adee152a28eed2cce9c72faaf6553dd500f68f00601062ec6821ec0a3a77f4e13 + "@svgr/core": ^6.5.0 + "@svgr/plugin-jsx": ^6.5.0 + "@svgr/plugin-svgo": ^6.5.0 + checksum: 2c0b18b20694b1301e86893e488269882ae136259cc17b4ab47c208c6edcdfeaefa23894b3fe68d36384e26380eafde23571b0cb143aadf538c027d72eb1d702 languageName: node linkType: hard -"@swc/core-android-arm-eabi@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-android-arm-eabi@npm:1.3.4" +"@swc/core-android-arm-eabi@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-android-arm-eabi@npm:1.3.9" dependencies: "@swc/wasm": 1.2.122 conditions: os=android & cpu=arm languageName: node linkType: hard -"@swc/core-android-arm64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-android-arm64@npm:1.3.4" +"@swc/core-android-arm64@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-android-arm64@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-darwin-arm64@npm:1.3.4" +"@swc/core-darwin-arm64@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-darwin-arm64@npm:1.3.9" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-darwin-x64@npm:1.3.4" +"@swc/core-darwin-x64@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-darwin-x64@npm:1.3.9" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-freebsd-x64@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-freebsd-x64@npm:1.3.4" +"@swc/core-freebsd-x64@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-freebsd-x64@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.4" +"@swc/core-linux-arm-gnueabihf@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.4" +"@swc/core-linux-arm64-gnu@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.9" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.4" +"@swc/core-linux-arm64-musl@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.9" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.4" +"@swc/core-linux-x64-gnu@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.9" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-linux-x64-musl@npm:1.3.4" +"@swc/core-linux-x64-musl@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-linux-x64-musl@npm:1.3.9" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.4" +"@swc/core-win32-arm64-msvc@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.4" +"@swc/core-win32-ia32-msvc@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.9" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.4": - version: 1.3.4 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.4" +"@swc/core-win32-x64-msvc@npm:1.3.9": + version: 1.3.9 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.9" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"@swc/core@npm:^1.2.239": - version: 1.3.4 - resolution: "@swc/core@npm:1.3.4" +"@swc/core@npm:^1.3.9": + version: 1.3.9 + resolution: "@swc/core@npm:1.3.9" dependencies: - "@swc/core-android-arm-eabi": 1.3.4 - "@swc/core-android-arm64": 1.3.4 - "@swc/core-darwin-arm64": 1.3.4 - "@swc/core-darwin-x64": 1.3.4 - "@swc/core-freebsd-x64": 1.3.4 - "@swc/core-linux-arm-gnueabihf": 1.3.4 - "@swc/core-linux-arm64-gnu": 1.3.4 - "@swc/core-linux-arm64-musl": 1.3.4 - "@swc/core-linux-x64-gnu": 1.3.4 - "@swc/core-linux-x64-musl": 1.3.4 - "@swc/core-win32-arm64-msvc": 1.3.4 - "@swc/core-win32-ia32-msvc": 1.3.4 - "@swc/core-win32-x64-msvc": 1.3.4 + "@swc/core-android-arm-eabi": 1.3.9 + "@swc/core-android-arm64": 1.3.9 + "@swc/core-darwin-arm64": 1.3.9 + "@swc/core-darwin-x64": 1.3.9 + "@swc/core-freebsd-x64": 1.3.9 + "@swc/core-linux-arm-gnueabihf": 1.3.9 + "@swc/core-linux-arm64-gnu": 1.3.9 + "@swc/core-linux-arm64-musl": 1.3.9 + "@swc/core-linux-x64-gnu": 1.3.9 + "@swc/core-linux-x64-musl": 1.3.9 + "@swc/core-win32-arm64-msvc": 1.3.9 + "@swc/core-win32-ia32-msvc": 1.3.9 + "@swc/core-win32-x64-msvc": 1.3.9 dependenciesMeta: "@swc/core-android-arm-eabi": optional: true @@ -12672,16 +12971,16 @@ __metadata: optional: true bin: swcx: run_swcx.js - checksum: 24b4ca4a096fea53056ed227a8aa09aa38cfe5eef344451397e66a2d183ded119872cf4fc8c671c0a6eb34985537cbf8d8a7e742b9e27dad0735426693d11dc8 + checksum: 761918f1bca5d494eaaafd49720717e3b3071df5bc6ef8b298a778ba4e4d20bc5a78c939a0b1c98623f9fe23e535a16a359179b72390cd1f5cbc891ea53c22fa languageName: node linkType: hard "@swc/helpers@npm:^0.4.7": - version: 0.4.11 - resolution: "@swc/helpers@npm:0.4.11" + version: 0.4.12 + resolution: "@swc/helpers@npm:0.4.12" dependencies: tslib: ^2.4.0 - checksum: 736857d524b41a8a4db81094e9b027f554004e0fa3e86325d85bdb38f7e6459ce022db079edb6c61ba0f46fe8583b3e663e95f7acbd13e51b8da6c34e45bba2e + checksum: 3f9112f37d87815b6d4270137fc78d22bb98c75138e9b0eac7cac203ec2cf2bffbf13b20a713067c292affd5e9e70a724eb245b8daf0963e7fe528b901771c28 languageName: node linkType: hard @@ -12720,18 +13019,18 @@ __metadata: languageName: node linkType: hard -"@tanstack/query-core@npm:4.7.2": - version: 4.7.2 - resolution: "@tanstack/query-core@npm:4.7.2" - checksum: 5be625bf27505acded2c5add69c54a48be8c6cacdf4ea821ef499ca449a3a5694bda995bdc2439a83039563f91cb3424b765c597fd28861c6a867efda039e6d1 +"@tanstack/query-core@npm:4.13.0": + version: 4.13.0 + resolution: "@tanstack/query-core@npm:4.13.0" + checksum: 11cb95be4dc6e1ba3f0f4eb04c255eab7fa5eca3441d6e698e2c6d03c84c03efaf34d475594e259593d76f584af0c0e830c7d868a682d6dc0f61d1a439728d12 languageName: node linkType: hard "@tanstack/react-query@npm:^4.1.3": - version: 4.7.2 - resolution: "@tanstack/react-query@npm:4.7.2" + version: 4.13.0 + resolution: "@tanstack/react-query@npm:4.13.0" dependencies: - "@tanstack/query-core": 4.7.2 + "@tanstack/query-core": 4.13.0 use-sync-external-store: ^1.2.0 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -12742,7 +13041,7 @@ __metadata: optional: true react-native: optional: true - checksum: 07f6ec6c44c6826986167d772d78051414f15f08c874fe38187e1175a527ec71f77025d1466651802b8cde991618acece668fc17d7dc62af837ad7fb02882fa3 + checksum: e538b585e4c2b4a8ff84c17509f4ce226e18e3e862553b6a9ca90e29c200db4ec7ab4f400f19ef4846a76c51babe0db120c54779054805fbecdcd29b369caf0e languageName: node linkType: hard @@ -13786,13 +14085,6 @@ __metadata: languageName: node linkType: hard -"@types/json-buffer@npm:~3.0.0": - version: 3.0.0 - resolution: "@types/json-buffer@npm:3.0.0" - checksum: 6b0a371dd603f0eec9d00874574bae195382570e832560dadf2193ee0d1062b8e0694bbae9798bc758632361c227b1e3b19e3bd914043b498640470a2da38b77 - languageName: node - linkType: hard - "@types/json-schema-merge-allof@npm:^0.6.0": version: 0.6.1 resolution: "@types/json-schema-merge-allof@npm:0.6.1" @@ -13842,11 +14134,11 @@ __metadata: linkType: hard "@types/keyv@npm:*": - version: 3.1.1 - resolution: "@types/keyv@npm:3.1.1" + version: 3.1.4 + resolution: "@types/keyv@npm:3.1.4" dependencies: "@types/node": "*" - checksum: ee0d098693bf4af44be756eed02daf95f5d0fd4b5b02da952a5952e08842baddf6a986a9ea5f9e460729782f1a0a47848c892ad96ea188b66a363feb49a1536f + checksum: e009a2bfb50e90ca9b7c6e8f648f8464067271fd99116f881073fa6fa76dc8d0133181dd65e6614d5fb1220d671d67b0124aef7d97dc02d7e342ab143a47779d languageName: node linkType: hard @@ -13894,13 +14186,20 @@ __metadata: languageName: node linkType: hard -"@types/luxon@npm:*, @types/luxon@npm:^3.0.0": +"@types/luxon@npm:*": version: 3.0.1 resolution: "@types/luxon@npm:3.0.1" checksum: a81444f9b474ea9b3063ab4cc68b917a2634e38b4e229f86c78c35023a32bf5e8d1044d7c229c011291662c976cb6c4cf109dc3d2077c571790a31779a554178 languageName: node linkType: hard +"@types/luxon@npm:^3.0.0": + version: 3.0.2 + resolution: "@types/luxon@npm:3.0.2" + checksum: e47e199c5d70ce16e4eff54b21fdf385065f1134774109b40c36dba8b5fd29a732b763125e93863d6070dd22ba6a3f18dbdd368cf3748aae9f24bb335c5c210d + languageName: node + linkType: hard + "@types/markdown-it@npm:^12.2.3": version: 12.2.3 resolution: "@types/markdown-it@npm:12.2.3" @@ -14023,6 +14322,15 @@ __metadata: languageName: node linkType: hard +"@types/node-forge@npm:^1.3.0": + version: 1.3.0 + resolution: "@types/node-forge@npm:1.3.0" + dependencies: + "@types/node": "*" + checksum: f811885f997fbeebb0df2db8249b9b288ad5f5573beaecf6d323b019bb9a4c12f1476a8974c934f62329862f29986c116263a50aef0008364cc3f9867672a5a0 + languageName: node + linkType: hard + "@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0": version: 17.0.25 resolution: "@types/node@npm:17.0.25" @@ -14246,11 +14554,11 @@ __metadata: linkType: hard "@types/puppeteer@npm:^5.4.4": - version: 5.4.6 - resolution: "@types/puppeteer@npm:5.4.6" + version: 5.4.7 + resolution: "@types/puppeteer@npm:5.4.7" dependencies: "@types/node": "*" - checksum: b969230812e95fcc5eee83151731e51b287cc934169f552034d8eea58ce9b5beb4ba09b9ce05b75d9510241a85d244f28e8b0a0a4b9f7dffcad8c37a39288260 + checksum: 491c0ca2d879b72e869f5152a9ea7de1356a66f44ef6210fdd5c5036d893b4ee2956212e2f3353c7045b9e56d1a4cea408f41a26d7cf3708774c4d6c19a7c654 languageName: node linkType: hard @@ -15108,9 +15416,9 @@ __metadata: languageName: node linkType: hard -"@uiw/codemirror-extensions-basic-setup@npm:4.12.3": - version: 4.12.3 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.12.3" +"@uiw/codemirror-extensions-basic-setup@npm:4.12.4": + version: 4.12.4 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.12.4" dependencies: "@codemirror/autocomplete": ^6.0.0 "@codemirror/commands": ^6.0.0 @@ -15127,19 +15435,19 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 14c5c6694b2f5ce34bba2ed3fa7017a268c36f3ba234711580d31243603a14adc2b31ebec7d665cd6687917a8ac617d6ac453861d32bf277f43b65946ac1c656 + checksum: 27c65e051383cedf0ea63b3c923caf10aa45dba7cec01b3a767d81d1d0114ba221665bf0d8166f8de67946806fde7348768cf4a7683a70907e9f2eef9c99dbea languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.9.3": - version: 4.12.3 - resolution: "@uiw/react-codemirror@npm:4.12.3" + version: 4.12.4 + resolution: "@uiw/react-codemirror@npm:4.12.4" dependencies: "@babel/runtime": ^7.18.6 "@codemirror/commands": ^6.1.0 "@codemirror/state": ^6.1.1 "@codemirror/theme-one-dark": ^6.0.0 - "@uiw/codemirror-extensions-basic-setup": 4.12.3 + "@uiw/codemirror-extensions-basic-setup": 4.12.4 codemirror: ^6.0.0 peerDependencies: "@babel/runtime": ">=7.11.0" @@ -15149,7 +15457,7 @@ __metadata: codemirror: ">=6.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: 504003e6162dbe672072ace980117d1eeb9264282d6ad314de0398e0d29925133bc8d8c484c3aadb91b6ebedb8ebb259307b838cc31d39ab4792af5ab061949c + checksum: e7228beb95e918929e99e819c4f3b3920a16c5aca2477775c41bd16166af5bb7b5cefe240bd75b80fd9f61cde82086f93f8cd2dc1e2a4ecf02ff6d756cfd6f42 languageName: node linkType: hard @@ -15345,6 +15653,13 @@ __metadata: languageName: node linkType: hard +"@xmldom/xmldom@npm:^0.7.6": + version: 0.7.6 + resolution: "@xmldom/xmldom@npm:0.7.6" + checksum: 3c31dcd909aaefd65090033bd45e0aca42d636a9ae43c7313f4be87de570d046abba28362810d63c0718d6f08a9ce5f8da27b87437afc24d2290b6d4e75a6eee + languageName: node + linkType: hard + "@xobotyi/scrollbar-width@npm:^1.9.5": version: 1.9.5 resolution: "@xobotyi/scrollbar-width@npm:1.9.5" @@ -15626,7 +15941,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.6.3, ajv@npm:^8.8.0": +"ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.11.0, ajv@npm:^8.6.3, ajv@npm:^8.8.0": version: 8.11.0 resolution: "ajv@npm:8.11.0" dependencies: @@ -16756,13 +17071,6 @@ __metadata: languageName: node linkType: hard -"balanced-match@npm:^0.4.2": - version: 0.4.2 - resolution: "balanced-match@npm:0.4.2" - checksum: 205ebb42ce8529fa8e043a808b41bfb9818d5f98a8eb76a1cd5483f8a98dd0baefc8a9d940f36b591b1316a04f56b35c32b60ac9b1f848e41e4698672cec6c1e - languageName: node - linkType: hard - "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -18374,7 +18682,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:*, commander@npm:^9.1.0, commander@npm:^9.3.0": +"commander@npm:*, commander@npm:^9.3.0": version: 9.4.0 resolution: "commander@npm:9.4.0" checksum: a322de584a6ccd1ea83c24f6a660e52d16ffbe2613fcfbb8d2cc68bc9dec637492456d754fe8bb5b039ad843ed8e04fb0b107e581a75f62cde9e1a0ab1546e09 @@ -18430,6 +18738,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^9.1.0": + version: 9.4.1 + resolution: "commander@npm:9.4.1" + checksum: bfb18e325a5bdf772763c2213d5c7d9e77144d944124e988bcd8e5e65fb6d45d5d4e86b09155d0f2556c9a59c31e428720e57968bcd050b2306e910a0bf3cf13 + languageName: node + linkType: hard + "common-ancestor-path@npm:^1.0.1": version: 1.0.1 resolution: "common-ancestor-path@npm:1.0.1" @@ -18493,16 +18808,6 @@ __metadata: languageName: node linkType: hard -"compress-brotli@npm:^1.3.8": - version: 1.3.8 - resolution: "compress-brotli@npm:1.3.8" - dependencies: - "@types/json-buffer": ~3.0.0 - json-buffer: ~3.0.1 - checksum: de7589d692d40eb362f6c91070b5e51bc10b05a89eabb4a7c76c1aa21b625756f8c101c6999e4df0c4dc6199c5ca2e1353573bfdcca5615810f27485394162a5 - languageName: node - linkType: hard - "compress-commons@npm:^4.1.0": version: 4.1.0 resolution: "compress-commons@npm:4.1.0" @@ -18786,14 +19091,28 @@ __metadata: languageName: node linkType: hard -"core-js-pure@npm:^3.20.2, core-js-pure@npm:^3.6.5, core-js-pure@npm:^3.8.1": +"core-js-pure@npm:^3.20.2": version: 3.21.1 resolution: "core-js-pure@npm:3.21.1" checksum: 00a5dff599b7fb0b30746a638b9d0edbdc0df24ed1580ca56be595fbe3c78c375d37fc4e1bff23627109229702c9ee8ea2587a66b8280eb33b85160aa4e401e9 languageName: node linkType: hard -"core-js@npm:^2.4.0, core-js@npm:^2.5.0, core-js@npm:^2.6.10": +"core-js-pure@npm:^3.23.3": + version: 3.25.5 + resolution: "core-js-pure@npm:3.25.5" + checksum: e48799a8ab28f00ef3db18377142ff2c578574ab2b18ebddde6cbf12823e0181a57c80e3caa6046ce2a2e439d603a252be767583ddc54248e3d1060bc5012127 + languageName: node + linkType: hard + +"core-js-pure@npm:^3.6.5": + version: 3.25.3 + resolution: "core-js-pure@npm:3.25.3" + checksum: be3097b8e2b541977a08004e433b68adb4951cc7694229e695ae407f296d370c1f360e5fd70fc6910c4427e46e48e087ac455d68cebdf4314370907d2e03eef1 + languageName: node + linkType: hard + +"core-js@npm:^2.4.0, core-js@npm:^2.5.0": version: 2.6.12 resolution: "core-js@npm:2.6.12" checksum: 44fa9934a85f8c78d61e0c8b7b22436330471ffe59ec5076fe7f324d6e8cf7f824b14b1c81ca73608b13bdb0fef035bd820989bf059767ad6fa13123bb8bd016 @@ -18808,9 +19127,9 @@ __metadata: linkType: hard "core-js@npm:^3.6.5": - version: 3.25.3 - resolution: "core-js@npm:3.25.3" - checksum: 26ca0a5e575e6da03dc30099f48bc00db50a1c0a3f81299165f111cb163869ae453de5e345f2cb7bc8be85a9adcee11fca94f0fbdbdf6d483c68e1c55a9efc5a + version: 3.25.5 + resolution: "core-js@npm:3.25.5" + checksum: 208b308c49bc022f90d4349d4c99802a73c9d55053976b3c529f10014c1e37845926defad8c519f2c7f71ea0acf18d2b323ab6aaee34dc85b4c4b3ced0623f3f languageName: node linkType: hard @@ -18974,18 +19293,18 @@ __metadata: linkType: hard "cron@npm:^2.0.0": - version: 2.0.0 - resolution: "cron@npm:2.0.0" + version: 2.1.0 + resolution: "cron@npm:2.1.0" dependencies: luxon: ^1.23.x - checksum: 179ec137ada4ceb44cafe51c55491e84954308d7012d2a44539f0dadbbb1ffbbe3072c2f7aaa88595d60bd56e0d536aae2e4aaa4430c869317d6c2abd966988b + checksum: 9395875c091f56db7964491c249cb143d2e4ba77560d7132da783943c1b0537ef1814eb8f552c81eda5a2aa153216dd3f5b7ff63e372a68a063fcfafe8231f91 languageName: node linkType: hard "cronstrue@npm:^2.2.0": - version: 2.11.0 - resolution: "cronstrue@npm:2.11.0" - checksum: 9a96c052ffbefd5e88458fb3afe7655d2d1c807660c35da982de7b387d507e471d21f12e275dae0abb11f15df471847ea9157a9637e126bf8439d7baff0b69ca + version: 2.14.0 + resolution: "cronstrue@npm:2.14.0" + checksum: ffbceca5211575513b37a454253fe7aeb16fe841ed12c1e8a2241f0e55f46a72d4cb97fc3b56aa6bfc58c7d249f8c168f89bfd2011841f237098f86f435f6fcf languageName: node linkType: hard @@ -19387,8 +19706,8 @@ __metadata: linkType: hard "cypress@npm:^10.0.0": - version: 10.8.0 - resolution: "cypress@npm:10.8.0" + version: 10.10.0 + resolution: "cypress@npm:10.10.0" dependencies: "@cypress/request": ^2.88.10 "@cypress/xvfb": ^1.2.4 @@ -19434,7 +19753,7 @@ __metadata: yauzl: ^2.10.0 bin: cypress: bin/cypress - checksum: c052690049980e7721e6fca563b724fde839d87d83c1478dfe26ce7d230992717c2c4028e7157bfb39ec274473e51929f49e5aab6a23c2b25cde2a439b1c3cf9 + checksum: 668a32534a527dba79754abbf98af176b80c539a12ec00058932ba2a19c794c7888323e59e738c30f726ad740c5451c31d02548a0cb7c1b1c8ad01c55a984ca2 languageName: node linkType: hard @@ -19447,27 +19766,6 @@ __metadata: languageName: node linkType: hard -"d3-array@npm:^1.2.0": - version: 1.2.4 - resolution: "d3-array@npm:1.2.4" - checksum: d0be1fa7d72dbfac8a3bcffbb669d42bcb9128d8818d84d2b1df0c60bbe4c8e54a798be0457c55a219b399e2c2fabcbd581cbb130eb638b5436b0618d7e56000 - languageName: node - linkType: hard - -"d3-collection@npm:1": - version: 1.0.7 - resolution: "d3-collection@npm:1.0.7" - checksum: 9c6b910a9da0efb021e294509f98263ca4f62d10b997bb30ccfb6edd582b703da36e176b968b5bac815fbb0f328e49643c38cf93b5edf8572a179ba55cf4a09d - languageName: node - linkType: hard - -"d3-color@npm:1": - version: 1.4.1 - resolution: "d3-color@npm:1.4.1" - checksum: a214b61458b5fcb7ad1a84faed0e02918037bab6be37f2d437bf0e2915cbd854d89fbf93754f17b0781c89e39d46704633d05a2bfae77e6209f0f4b140f9894b - languageName: node - linkType: hard - "d3-color@npm:1 - 2": version: 2.0.0 resolution: "d3-color@npm:2.0.0" @@ -19517,13 +19815,6 @@ __metadata: languageName: node linkType: hard -"d3-format@npm:1": - version: 1.4.5 - resolution: "d3-format@npm:1.4.5" - checksum: 1b8b2c0bca182173bccd290a43e8b635a83fc8cfe52ec878c7bdabb997d47daac11f2b175cebbe73f807f782ad655f542bdfe18180ca5eb3498a3a82da1e06ab - languageName: node - linkType: hard - "d3-format@npm:1 - 2": version: 2.0.0 resolution: "d3-format@npm:2.0.0" @@ -19540,15 +19831,6 @@ __metadata: languageName: node linkType: hard -"d3-interpolate@npm:1, d3-interpolate@npm:^1.3.0": - version: 1.4.0 - resolution: "d3-interpolate@npm:1.4.0" - dependencies: - d3-color: 1 - checksum: d98988bd1e2f59d01f100d0a19315ad8f82ef022aa09a65aff76f747a44f9b52f2d64c6578b8f47e01f2b14a8f0ef88f5460d11173c0dd2d58238c217ac0ec03 - languageName: node - linkType: hard - "d3-interpolate@npm:1.2.0 - 2, d3-interpolate@npm:^2.0.0": version: 2.0.1 resolution: "d3-interpolate@npm:2.0.1" @@ -19558,13 +19840,6 @@ __metadata: languageName: node linkType: hard -"d3-path@npm:1": - version: 1.0.9 - resolution: "d3-path@npm:1.0.9" - checksum: d4382573baf9509a143f40944baeff9fead136926aed6872f7ead5b3555d68925f8a37935841dd51f1d70b65a294fe35c065b0906fb6e42109295f6598fc16d0 - languageName: node - linkType: hard - "d3-path@npm:1 - 2": version: 2.0.0 resolution: "d3-path@npm:2.0.0" @@ -19586,20 +19861,6 @@ __metadata: languageName: node linkType: hard -"d3-scale@npm:^2.1.0": - version: 2.2.2 - resolution: "d3-scale@npm:2.2.2" - dependencies: - d3-array: ^1.2.0 - d3-collection: 1 - d3-format: 1 - d3-interpolate: 1 - d3-time: 1 - d3-time-format: 2 - checksum: 42086d4b9db9f8492a99dbbdacf546983faef1bb6260fe875c0c1884f1ca9cf5fd233de3702c2f9e24145b1c5383945e929c8682d80fa57ab515ef2c4f2c61f6 - languageName: node - linkType: hard - "d3-scale@npm:^3.0.0": version: 3.3.0 resolution: "d3-scale@npm:3.3.0" @@ -19620,15 +19881,6 @@ __metadata: languageName: node linkType: hard -"d3-shape@npm:^1.2.0": - version: 1.3.7 - resolution: "d3-shape@npm:1.3.7" - dependencies: - d3-path: 1 - checksum: 46566a3ab64a25023653bf59d64e81e9e6c987e95be985d81c5cedabae5838bd55f4a201a6b69069ca862eb63594cd263cac9034afc2b0e5664dfe286c866129 - languageName: node - linkType: hard - "d3-shape@npm:^2.0.0": version: 2.1.0 resolution: "d3-shape@npm:2.1.0" @@ -19647,15 +19899,6 @@ __metadata: languageName: node linkType: hard -"d3-time-format@npm:2": - version: 2.3.0 - resolution: "d3-time-format@npm:2.3.0" - dependencies: - d3-time: 1 - checksum: 5445eaaf2b3b2095cdc1fa75dfd2f361a61c39b677dcc1c2ba4cb6bc0442953de0fbaaa397d7d7a9325ad99c63d869f162a713e150e826ff8af482615664cb3f - languageName: node - linkType: hard - "d3-time-format@npm:2 - 3": version: 3.0.0 resolution: "d3-time-format@npm:3.0.0" @@ -19665,13 +19908,6 @@ __metadata: languageName: node linkType: hard -"d3-time@npm:1": - version: 1.1.0 - resolution: "d3-time@npm:1.1.0" - checksum: 33fcfff94ff093dde2048c190ecca8b39fe0ec8b3c61e9fc39c5f6072ce5b86dd2b91823f086366995422bbbac7f74fd9abdb7efe4f292a73b1c6197c699cc78 - languageName: node - linkType: hard - "d3-time@npm:1 - 2, d3-time@npm:^2.1.1": version: 2.1.1 resolution: "d3-time@npm:2.1.1" @@ -21688,8 +21924,8 @@ __metadata: linkType: hard "eslint-plugin-react@npm:^7.28.0": - version: 7.31.8 - resolution: "eslint-plugin-react@npm:7.31.8" + version: 7.31.10 + resolution: "eslint-plugin-react@npm:7.31.10" dependencies: array-includes: ^3.1.5 array.prototype.flatmap: ^1.3.0 @@ -21707,7 +21943,7 @@ __metadata: string.prototype.matchall: ^4.0.7 peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - checksum: 0683e2a624a4df6f08264a3f6bc614a81e8f961c83173bdf2d8d3523f84ed5d234cddc976dbc6815913e007c5984df742ba61be0c0592b27c3daabe0f68165a3 + checksum: f013669c296483559a760648fa06425f161b1aff93c668f14c4561c933d22a7836b745b88a795c53cab929c71513d5fd1f2ffdddff915709f01b77ac25f5b71b languageName: node linkType: hard @@ -22027,6 +22263,7 @@ __metadata: "@backstage/plugin-apache-airflow": "workspace:^" "@backstage/plugin-api-docs": "workspace:^" "@backstage/plugin-azure-devops": "workspace:^" + "@backstage/plugin-azure-sites": "workspace:^" "@backstage/plugin-badges": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-graph": "workspace:^" @@ -22077,7 +22314,6 @@ __metadata: "@material-ui/lab": 4.0.0-alpha.57 "@octokit/rest": ^19.0.3 "@oriflame/backstage-plugin-score-card": ^0.5.1 - "@rjsf/core": ^3.2.1 "@roadiehq/backstage-plugin-buildkite": ^2.0.8 "@roadiehq/backstage-plugin-github-insights": ^2.0.5 "@roadiehq/backstage-plugin-github-pull-requests": ^2.2.7 @@ -22133,6 +22369,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-azure-devops-backend": "workspace:^" + "@backstage/plugin-azure-sites-backend": "workspace:^" "@backstage/plugin-badges-backend": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" "@backstage/plugin-code-coverage-backend": "workspace:^" @@ -24924,7 +25161,14 @@ __metadata: languageName: node linkType: hard -"immer@npm:^9.0.1, immer@npm:^9.0.7": +"immer@npm:^9.0.1": + version: 9.0.16 + resolution: "immer@npm:9.0.16" + checksum: e9a5ca65c929b329da7a3b7beccf7984271cda7bdd47b2cab619eac3277dcd56598c211b55cc340786b6eff0c06652ac018808d9fd744443f06882364dece6bc + languageName: node + linkType: hard + +"immer@npm:^9.0.7": version: 9.0.15 resolution: "immer@npm:9.0.15" checksum: 92e3d63e810e3c3c2bb61b70c45443e37ef983ad12924e3edaf03725ae5979618f5b473439bb3bb4a8c4769f25132f18dec10ea15c40f0b20da5691ff96ff611 @@ -25126,7 +25370,7 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:^8.0.0, inquirer@npm:^8.2.0": +"inquirer@npm:^8.0.0": version: 8.2.4 resolution: "inquirer@npm:8.2.4" dependencies: @@ -25149,6 +25393,29 @@ __metadata: languageName: node linkType: hard +"inquirer@npm:^8.2.0": + version: 8.2.5 + resolution: "inquirer@npm:8.2.5" + dependencies: + ansi-escapes: ^4.2.1 + chalk: ^4.1.1 + cli-cursor: ^3.1.0 + cli-width: ^3.0.0 + external-editor: ^3.0.3 + figures: ^3.0.0 + lodash: ^4.17.21 + mute-stream: 0.0.8 + ora: ^5.4.1 + run-async: ^2.4.0 + rxjs: ^7.5.5 + string-width: ^4.1.0 + strip-ansi: ^6.0.0 + through: ^2.3.6 + wrap-ansi: ^7.0.0 + checksum: f13ee4c444187786fb393609dedf6b30870115a57b603f2e6424f29a99abc13446fd45ee22461c33c9c40a92a60a8df62d0d6b25d74fc6676fa4cb211de55b55 + languageName: node + linkType: hard + "internal-slot@npm:^1.0.3": version: 1.0.3 resolution: "internal-slot@npm:1.0.3" @@ -25207,9 +25474,9 @@ __metadata: languageName: node linkType: hard -"ioredis@npm:^5.1.0": - version: 5.1.0 - resolution: "ioredis@npm:5.1.0" +"ioredis@npm:^5.2.3": + version: 5.2.3 + resolution: "ioredis@npm:5.2.3" dependencies: "@ioredis/commands": ^1.1.1 cluster-key-slot: ^1.1.0 @@ -25220,7 +25487,14 @@ __metadata: redis-errors: ^1.2.0 redis-parser: ^3.0.0 standard-as-callback: ^2.1.0 - checksum: 7b1c137836ee136a634926df4ec68cc2393772a32ecda8e30dd305d7b9182af02d600456e661f96a6ceada17560ce7b6458948fd09b8adc277a1d287325777dd + checksum: 2cb7f0f4217e6774accad3620af1b7114722721c1d1824be2c9f0c2a77ab9629f2e0848d18b1a7208bc37796ae1207cb3e0898fce61900cfe797da0382724ad1 + languageName: node + linkType: hard + +"ip-regex@npm:^2.1.0": + version: 2.1.0 + resolution: "ip-regex@npm:2.1.0" + checksum: 331d95052aa53ce245745ea0fc3a6a1e2e3c8d6da65fa8ea52bf73768c1b22a9ac50629d1d2b08c04e7b3ac4c21b536693c149ce2c2615ee4796030e5b3e3cba languageName: node linkType: hard @@ -26687,11 +26961,11 @@ __metadata: linkType: hard "jest-when@npm:^3.1.0": - version: 3.5.1 - resolution: "jest-when@npm:3.5.1" + version: 3.5.2 + resolution: "jest-when@npm:3.5.2" peerDependencies: jest: ">= 25" - checksum: 1efb9f497f7c846fe8b0f4125d5f449c4a4d78d5d0afa910d134b301ae4c119ea52c9465db38d2146269d42808afe8f3a4328d1d656878a9a69458ee653f6499 + checksum: 9ad95552d377ef4d517c96a14c38bd8626d6856f518e7efc8a01d76a45a39d3c52281392c98da781c302114c78cd1ea17556c7f638d49d15971fcce9d58306e8 languageName: node linkType: hard @@ -27107,7 +27381,7 @@ __metadata: languageName: node linkType: hard -"json-buffer@npm:3.0.1, json-buffer@npm:^3.0.1, json-buffer@npm:~3.0.1": +"json-buffer@npm:3.0.1, json-buffer@npm:^3.0.1": version: 3.0.1 resolution: "json-buffer@npm:3.0.1" checksum: 9026b03edc2847eefa2e37646c579300a1f3a4586cfb62bf857832b60c852042d0d6ae55d1afb8926163fa54c2b01d83ae24705f34990348bdac6273a29d4581 @@ -27160,15 +27434,15 @@ __metadata: linkType: hard "json-schema-library@npm:^7.0.0": - version: 7.0.0 - resolution: "json-schema-library@npm:7.0.0" + version: 7.2.1 + resolution: "json-schema-library@npm:7.2.1" dependencies: deepmerge: ^4.2.2 fast-deep-equal: ^3.1.3 gson-pointer: ^4.1.1 gson-query: ^5.1.0 valid-url: ^1.0.9 - checksum: 4e6b129b836c84d8080d46a02fef4886ffec8114182ea015a80536f6b8609c5a11354a5e2a98a15b0734bfb25ec60b6a0f1e7230172396d026c679cb385b8f21 + checksum: 65bc4014cdfe22c4f46d6cb0a0c059d94045128fb96189c4823dff653b6788c2dbab8d5d2e8475bbdc65fd47abf19cedcc3f02df608452c5413fea5d6e675003 languageName: node linkType: hard @@ -27359,10 +27633,10 @@ __metadata: languageName: node linkType: hard -"jsonpointer@npm:^5.0.0": - version: 5.0.0 - resolution: "jsonpointer@npm:5.0.0" - checksum: c7ec0b6bb596b81de687bc12945586bbcdc80dfb54919656d2690d76334f796a936270067ee9f1b5bbc2d9ecc551afb366ac35e6685aa61f07b5b68d1e5e857d +"jsonpointer@npm:^5.0.0, jsonpointer@npm:^5.0.1": + version: 5.0.1 + resolution: "jsonpointer@npm:5.0.1" + checksum: 0b40f712900ad0c846681ea2db23b6684b9d5eedf55807b4708c656f5894b63507d0e28ae10aa1bddbea551241035afe62b6df0800fc94c2e2806a7f3adecd7c languageName: node linkType: hard @@ -27622,12 +27896,11 @@ __metadata: linkType: hard "keyv@npm:^4.0.0, keyv@npm:^4.0.3": - version: 4.3.2 - resolution: "keyv@npm:4.3.2" + version: 4.5.0 + resolution: "keyv@npm:4.5.0" dependencies: - compress-brotli: ^1.3.8 json-buffer: 3.0.1 - checksum: 237952f5faa2ed08da36677d7a3faae48b7e3c305264698cbf4480443f293a2f0c6c63c1d05f5cd4a842ee864dbb395745e6636fecd07489565776a22de7b8d6 + checksum: d294873cf88ec8f691e5edeb7b4b884f886c5f021a01902a0e243c362449db2b55419d7fb7187d059add747b7398321e39e44d391b65f94935174ce13452714d languageName: node linkType: hard @@ -28097,7 +28370,7 @@ __metadata: languageName: node linkType: hard -"lodash-es@npm:^4.17.21": +"lodash-es@npm:^4.17.15, lodash-es@npm:^4.17.21": version: 4.17.21 resolution: "lodash-es@npm:4.17.21" checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 @@ -28314,13 +28587,6 @@ __metadata: languageName: node linkType: hard -"lodash.throttle@npm:^4.1.1": - version: 4.1.1 - resolution: "lodash.throttle@npm:4.1.1" - checksum: 129c0a28cee48b348aef146f638ef8a8b197944d4e9ec26c1890c19d9bf5a5690fe11b655c77a4551268819b32d27f4206343e30c78961f60b561b8608c8c805 - languageName: node - linkType: hard - "lodash.union@npm:^4.6.0": version: 4.6.0 resolution: "lodash.union@npm:4.6.0" @@ -28335,7 +28601,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.13, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.17.5, lodash@npm:^4.7.0, lodash@npm:~4.17.0, lodash@npm:~4.17.15, lodash@npm:~4.17.4": +"lodash@npm:4.17.21, lodash@npm:^4.17.10, lodash@npm:^4.17.11, lodash@npm:^4.17.13, lodash@npm:^4.17.14, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4, lodash@npm:^4.7.0, lodash@npm:~4.17.0, lodash@npm:~4.17.15": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: eb835a2e51d381e561e508ce932ea50a8e5a68f4ebdd771ea240d3048244a8d13658acbd502cd4829768c56f2e16bdd4340b9ea141297d472517b83868e677f7 @@ -28532,12 +28798,12 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.26.1": - version: 0.26.1 - resolution: "magic-string@npm:0.26.1" +"magic-string@npm:^0.26.6": + version: 0.26.7 + resolution: "magic-string@npm:0.26.7" dependencies: sourcemap-codec: ^1.4.8 - checksum: 23f21f5734346ddfbabd7b9834e3ecda3521e3e1db81166c1513b45b729489bbed1eafa8cd052c7db7fdc7c68ebc5c03bc00dd5a23697edda15dbecaf8c98397 + checksum: 89b0d60cbb32bbf3d1e23c46ea93db082d18a8230b972027aecb10a40bba51be519ecce0674f995571e3affe917b76b09f59d8dbc9a1b2c9c4102a2b6e8a2b01 languageName: node linkType: hard @@ -28717,7 +28983,7 @@ __metadata: languageName: node linkType: hard -"marked@npm:^4.0.10, marked@npm:^4.0.14": +"marked@npm:^4.0.10": version: 4.1.0 resolution: "marked@npm:4.1.0" bin: @@ -28726,6 +28992,15 @@ __metadata: languageName: node linkType: hard +"marked@npm:^4.0.14": + version: 4.1.1 + resolution: "marked@npm:4.1.1" + bin: + marked: bin/marked.js + checksum: 717e3357952ee53de831bf0eb110ed075bebca2376c58bcdf7ee523ef540d45308ad6d51b2c933da0968832ea4386f31c142ca65443e77c098e84f6cce73e418 + languageName: node + linkType: hard + "matcher@npm:^3.0.0": version: 3.0.0 resolution: "matcher@npm:3.0.0" @@ -28764,13 +29039,6 @@ __metadata: languageName: node linkType: hard -"math-expression-evaluator@npm:^1.2.14": - version: 1.2.22 - resolution: "math-expression-evaluator@npm:1.2.22" - checksum: 97b46e3311025699209d5178ea197e4df8286ac378075f105ab0cbc467f55a926a522a7e5a5ce8fc2d88881b6d32f1e3eacd81f34f265e7907cc5715e0f61a38 - languageName: node - linkType: hard - "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -29615,7 +29883,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:^3.0.2, minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -29635,13 +29903,20 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6": +"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.6": version: 1.2.6 resolution: "minimist@npm:1.2.6" checksum: d15428cd1e11eb14e1233bcfb88ae07ed7a147de251441d61158619dfb32c4d7e9061d09cab4825fdee18ecd6fce323228c8c47b5ba7cd20af378ca4048fb3fb languageName: node linkType: hard +"minimist@npm:^1.2.5": + version: 1.2.7 + resolution: "minimist@npm:1.2.7" + checksum: 7346574a1038ca23c32e02252f603801f09384dd1d78b69a943a4e8c2c28730b80e96193882d3d3b22a063445f460e48316b29b8a25addca2d7e5e8f75478bec + languageName: node + linkType: hard + "minimisted@npm:^2.0.0": version: 2.0.1 resolution: "minimisted@npm:2.0.1" @@ -29925,8 +30200,8 @@ __metadata: linkType: hard "msw@npm:^0.47.0": - version: 0.47.3 - resolution: "msw@npm:0.47.3" + version: 0.47.4 + resolution: "msw@npm:0.47.4" dependencies: "@mswjs/cookies": ^0.2.2 "@mswjs/interceptors": ^0.17.5 @@ -29945,7 +30220,7 @@ __metadata: outvariant: ^1.3.0 path-to-regexp: ^6.2.0 statuses: ^2.0.0 - strict-event-emitter: ^0.2.0 + strict-event-emitter: ^0.2.6 type-fest: ^2.19.0 yargs: ^17.3.1 peerDependencies: @@ -29955,7 +30230,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 1be018c7b2eff982409967cccb5c604e45f65710ee9698bab57fbe794f8426d1a4d33e52b75ef395c6d226948c799241c7c2c7748ec4f5b741e7f25bcbafbd1e + checksum: 10ff632641d40384d6622abf4df6399e4ae649db0f676b5d1ee2d0a515ec96f33abe9d4fecba08cdba4b2e43255af419da9eefc020d40a7e10669d0906457197 languageName: node linkType: hard @@ -30062,16 +30337,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.1.23": - version: 3.3.1 - resolution: "nanoid@npm:3.3.1" - bin: - nanoid: bin/nanoid.cjs - checksum: 4ef0969e1bbe866fc223eb32276cbccb0961900bfe79104fa5abe34361979dead8d0e061410a5c03bc3d47455685adf32c09d6f27790f4a6898fb51f7df7ec86 - languageName: node - linkType: hard - -"nanoid@npm:^3.3.4": +"nanoid@npm:^3.1.23, nanoid@npm:^3.3.4": version: 3.3.4 resolution: "nanoid@npm:3.3.4" bin: @@ -31562,17 +31828,17 @@ __metadata: linkType: hard "passport-saml@npm:^3.1.2": - version: 3.2.1 - resolution: "passport-saml@npm:3.2.1" + version: 3.2.4 + resolution: "passport-saml@npm:3.2.4" dependencies: - "@xmldom/xmldom": ^0.7.5 + "@xmldom/xmldom": ^0.7.6 debug: ^4.3.2 passport-strategy: ^1.0.0 xml-crypto: ^2.1.3 xml-encryption: ^2.0.0 xml2js: ^0.4.23 xmlbuilder: ^15.1.1 - checksum: 7236c82f2b16240acc6e026cbe171e130e703d0e85c7f83733be0137218a62f61dee72d34cea00ea02a0db4b823497e574ef4b6585a316097b926eb760f90bb8 + checksum: 8e885af4d44c2d862b2ea0d051ab2a36bc6f9a70e62f90daf7ce4eefd126ac2ab4d5fc070693eba05f5e1be248af23fa018611bbfa7fad31708371f387f5dd77 languageName: node linkType: hard @@ -32532,7 +32798,18 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.1.0, postcss@npm:^8.4.7": +"postcss@npm:^8.1.0": + version: 8.4.17 + resolution: "postcss@npm:8.4.17" + dependencies: + nanoid: ^3.3.4 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: a6d9096dd711e17f7b1d18ff5dcb4fdedf3941d5a3dc8b0e4ea873b8f31972d57f73d6da9a8aed7ff389eb52190ed34f6a94f299a7f5ddc68b08a24a48f77eb9 + languageName: node + linkType: hard + +"postcss@npm:^8.4.7": version: 8.4.16 resolution: "postcss@npm:8.4.16" dependencies: @@ -32815,7 +33092,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.5.7, prop-types@npm:^15.5.8, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.5.7, prop-types@npm:^15.5.8, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -33589,6 +33866,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.2.0": + version: 18.2.0 + resolution: "react-is@npm:18.2.0" + checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e + languageName: node + linkType: hard + "react-lifecycles-compat@npm:^3.0.4": version: 3.0.4 resolution: "react-lifecycles-compat@npm:3.0.4" @@ -33650,20 +33934,6 @@ __metadata: languageName: node linkType: hard -"react-resize-detector@npm:^2.3.0": - version: 2.3.0 - resolution: "react-resize-detector@npm:2.3.0" - dependencies: - lodash.debounce: ^4.0.8 - lodash.throttle: ^4.1.1 - prop-types: ^15.6.0 - resize-observer-polyfill: ^1.5.0 - peerDependencies: - react: ^0.14.7 || ^15.0.0 || ^16.0.0 - checksum: c409cc31edaa6bb943aa1098c5830f1c430b448ebc7d2a1c4c14b1ad0eb4ea0d0bd1f48e4315569692af52270f7dc8d7fb0862c1b6121411589c4d5cd39fc1e4 - languageName: node - linkType: hard - "react-resize-detector@npm:^6.6.3": version: 6.7.8 resolution: "react-resize-detector@npm:6.7.8" @@ -33678,6 +33948,18 @@ __metadata: languageName: node linkType: hard +"react-resize-detector@npm:^7.1.2": + version: 7.1.2 + resolution: "react-resize-detector@npm:7.1.2" + dependencies: + lodash: ^4.17.21 + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 55f4abad7f7523d16b081b5ab20f75c539a54a08253ce7e9df473d48386f42ceca6c31584ba9fa26e3528b498ef6685ec77fb9a22cffc97df05fb326d0bf1b26 + languageName: node + linkType: hard + "react-router-beta@npm:react-router@6.0.0-beta.0, react-router@npm:6.0.0-beta.0": version: 6.0.0-beta.0 resolution: "react-router@npm:6.0.0-beta.0" @@ -33737,21 +34019,6 @@ __metadata: languageName: node linkType: hard -"react-smooth@npm:^1.0.5": - version: 1.0.5 - resolution: "react-smooth@npm:1.0.5" - dependencies: - lodash: ~4.17.4 - prop-types: ^15.6.0 - raf: ^3.4.0 - react-transition-group: ^2.5.0 - peerDependencies: - react: ^15.0.0 || ^16.0.0 - react-dom: ^15.0.0 || ^16.0.0 - checksum: 29a2a00e09e0f5d4dea4424f945dc7dab9b611184c60b01bd24082b6293f9e5e4bafe0e4386d4c52a2c9c651caff3eeb5ce0966917a0bd06ba1165306853c822 - languageName: node - linkType: hard - "react-smooth@npm:^2.0.0": version: 2.0.0 resolution: "react-smooth@npm:2.0.0" @@ -33767,6 +34034,20 @@ __metadata: languageName: node linkType: hard +"react-smooth@npm:^2.0.1": + version: 2.0.1 + resolution: "react-smooth@npm:2.0.1" + dependencies: + fast-equals: ^2.0.0 + react-transition-group: 2.9.0 + peerDependencies: + prop-types: ^15.6.0 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 65678491cbd506573f2dba82778ebf8259714d68dd227e0ee7c0e204bcbc7839cf97401620b4223814066581f1dce0493f97162a03dc2a68058b5a7ad2b41085 + languageName: node + linkType: hard + "react-sparklines@npm:^1.7.0": version: 1.7.0 resolution: "react-sparklines@npm:1.7.0" @@ -33820,7 +34101,7 @@ __metadata: languageName: node linkType: hard -"react-transition-group@npm:2.9.0, react-transition-group@npm:^2.5.0": +"react-transition-group@npm:2.9.0": version: 2.9.0 resolution: "react-transition-group@npm:2.9.0" dependencies: @@ -34068,7 +34349,7 @@ __metadata: languageName: node linkType: hard -"recharts-scale@npm:^0.4.2, recharts-scale@npm:^0.4.4": +"recharts-scale@npm:^0.4.4": version: 0.4.5 resolution: "recharts-scale@npm:0.4.5" dependencies: @@ -34077,25 +34358,29 @@ __metadata: languageName: node linkType: hard -"recharts@npm:^1.8.5": - version: 1.8.5 - resolution: "recharts@npm:1.8.5" +"recharts@npm:^2.0.0": + version: 2.1.15 + resolution: "recharts@npm:2.1.15" dependencies: + "@types/d3-interpolate": ^2.0.0 + "@types/d3-scale": ^3.0.0 + "@types/d3-shape": ^2.0.0 classnames: ^2.2.5 - core-js: ^2.6.10 - d3-interpolate: ^1.3.0 - d3-scale: ^2.1.0 - d3-shape: ^1.2.0 - lodash: ^4.17.5 - prop-types: ^15.6.0 - react-resize-detector: ^2.3.0 - react-smooth: ^1.0.5 - recharts-scale: ^0.4.2 - reduce-css-calc: ^1.3.0 + d3-interpolate: ^2.0.0 + d3-scale: ^3.0.0 + d3-shape: ^2.0.0 + eventemitter3: ^4.0.1 + lodash: ^4.17.19 + react-is: ^16.10.2 + react-resize-detector: ^7.1.2 + react-smooth: ^2.0.1 + recharts-scale: ^0.4.4 + reduce-css-calc: ^2.1.8 peerDependencies: - react: ^15.0.0 || ^16.0.0 - react-dom: ^15.0.0 || ^16.0.0 - checksum: bdc1b712b90a3e5f5fcc1bbb7755dde8666c056f67cad90f4e4a2dd726ed45f9fcc82fce4a679ae168c2ed2e243a4c1acffb3e1f45ad4d96c47bcf9e764b5c8f + prop-types: ^15.6.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 2f0be89eca6da8a6d899dac8676de145001353b6072b7853d3f04091c38604f80b241b4ef04d4b28268a95df4a8134b565a192077cb6a2903048fc90f19a5def languageName: node linkType: hard @@ -34143,11 +34428,11 @@ __metadata: linkType: hard "recursive-readdir@npm:^2.2.2": - version: 2.2.2 - resolution: "recursive-readdir@npm:2.2.2" + version: 2.2.3 + resolution: "recursive-readdir@npm:2.2.3" dependencies: - minimatch: 3.0.4 - checksum: a6b22994d76458443d4a27f5fd7147ac63ad31bba972666a291d511d4d819ee40ff71ba7524c14f6a565b8cfaf7f48b318f971804b913cf538d58f04e25d1fee + minimatch: ^3.0.5 + checksum: 88ec96e276237290607edc0872b4f9842837b95cfde0cdbb1e00ba9623dfdf3514d44cdd14496ab60a0c2dd180a6ef8a3f1c34599e6cf2273afac9b72a6fb2b5 languageName: node linkType: hard @@ -34177,17 +34462,6 @@ __metadata: languageName: node linkType: hard -"reduce-css-calc@npm:^1.3.0": - version: 1.3.0 - resolution: "reduce-css-calc@npm:1.3.0" - dependencies: - balanced-match: ^0.4.2 - math-expression-evaluator: ^1.2.14 - reduce-function-call: ^1.0.1 - checksum: 72696db02ede8772fe5ba9c47c6d451557bea0d11525f4e54b6adfcc4e540558755bb0ad454824cb6b998f0bd940eb2ef594f35b3d509dfa37934d93ece0f707 - languageName: node - linkType: hard - "reduce-css-calc@npm:^2.1.8": version: 2.1.8 resolution: "reduce-css-calc@npm:2.1.8" @@ -34198,15 +34472,6 @@ __metadata: languageName: node linkType: hard -"reduce-function-call@npm:^1.0.1": - version: 1.0.3 - resolution: "reduce-function-call@npm:1.0.3" - dependencies: - balanced-match: ^1.0.0 - checksum: d0169016ea22b59d55fa3206507c8f2d009574abd0f9b86552035a8405d52f6d7d5b60d084c5950d6f2884df7de42f87a6260b1b386b79ede63bfc87ea0c3ce8 - languageName: node - linkType: hard - "redux-immutable@npm:^4.0.0": version: 4.0.0 resolution: "redux-immutable@npm:4.0.0" @@ -34625,7 +34890,7 @@ __metadata: languageName: node linkType: hard -"resize-observer-polyfill@npm:^1.5.0, resize-observer-polyfill@npm:^1.5.1": +"resize-observer-polyfill@npm:^1.5.1": version: 1.5.1 resolution: "resize-observer-polyfill@npm:1.5.1" checksum: 57e7f79489867b00ba43c9c051524a5c8f162a61d5547e99333549afc23e15c44fd43f2f318ea0261ea98c0eb3158cca261e6f48d66e1ed1cd1f340a43977094 @@ -34959,18 +35224,18 @@ __metadata: linkType: hard "rollup-plugin-dts@npm:^4.0.1": - version: 4.2.2 - resolution: "rollup-plugin-dts@npm:4.2.2" + version: 4.2.3 + resolution: "rollup-plugin-dts@npm:4.2.3" dependencies: - "@babel/code-frame": ^7.16.7 - magic-string: ^0.26.1 + "@babel/code-frame": ^7.18.6 + magic-string: ^0.26.6 peerDependencies: rollup: ^2.55 typescript: ^4.1 dependenciesMeta: "@babel/code-frame": optional: true - checksum: cf4b45f6cca442a5f44af0f0fb567c8fc540ecb792c763571d1bcda9bf495803bcc8d4eaef451a2dd32f7f391eb822e2b96cc6b86b096db54a4d3935236fd8da + checksum: b1de94202d0574e7c12105bf0d013e7142c1b9b74d6b83d194d870dcdc281e90cff45ed47a0ab1c62280cc25e75f522e1278ec0ba89c8f75b8bcb56dc98c2c63 languageName: node linkType: hard @@ -36545,6 +36810,15 @@ __metadata: languageName: node linkType: hard +"strict-event-emitter@npm:^0.2.6": + version: 0.2.7 + resolution: "strict-event-emitter@npm:0.2.7" + dependencies: + events: ^3.3.0 + checksum: 111691e7d3fce0810586ccd8e8234af883ad3b121ef69091c7e260c32299d1ba085a95238ad09b43478bc5e9e80370f2fcb8114716e343be6f44bfc08fab4142 + languageName: node + linkType: hard + "strict-uri-encode@npm:^2.0.0": version: 2.0.0 resolution: "strict-uri-encode@npm:2.0.0" @@ -37028,8 +37302,8 @@ __metadata: linkType: hard "swagger-ui-react@npm:^4.11.1": - version: 4.14.2 - resolution: "swagger-ui-react@npm:4.14.2" + version: 4.14.3 + resolution: "swagger-ui-react@npm:4.14.3" dependencies: "@babel/runtime-corejs3": ^7.18.9 "@braintree/sanitize-url": =6.0.0 @@ -37067,7 +37341,7 @@ __metadata: peerDependencies: react: ">=17.0.0" react-dom: ">=17.0.0" - checksum: ee8d19bdfd32032926d86a1c547b5fd423f658e0e4c29bd2b96808143f7ef5c1e094fb6c2a0a6e3f20b8a5d7e0a7732f81a31fc10f8e6e6702b8b1333a40164c + checksum: ae8e932ff7eb0b4be71fd1ad7f00ef3eb461636a83985bd36ea28647f60769e6234a9921ff2f84aa33c370f30b8fa1c30a6ca7cf3c4a6ded963aaf9a337aa197 languageName: node linkType: hard @@ -37654,6 +37928,17 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:^3.0.1": + version: 3.0.1 + resolution: "tough-cookie@npm:3.0.1" + dependencies: + ip-regex: ^2.1.0 + psl: ^1.1.28 + punycode: ^2.1.1 + checksum: 796f6239bce5674a1267b19f41972a2602a2a23715817237b5922b0dc2343512512eea7d41d29210a4ec545f8ef32173bbbf01277dd8ec3ae3841b19cbe69f67 + languageName: node + linkType: hard + "tough-cookie@npm:^4.0.0": version: 4.0.0 resolution: "tough-cookie@npm:4.0.0" @@ -37855,7 +38140,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": +"tslib@npm:^1.10.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd @@ -38674,16 +38959,15 @@ __metadata: linkType: hard "util@npm:^0.12.3, util@npm:^0.12.4": - version: 0.12.4 - resolution: "util@npm:0.12.4" + version: 0.12.5 + resolution: "util@npm:0.12.5" dependencies: inherits: ^2.0.3 is-arguments: ^1.0.4 is-generator-function: ^1.0.7 is-typed-array: ^1.1.3 - safe-buffer: ^5.1.2 which-typed-array: ^1.1.2 - checksum: 8eac7a6e6b341c0f1b3eb73bbe5dfcae31a7e9699c8fc3266789f3e95f7637946a7700dcf1904dbd3749a58a36760ebf7acf4bb5b717f7468532a8a79f44eff0 + checksum: 705e51f0de5b446f4edec10739752ac25856541e0254ea1e7e45e5b9f9b0cb105bc4bd415736a6210edc68245a7f903bf085ffb08dd7deb8a0e847f60538a38a languageName: node linkType: hard @@ -39430,7 +39714,7 @@ __metadata: languageName: node linkType: hard -"winston@npm:^3.2.1, winston@npm:^3.7.2, winston@npm:^3.8.1": +"winston@npm:^3.2.1": version: 3.8.2 resolution: "winston@npm:3.8.2" dependencies: @@ -40124,6 +40408,15 @@ __metadata: languageName: node linkType: hard +"zod-to-json-schema@npm:^3.18.1": + version: 3.18.1 + resolution: "zod-to-json-schema@npm:3.18.1" + peerDependencies: + zod: ^3.18.0 + checksum: e55d0de83b50fbd1caa7541d037858815964477b52a9e6495496e447107386cf16e2c08b007fcfbffd7fbe069ca2c19018425a53eaee36aff5dda942d3db71f4 + languageName: node + linkType: hard + "zod@npm:^3.11.6, zod@npm:^3.9.5": version: 3.18.0 resolution: "zod@npm:3.18.0"