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/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/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/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/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-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/create-app-1667233110.md b/.changeset/create-app-1667233110.md new file mode 100644 index 0000000000..b50d431d4b --- /dev/null +++ b/.changeset/create-app-1667233110.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Bumped create-app version. diff --git a/.changeset/curvy-islands-marry.md b/.changeset/curvy-islands-marry.md deleted file mode 100644 index 2d3bad31ac..0000000000 --- a/.changeset/curvy-islands-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-github-pull-requests-board': patch ---- - -Replace the momentjs dependency with luxon. 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/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/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-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/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/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/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/grumpy-clouds-drum.md b/.changeset/grumpy-clouds-drum.md new file mode 100644 index 0000000000..cf2d5d2a32 --- /dev/null +++ b/.changeset/grumpy-clouds-drum.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +- The GitLab Project ID for the `publish:gitlab:merge-request` action is now passed through the query parameter `project` in the `repoUrl`. It still allows people to not use the `projectid` and use the `repoUrl` with the `owner` and `repo` query parameters instead. This makes it easier to publish to repositories instead of writing the full path to the project. 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/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/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/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/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-cobras-sell.md b/.changeset/lucky-cobras-sell.md new file mode 100644 index 0000000000..16d86d52e8 --- /dev/null +++ b/.changeset/lucky-cobras-sell.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-playlist': patch +'@backstage/plugin-playlist-backend': patch +--- + +Updated Playlist plugin docs: + +- Updated `playlist` plugin README to include note about installing backend plugin and added images for the various features +- Updated `playlist-backend` plugin README to remove `IdentityClient` import in example as it is not used and made minor change to headings 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/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/metal-hairs-mix.md b/.changeset/metal-hairs-mix.md new file mode 100644 index 0000000000..f795c37506 --- /dev/null +++ b/.changeset/metal-hairs-mix.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-azure-devops-backend': patch +--- + +- Adjusted the asset parser to accept case sensitive +- Fixed fetching data that was using the deprecated function 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/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/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 0bdcd4a8d9..9aa5bbdfdf 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -173,7 +173,80 @@ "@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-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": [] + "changesets": [ + "analyze-software-creation", + "analyze-software-exploration", + "beige-gorillas-sip", + "big-islands-add", + "brave-eels-allow", + "brown-days-pretend", + "calm-bottles-happen", + "chatty-planets-flash", + "clean-feet-remain", + "clean-planets-rhyme", + "create-app-1667233110", + "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-clouds-drum", + "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-cobras-sell", + "lucky-spoons-hide", + "mean-files-fly", + "metal-dogs-swim", + "metal-hairs-mix", + "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/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/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/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/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-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/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-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/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-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-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/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/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/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/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/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/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml new file mode 100644 index 0000000000..782b5fdfa0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -0,0 +1,84 @@ +name: '๐Ÿ› Bug Report' +description: 'Submit a bug report to help us improve' +title: '๐Ÿ› Bug Report: ' +labels: + - bug +body: + - type: markdown + attributes: + value: We value your time and effort to submit this bug report. ๐Ÿ™ + - type: textarea + id: description + validations: + required: true + attributes: + label: '๐Ÿ“œ Description' + description: 'A clear and concise description of what the bug is.' + placeholder: 'It bugs out when ...' + - type: textarea + id: expected-behavior + validations: + required: true + attributes: + label: '๐Ÿ‘ Expected behavior' + description: 'What did you think should happen?' + placeholder: 'It should ...' + - type: textarea + id: actual-behavior + validations: + required: true + attributes: + label: '๐Ÿ‘Ž Actual Behavior with Screenshots' + description: 'What did actually happen? Add screenshots, if applicable.' + placeholder: 'It actually ...' + - type: textarea + id: steps-to-reproduce + validations: + required: true + attributes: + label: '๐Ÿ‘Ÿ Reproduction steps' + description: 'How do you trigger this bug? Please walk us through it step by step.' + placeholder: + "Provide a link to a live example, or an unambiguous set of steps to reproduce this bug. Include code or configuration to reproduce, if relevant.\n + 1. Go to '...'\n + 2. Click on '....'\n + 3. Scroll down to '....'" + - type: textarea + id: context + validations: + required: false + attributes: + label: '๐Ÿ“ƒ Provide the context for the Bug.' + description: 'How has this issue affected you? What are you trying to accomplish?' + placeholder: 'Providing context (e.g. links to configuration settings, stack trace or log data) helps us come up with a solution that is most useful in the real world.' + - type: textarea + id: environment + validations: + required: false + attributes: + label: '๐Ÿ–ฅ๏ธ Your Environment' + description: 'Provide Browser Information + Provide Output of `yarn backstage-cli info`' + placeholder: 'Include as many relevant details about the environment you experienced the bug in.' + - type: checkboxes + id: no-duplicate-issues + attributes: + label: '๐Ÿ‘€ Have you spent some time to check if this bug has been raised before?' + options: + - label: "I checked and didn't find similar issue" + required: true + - type: checkboxes + id: read-code-of-conduct + attributes: + label: '๐Ÿข Have you read the Code of Conduct?' + options: + - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)' + required: true + - type: dropdown + attributes: + label: Are you willing to submit PR? + description: This is absolutely not required, but we are happy to guide you in the contribution process. + options: + - Yes I am willing to submit a PR! + - No, but I'm happy to collaborate on a PR with someone else + - No, I don't have time to work on this right now diff --git a/.github/ISSUE_TEMPLATE/bug_template.md b/.github/ISSUE_TEMPLATE/bug_template.md deleted file mode 100644 index 04f8fd68d0..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_template.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -name: 'Bug Report' -about: 'Create Bug Report' -labels: bug ---- - - - -## Expected Behavior - - - -## Actual Behavior - - - -## Steps to Reproduce - - - - -1. Step 1 -2. Step 2 -3. ... - -## Context - - - - - -## Your Environment - - - -- Browser Information: - -- Output of `yarn backstage-cli info`: - -```text - -``` diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml new file mode 100644 index 0000000000..9cac5055f1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -0,0 +1,54 @@ +name: ๐Ÿš€ Feature +description: 'Submit a proposal for a new feature' +title: '๐Ÿš€ Feature: ' +labels: [enhancement] +body: + - type: markdown + attributes: + value: | + We value your time and efforts to submit this Feature request form. ๐Ÿ™ + - type: textarea + id: feature-description + validations: + required: true + attributes: + label: '๐Ÿ”– Feature description' + description: 'A clear and concise description of what the feature is.' + placeholder: 'You should add ...' + - type: textarea + id: context + validations: + required: true + attributes: + label: '๐ŸŽค Context' + description: 'Please explain why this feature should be implemented and how it would be used. Add examples, if applicable.' + placeholder: 'In my use-case, ...' + - type: textarea + id: implementation + attributes: + label: 'โœŒ๏ธ Possible Implementation' + description: 'A clear and concise description of what you want to happen.' + placeholder: 'Not obligatory, but ideas as to the implementation of the addition or change' + - type: checkboxes + id: no-duplicate-issues + attributes: + label: '๐Ÿ‘€ Have you spent some time to check if this feature request has been raised before?' + options: + - label: "I checked and didn't find similar issue" + required: true + - type: checkboxes + id: read-code-of-conduct + attributes: + label: '๐Ÿข Have you read the Code of Conduct?' + options: + - label: 'I have read the [Contributing Guidelines](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)' + required: true + - type: dropdown + id: willing-to-submit-pr + attributes: + label: Are you willing to submit PR? + description: This is absolutely not required, but we are happy to guide you in the contribution process. + options: + - Yes I am willing to submit a PR! + - No, but I'm happy to collaborate on a PR with someone else + - No, I don't have time to work on this right now diff --git a/.github/ISSUE_TEMPLATE/feature_template.md b/.github/ISSUE_TEMPLATE/feature_template.md deleted file mode 100644 index d70622bf52..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_template.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: 'Feature Request' -about: 'Suggest new features and changes' -labels: enhancement ---- - - - -## Feature Suggestion - - - - -## Possible Implementation - - - -## Context - - - - diff --git a/.github/ISSUE_TEMPLATE/plugin.yaml b/.github/ISSUE_TEMPLATE/plugin.yaml new file mode 100644 index 0000000000..9f3a708b41 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/plugin.yaml @@ -0,0 +1,52 @@ +name: ๐Ÿ”Œ Plugin +description: 'Submit a proposal for a new Plugin' +title: '๐Ÿ”Œ Plugin: ' +labels: [plugin] +body: + - type: markdown + attributes: + value: | + We value your time and efforts to submit this Plugin request form. ๐Ÿ™ + - type: textarea + id: plugin-summary + validations: + required: true + attributes: + label: '๐Ÿ”– Summary' + description: 'Provide a general summary of the plugin and how it should work' + placeholder: 'You should add ...' + - type: textarea + id: website + attributes: + label: '๐ŸŒ Project website (if applicable)' + description: 'Add a link to the open source project or product this plugin will integrate with, if existing' + placeholder: 'Website Link is ...' + - type: textarea + id: context + attributes: + label: 'โœŒ๏ธ Context' + description: 'A clear and concise description about the Plugin.' + placeholder: 'Providing additional context' + - type: checkboxes + id: no-duplicate-issues + attributes: + label: '๐Ÿ‘€ Have you spent some time to check if this plugin request has been raised before?' + options: + - label: "I checked and didn't find similar issue" + required: true + - type: checkboxes + id: read-code-of-conduct + attributes: + label: '๐Ÿข Have you read the Code of Conduct?' + options: + - label: 'I have read the [Contributing Guidelines](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)' + required: true + - type: dropdown + id: willing-to-submit-pr + attributes: + label: Are you willing to submit PR? + description: This is absolutely not required, but we are happy to guide you in the contribution process. + options: + - Yes I am willing to submit a PR! + - No, but I'm happy to collaborate on a PR with someone else + - No, I don't have time to work on this right now diff --git a/.github/ISSUE_TEMPLATE/plugin_template.md b/.github/ISSUE_TEMPLATE/plugin_template.md deleted file mode 100644 index 75f0828848..0000000000 --- a/.github/ISSUE_TEMPLATE/plugin_template.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -name: 'Plugin suggestion' -about: 'Suggest a new Backstage plugin' -title: '[Plugin] THE PLUGIN NAME' -labels: plugin ---- - - - - -## Summary - - - -## Project website (if applicable) - - - -## Context - - diff --git a/.github/ISSUE_TEMPLATE/rfc.yaml b/.github/ISSUE_TEMPLATE/rfc.yaml new file mode 100644 index 0000000000..775c64e674 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/rfc.yaml @@ -0,0 +1,51 @@ +name: ๐Ÿ’ฌ RFC +description: 'Request For Comments (RFC) from the community' +title: '๐Ÿ’ฌ RFC: ' +labels: [rfc] +body: + - type: markdown + attributes: + value: | + We value your time and efforts to submit this RFC form. ๐Ÿ™ + - type: textarea + id: rfc-need + validations: + required: true + attributes: + label: '๐Ÿ”– Need' + description: 'Let us know why are you proposing this change' + placeholder: 'The problem weโ€™re trying to address and the benefits/impact we expect to get from this are ...' + - type: textarea + id: proposal + validations: + required: true + attributes: + label: '๐ŸŽ‰ Proposal' + description: 'Describe the proposal in as much detail as needed for reviewers to give concrete feedback.' + placeholder: 'Take special care in this section to describe any implications on data privacy or security.' + - type: textarea + id: alternatives + attributes: + label: 'ใ€ฝ๏ธ Alternatives' + description: 'What alternatives to the proposed solution were considered?' + placeholder: 'What criteria/data was used to discard these?' + - type: textarea + id: risk + attributes: + label: 'โŒ Risks' + description: 'What other things happening could conflict or compete (for example for resources) with the proposal?' + placeholder: 'What risk are there and how do we plan to handle them?' + - type: checkboxes + id: no-duplicate-issues + attributes: + label: '๐Ÿ‘€ Have you spent some time to check if this RFC has been raised before?' + options: + - label: "I checked and didn't find similar issue" + required: true + - type: checkboxes + id: read-code-of-conduct + attributes: + label: '๐Ÿข Have you read the Code of Conduct?' + options: + - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)' + required: true diff --git a/.github/ISSUE_TEMPLATE/rfc_template.md b/.github/ISSUE_TEMPLATE/rfc_template.md deleted file mode 100644 index c4990ee5d6..0000000000 --- a/.github/ISSUE_TEMPLATE/rfc_template.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: 'RFC' -about: 'Request For Comments (RFC) from the community' -labels: rfc -title: '[RFC] ' ---- - -**Status:** Open for comments - - - -## Need - - - -## Proposal - - - -## Alternatives - - - -## Risks - - diff --git a/.github/ISSUE_TEMPLATE/ux-component_template.md b/.github/ISSUE_TEMPLATE/ux-component_template.md deleted file mode 100644 index 95b8f47615..0000000000 --- a/.github/ISSUE_TEMPLATE/ux-component_template.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: 'UX Component' -about: 'For designers to request UX components to be added to the Backstage Storybook' -labels: design ---- - -## ๐Ÿ—’ General - - - - -## ๐Ÿ’ป Usage - - - -## ๐Ÿ“ Specs - - - - -## ๐Ÿ”ฎ Future - - diff --git a/.github/ISSUE_TEMPLATE/ux_component.yaml b/.github/ISSUE_TEMPLATE/ux_component.yaml new file mode 100644 index 0000000000..453f1e8278 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ux_component.yaml @@ -0,0 +1,53 @@ +name: ๐Ÿฆ„ UX Component +description: 'For designers to request UX components to be added to the Backstage Storybook' +title: '๐Ÿฆ„ UX Component: ' +labels: [design] +body: + - type: markdown + attributes: + value: | + We value your time and efforts to submit this RFC form. ๐Ÿ™ + - type: textarea + id: ux-general + validations: + required: true + attributes: + label: '๐Ÿ”– General' + description: 'Write a nice note to the community requesting the creation of a new component.' + placeholder: 'Include an image of your component. Bonus points for a GIF!' + - type: textarea + id: usage + validations: + required: true + attributes: + label: '๐Ÿ’ป Usage' + description: "Tell us what's the point of this component/pattern is." + placeholder: 'How does it help? How should it work? Any rules?' + - type: textarea + id: specs + validations: + required: true + attributes: + label: '๐Ÿ“ Specs' + description: 'Include images that detail the redlines for your component.' + placeholder: "Once we get our Figma workspace set up, we'll be posting the Figma files rather than doing specs by hand." + - type: textarea + id: future + attributes: + label: '๐Ÿ”ฎ Future' + description: 'List out any upcoming, exciting functionality for this component.' + placeholder: 'The component will have ...' + - type: checkboxes + id: no-duplicate-issues + attributes: + label: '๐Ÿ‘€ Have you spent some time to check if this UX Component request has been raised before?' + options: + - label: "I checked and didn't find similar issue" + required: true + - type: checkboxes + id: read-code-of-conduct + attributes: + label: '๐Ÿข Have you read the Code of Conduct?' + options: + - label: 'I have read the [Code of Conduct](https://github.com/backstage/backstage/blob/master/CODE_OF_CONDUCT.md)' + required: true 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 1e90802d35..839afaa256 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -1,11 +1,14 @@ abc accessors +ACLs addon addons ADRs +airbrake Airbrake Airbrakes -airbrake +Alaria +Alef Anddddd Apdex api @@ -17,6 +20,7 @@ autoscaling Autoscaling autoselect Avro +backend's backported backporting Bigtable @@ -24,9 +28,9 @@ Billett bitbucket Bitrise Blackbox -Brex bool boolean +Brex builtins callout CDNs @@ -40,6 +44,7 @@ CI/CD classname cli cloudbuild +Cloudflare Cloudformation cncf Cobertura @@ -47,11 +52,11 @@ codeblocks Codecov codehilite Codehilite -codescene -CodeScene codemod codemods codeowners +codescene +CodeScene composability composable config @@ -73,8 +78,8 @@ Debounce debuggability declaratively deduplicated -deps dependabot +deps destructured destructuring dev @@ -88,14 +93,18 @@ dockerfiles Dockerize dockerode Docusaurus +DOMPurify +don'ts dynatrace Dynatrace ecco +elasticsearch env Env -elasticsearch esbuild eslint +ESModule +ESModules etag Expedia facto @@ -111,6 +120,8 @@ Francesco gerrit Gerrit gitbeaker +gitea +Gitea github Gitiles gitlab @@ -132,23 +143,26 @@ hotspots http https Iain +Iglesias iLert img incentivised Indal +indexable inlined inlinehilite +integrator's interop JaCoCo JavaScript jenkins Jira -JWTs jq js json jsonnet jsx +JWTs Kaewkasi Keyv Knex @@ -168,6 +182,7 @@ lunr Luxon magiclink mailto +maintainer's maintainership makefile md @@ -222,6 +237,7 @@ orgs pagerduty pageview parallelization +parseable Patrik Peloton performant @@ -256,14 +272,13 @@ rebase Recharts Redash replicasets -statefulsets repo Repo repos rerender rerenders -Reusability reusability +Reusability roadmaps rollbar Rollbar @@ -286,9 +301,9 @@ semver serializable Serverless shoutout -siloed SIG SIGs +siloed Sinon Snyk Sonarqube @@ -301,11 +316,17 @@ Spotify sqlite squidfunk src +statefulsets stdout -stringify +storable stringified +stringify subcomponent subcomponents +subfolder +subfolders +subheader +subheaders subkey subroutes subtree @@ -313,10 +334,10 @@ superfences Superfences superset supertype -storable SVGs talkdesk Talkdesk +Tanzu tasklist techdocs Telenor @@ -326,6 +347,7 @@ templater Templater templaters Templaters +TFRecord theia thumbsup todo @@ -342,6 +364,7 @@ transpiled transpiler transpilers truthy +TSDoc typeahead ui unbreak @@ -356,14 +379,17 @@ upvote URIs URLs utils +Valentina validator validators varchar +VMware VPCs VSCode Wayfair Weaveworks Webpack +widget's winston www WWW @@ -373,7 +399,7 @@ xyz yaml Zalando Zhou +zod +Zolotusky zoomable zsh -Alef -Cloudflare 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/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 089474e5b8..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 * * *' @@ -13,9 +14,6 @@ jobs: matrix: node-version: [14.x] - env: - NODE_OPTIONS: --max-old-space-size=4096 - steps: - uses: actions/checkout@v3 @@ -38,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/ADOPTERS.md b/ADOPTERS.md index bb17754c95..2dffec64dc 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. | @@ -25,7 +25,7 @@ _You can do this by using the [Adopter form](https://info.backstage.spotify.com/ | [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. | @@ -214,4 +214,6 @@ _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 +| [Loft](https://loft.com.br) | [Augusto Hoffmann](mailto:augusto.hoffmann@loft.com.br) | We're using Backstage to give visibility and promote ownership of all our applications, resources and tools. Now moving to use it as a Developer Portal to create applications, AWS resources etc. | diff --git a/README.md b/README.md index 53f8275b88..8a2bd338d9 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![Discord](https://img.shields.io/discord/687207715902193673)](https://discord.gg/EBHEGzX) ![Code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg) [![Codecov](https://img.shields.io/codecov/c/github/backstage/backstage)](https://codecov.io/gh/backstage/backstage) -[![](https://img.shields.io/npm/v/@backstage/core?label=Version)](https://github.com/backstage/backstage/releases) +[![](https://img.shields.io/github/v/release/backstage/backstage)](https://github.com/backstage/backstage/releases) ## What is Backstage? @@ -52,8 +52,8 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how - [Code of Conduct](CODE_OF_CONDUCT.md) - This is how we roll - [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 +- [Newsletter](https://spoti.fi/backstagenewsletter) - Subscribe to our email newsletter +- [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/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/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/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/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 7dc0e326fa..55843f3b24 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -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/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/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 092a171223..4f032901a9 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -104,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 @@ -118,7 +125,8 @@ 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 + 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. @@ -139,6 +147,12 @@ This provider supports multiple organizations via unique provider IDs. - **`organization`**: Name of your organization account/workspace. If you want to add multiple organizations, you need to add one provider config each. +- **`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. 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 9407dc407a..d82836b9ae 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -11,7 +11,7 @@ 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'; 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/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/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.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/releases/v1.8.0-next.1-changelog.md b/docs/releases/v1.8.0-next.1-changelog.md new file mode 100644 index 0000000000..1f65dfdc9c --- /dev/null +++ b/docs/releases/v1.8.0-next.1-changelog.md @@ -0,0 +1,106 @@ +# Release v1.8.0-next.1 + +## @backstage/plugin-scaffolder-backend@1.8.0-next.1 + +### Minor Changes + +- 5921b5ce49: - The GitLab Project ID for the `publish:gitlab:merge-request` action is now passed through the query parameter `project` in the `repoUrl`. It still allows people to not use the `projectid` and use the `repoUrl` with the `owner` and `repo` query parameters instead. This makes it easier to publish to repositories instead of writing the full path to the project. + +## @backstage/create-app@0.4.33-next.1 + +### Patch Changes + +- Bumped create-app version. + +## @backstage/plugin-azure-devops-backend@0.3.17-next.1 + +### Patch Changes + +- 62f284e394: - Adjusted the asset parser to accept case sensitive + - Fixed fetching data that was using the deprecated function + +## @backstage/plugin-playlist@0.1.2-next.1 + +### Patch Changes + +- 605f269f0d: Updated Playlist plugin docs: + + - Updated `playlist` plugin README to include note about installing backend plugin and added images for the various features + - Updated `playlist-backend` plugin README to remove `IdentityClient` import in example as it is not used and made minor change to headings + +## @backstage/plugin-playlist-backend@0.2.1-next.1 + +### Patch Changes + +- 605f269f0d: Updated Playlist plugin docs: + + - Updated `playlist` plugin README to include note about installing backend plugin and added images for the various features + - Updated `playlist-backend` plugin README to remove `IdentityClient` import in example as it is not used and made minor change to headings + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + +## @backstage/plugin-techdocs@1.4.0-next.1 + +### Patch Changes + +- 9e4d8e6198: Fix logic bug that broke techdocs-cli-embedded-app + +## @backstage/plugin-techdocs-addons-test-utils@1.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.4.0-next.1 + +## example-app@0.2.77-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.4.0-next.1 + - @backstage/plugin-playlist@0.1.2-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + +## example-backend@0.2.77-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + - @backstage/plugin-playlist-backend@0.2.1-next.1 + - @backstage/plugin-azure-devops-backend@0.3.17-next.1 + - example-app@0.2.77-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.1 + +## example-backend-next@0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + +## techdocs-cli-embedded-app@0.2.76-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.4.0-next.1 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/blog/2022-10-28-backstagecon-kubecon-2022.md b/microsite/blog/2022-10-28-backstagecon-kubecon-2022.md new file mode 100644 index 0000000000..ffc44ed432 --- /dev/null +++ b/microsite/blog/2022-10-28-backstagecon-kubecon-2022.md @@ -0,0 +1,36 @@ +--- +title: 'ICYMI: BackstageCon & KubeCon NA 2022 Recap' +description: 'We had a great time in Detroit! Learn more about our first dedicated conference to Backstage and KubeCon NA 2022.' +author: Emma White, Spotify +authorURL: https://www.linkedin.com/in/emmamckeewhite/ +--- + +## ICYMI: BackstageCon & KubeCon NA 2022 Recap + +![blog header image](assets/2022-10-28/preview.png) + +What an action-packed and memorable week for the Backstage community meeting up IRL at KubeCon North America 2022 in (unseasonably warm and sunny) Detroit. Letโ€™s see what the community was up to: + +### BackstageCon: A full day of nothing but Backstage + +![BackstageCon group](assets/2022-10-28/groupimage.jpg) + +We started the week off strong, packing a ballroom for [BackstageCon](https://events.linuxfoundation.org/backstagecon-north-america/) on Monday. It was a smorgasbord of some of our active community members at the first official Backstage conference โ€” as well as some new faces interested in learning more about Backstage! To welcome us, Roadieโ€™s [Martina Iglesias Fernandez](https://github.com/martina-if) and Spotifyโ€™s [Suzanne Daniels](https://github.com/suuus) took center stage. Next up, we listened to three keynotes from the eventโ€™s top sponsors: Roadieโ€™s [David Tuite](https://github.com/dtuite) enlightened us on how to 10x developer effectiveness for teams, [Dave Zolotusky](https://github.com/dzolotusky) from Spotify discussed developer happiness and [announced](https://www.linkedin.com/posts/spotify-r%26d_backstage-devex-activity-6990305201710587904-SnDQ?utm_source=share&utm_medium=member_desktop) that [Spotify Plugins for Backstage](https://backstage.spotify.com/blog/spotify-plugins-for-backstage/) are launching soon, and [Valentina Alaria](https://www.linkedin.com/in/valentinaalaria/) from VMware Tanzu talked about how to curate the Backstage experience to enable developer velocity. In between some creative snacks (lemon blueberry popcorn, anyone?) and coffee breaks, we heard from adopters including [Telus](https://backstage.spotify.com/blog/building-for-simplicity-TELUS/), [DAZN](https://backstage.spotify.com/blog/adopter-spotlight/dazn/), HP, US Bank, and more on lessons learned through their adoption journeys, we talked about different ways to leverage data within Backstage, and we discussed new ways to customize Backstage for specific adopter needs. + +You can watch all the BackstageCon sessions, talks, and keynotes [here](https://www.youtube.com/playlist?list=PLj6h78yzYM2OKySsTuiip3BqmdYZQRnSf)! + +### Project Meeting: Building a better Backstage together + +![project meeting image](assets/2022-10-28/real_project_meeting.jpeg) + +The following day, the Backstage maintainers hosted a lively project meeting, where we engaged with a lot of folks that had been at BackstageCon the day before. Over the course of the meeting, the Backstage community talked through recommended approaches for maintaining your Software Catalog as a source of truth, sourcing internal contributors for organizationโ€™s Backstage apps, improving frontend performance in a few plugins, how Backstage maintainers work, and appreciation for the contributions from our community. You can read more detailed notes from the project meeting in the repo [here](https://github.com/backstage/community/blob/main/project-meetings/kubecon-na-2022.md). + +### KubeCon: Demos, swag, and standing-room only + +And last but not least: three days of [KubeCon North America](https://events.linuxfoundation.org/kubecon-cloudnativecon-north-america/)! At KubeCon, the Backstage team met and greeted so many new faces at our kiosk in the Project Pavilion. We ran demos, answered questions, and handed out lots and lots of SWAG! + +![maintainer presentation](assets/2022-10-28/project_meeting.jpg) + +Spotifyโ€™s [Francesco Corti](https://github.com/fcorti) and core maintainer [Ben Lambert](https://github.com/benjdlambert) took the stage on Wednesday to present the Backstage maintainer talk to a PACKED audience. They discussed how core features of Backstage are changing the way end-user developers interact and manage their ecosystem, and encouraged folks to create the future of developer experience with the rest of the Backstage community. If you missed it, no worries! Their talk track will be posted on the [CNCF YouTube channel](https://www.youtube.com/c/cloudnativefdn). + +โ€ฆand thatโ€™s a wrap! What an incredible week. Our growing Backstage community continues to amaze us with its dedication and enthusiasm. We canโ€™t wait to see you all again at KubeCon Europe in The Netherlands! In fact, weโ€™d love to see our adopters give Backstage talks next year (hint, hint). The KubeCon Europe [CFP is open](https://events.linuxfoundation.org/kubecon-cloudnativecon-europe/program/cfp/#overview) until Friday, November 18, 2022. diff --git a/microsite/blog/assets/2022-10-28/groupimage.jpg b/microsite/blog/assets/2022-10-28/groupimage.jpg new file mode 100644 index 0000000000..ef12667e0a Binary files /dev/null and b/microsite/blog/assets/2022-10-28/groupimage.jpg differ diff --git a/microsite/blog/assets/2022-10-28/preview.png b/microsite/blog/assets/2022-10-28/preview.png new file mode 100644 index 0000000000..67774e7744 Binary files /dev/null and b/microsite/blog/assets/2022-10-28/preview.png differ diff --git a/microsite/blog/assets/2022-10-28/project_meeting.jpg b/microsite/blog/assets/2022-10-28/project_meeting.jpg new file mode 100644 index 0000000000..8e3bc91ddf Binary files /dev/null and b/microsite/blog/assets/2022-10-28/project_meeting.jpg differ diff --git a/microsite/blog/assets/2022-10-28/real_project_meeting.jpeg b/microsite/blog/assets/2022-10-28/real_project_meeting.jpeg new file mode 100644 index 0000000000..f1e0ff40bf Binary files /dev/null and b/microsite/blog/assets/2022-10-28/real_project_meeting.jpeg differ 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/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/community.js b/microsite/pages/en/community.js index 2e7801d581..0580ec045a 100644 --- a/microsite/pages/en/community.js +++ b/microsite/pages/en/community.js @@ -103,64 +103,79 @@ const Background = props => { Stay tuned to the latest developments + - - - Community sessions - - Maintainers and adopters meet monthly to share updates, demos, - and ideas. Yep, all sessions are recorded! - + + + + Community sessions + + Maintainers and adopters meet monthly to share updates, demos, + and ideas. Yep, all sessions are recorded! + + Join a session - - - Newsletter - - The official monthly Backstage newsletter. Don't miss the latest - news from your favorite project! - + + + + + Newsletter + + The official monthly Backstage newsletter. Don't miss the + latest news from your favorite project! + + Subscribe - - - Contributor Spotlight - - A recognition for valuable community work. Nominate contributing - members for their efforts! We'll put them in the spotlight โค๏ธ - + + + + + Contributor Spotlight + + A recognition for valuable community work. Nominate + contributing members for their efforts! We'll put them in the + spotlight โค๏ธ + + Nominate now + Community initiatives - - Open Mic Meetup - - A casual get together of Backstage users sharing their experiences - and helping each other. Hosted by{' '} - Roadie.io and{' '} - Frontside Software. - + + + Open Mic Meetup + + A casual get together of Backstage users sharing their + experiences and helping each other. Hosted by{' '} + Roadie.io and{' '} + Frontside Software. + + Learn more - - Backstage Weekly Newsletter - - A weekly newsletter with news, updates and things community from - your friends at Roadie.io. - + + + Backstage Weekly Newsletter + + A weekly newsletter with news, updates and things community from + your friends at Roadie.io. + + Learn more 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 91f4138174..9a0a59bd4b 100644 --- a/microsite/pages/en/plugins.js +++ b/microsite/pages/en/plugins.js @@ -16,24 +16,7 @@ const { const pluginsDirectory = require('path').join(process.cwd(), 'data/plugins'); const pluginMetadata = fs .readdirSync(pluginsDirectory) - .map(file => { - const fileContent = fs.readFileSync(`./data/plugins/${file}`, 'utf8'); - let metadata = yaml.load(fileContent); - - const gitIsoDate = require('child_process') - .execFileSync('git', [ - 'log', - '-1', - '--format="%ai"', - '--reverse', - `./data/plugins/${file}`, - ]) - .toString(); - - metadata.date = new Date(gitIsoDate); - - return metadata; - }) + .map(file => yaml.load(fs.readFileSync(`./data/plugins/${file}`, 'utf8'))) .sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase())); const truncate = text => text.length > 170 ? text.substr(0, 170) + '...' : text; @@ -119,11 +102,12 @@ const Plugins = () => ( authorUrl, documentation, category, - date, + addedDate, }) => (
- {Math.trunc((Date.now() - date) / (1000 * 60 * 60 * 24)) < - newForDays && ( + {Math.trunc( + (Date.now() - new Date(addedDate)) / (1000 * 60 * 60 * 24), + ) < newForDays && (
NEW
diff --git a/microsite/sidebars.json b/microsite/sidebars.json index bf6b036daf..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", @@ -234,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/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 85c4fa6e23..9f17e5ee36 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@types/react": "^17", "@types/react-dom": "^17" }, - "version": "1.7.0", + "version": "1.8.0-next.1", "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 2dcaaa362a..12b6a0894f 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index de27f276a6..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", + "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 d0c822dd1e..b713987116 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,77 @@ # example-app +## 0.2.77-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.4.0-next.1 + - @backstage/plugin-playlist@0.1.2-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.6-next.0 + +## 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 diff --git a/packages/app/package.json b/packages/app/package.json index 626848a60e..3f93c523a3 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.76", + "version": "0.2.77-next.1", "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:^", 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/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 9db48afd49..d8b2cd7c3b 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,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 + ## 0.2.2 ### Patch Changes diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 04a03677c0..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", + "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 c6211b05b4..3c219161d0 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,26 @@ # @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 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 2a7e307774..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", + "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/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 a569ff349c..9defd1025a 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 4de7123983..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", + "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 3315f4a27f..6c66580e6f 100644 --- a/packages/backend-next/CHANGELOG.md +++ b/packages/backend-next/CHANGELOG.md @@ -1,5 +1,22 @@ # example-backend-next +## 0.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + +## 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 diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index f82f2fc4fc..cc689aa1a8 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", + "version": "0.0.5-next.1", "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 c45f0a968e..512f0562f6 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 59e1de9529..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", + "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 87d3acf503..ecef237ff0 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 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 6c10478e6c..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", + "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 e32a82bab8..6847198298 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index aeedaec8e3..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", + "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 e519556f7f..bc8087bcc1 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,59 @@ # example-backend +## 0.2.77-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + - @backstage/plugin-playlist-backend@0.2.1-next.1 + - @backstage/plugin-azure-devops-backend@0.3.17-next.1 + - example-app@0.2.77-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.6-next.1 + +## 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 diff --git a/packages/backend/package.json b/packages/backend/package.json index 5cd4c1318e..d9500db73d 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.76", + "version": "0.2.77-next.1", "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 fbd49ff49e..f5b0e5b2c3 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 9c4e79b079..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", + "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 2ac459101c..87ca344f30 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 91016d6a33..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", + "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 ad63532139..c3f0dde1fe 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,37 @@ # @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 diff --git a/packages/cli/config/eslint-factory.js b/packages/cli/config/eslint-factory.js index e213f58e4e..04e23b2077 100644 --- a/packages/cli/config/eslint-factory.js +++ b/packages/cli/config/eslint-factory.js @@ -239,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 f460bfa4e0..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", + "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/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index b5b243d788..d6597c29a5 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 6a96e7841b..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", + "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 a03770f991..35b324bd24 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/config +## 1.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + ## 1.0.3 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 018aeb47cf..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", + "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 147a3737cc..07845f1031 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,20 @@ # @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 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 7d4b6eba30..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", + "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 0d58a7032a..00b1c55fdc 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,24 @@ # @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 diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 9587a32ab5..fc3ab4e5fc 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -495,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 4c7e9fb4db..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", + "version": "0.12.0-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx index dda2782c45..f128455549 100644 --- a/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx +++ b/packages/core-components/src/components/HeaderIconLinkRow/IconLinkVertical.tsx @@ -16,9 +16,8 @@ import React from 'react'; import classnames from 'classnames'; import { makeStyles } from '@material-ui/core/styles'; -import Link from '@material-ui/core/Link'; import LinkIcon from '@material-ui/icons/Link'; -import { Link as RouterLink } from '../Link'; +import { Link } from '../Link'; export type IconLinkVerticalProps = { color?: 'primary' | 'secondary'; @@ -80,14 +79,10 @@ export function IconLinkVertical({ if (disabled) { return ( - +
{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/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/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/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/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 4bc972e272..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 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/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 cb9c786ea9..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` 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 8b167be430..4f93cebf5d 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 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 1d8e21344c..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", + "version": "1.1.0-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 52ed5caee8..702f3d4d4e 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/create-app +## 0.4.33-next.1 + +### Patch Changes + +- Bumped create-app version. + +## 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 diff --git a/packages/create-app/package.json b/packages/create-app/package.json index a6a62fbbf0..932d54ee03 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", + "version": "0.4.33-next.1", "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/components/Root/Root.tsx b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx index 5400421e25..6768b48dad 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx @@ -1,5 +1,5 @@ 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 MapIcon from '@material-ui/icons/MyLocation'; @@ -7,7 +7,6 @@ import LibraryBooks from '@material-ui/icons/LibraryBooks'; import CreateComponentIcon from '@material-ui/icons/AddCircleOutline'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; -import { NavLink } from 'react-router-dom'; import { Settings as SidebarSettings, UserSettingsSignInAvatar, @@ -23,6 +22,7 @@ import { SidebarScrollWrapper, SidebarSpace, useSidebarOpenState, + Link, } from '@backstage/core-components'; import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; @@ -48,13 +48,7 @@ const SidebarLogo = () => { return (
- + {isOpen ? : }
diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 9513c71468..7177a25fb2 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,20 @@ # @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 diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 225f4e9a78..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", + "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 23978aeab8..f64792a5a3 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/errors +## 1.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.1-next.0 + ## 1.1.2 ### Patch Changes diff --git a/packages/errors/package.json b/packages/errors/package.json index 9abfeaaaaa..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", + "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 07d8fa516f..b396593d12 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 398ca58f36..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", + "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 244516e70e..4c34e91f8d 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,36 @@ # @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 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 add2b07a93..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", + "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 ffabaf3ffb..35515eb5d2 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,31 @@ # techdocs-cli-embedded-app +## 0.2.76-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.4.0-next.1 + +## 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 diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 2bcf099d80..740889a661 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", + "version": "0.2.76-next.1", "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 (
= { * @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 586caf36a1..84143ccf78 100644 --- a/plugins/adr-backend/CHANGELOG.md +++ b/plugins/adr-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json index 30b623ace0..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", + "version": "0.2.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr-common/CHANGELOG.md b/plugins/adr-common/CHANGELOG.md index be906e2ddc..f9093a1dcf 100644 --- a/plugins/adr-common/CHANGELOG.md +++ b/plugins/adr-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 diff --git a/plugins/adr-common/package.json b/plugins/adr-common/package.json index e62be75b1c..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", + "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 40a627a340..e1f34381e4 100644 --- a/plugins/adr/CHANGELOG.md +++ b/plugins/adr/CHANGELOG.md @@ -1,5 +1,20 @@ # @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 diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 8e4835eb31..e6113c7a83 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr", - "version": "0.2.2", + "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 0df7e1669c..38ed0acbc7 100644 --- a/plugins/airbrake-backend/CHANGELOG.md +++ b/plugins/airbrake-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index d35d005137..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", + "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 68bc61ff92..a714e4d69f 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index dd65baa46d..3f04501019 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.3.10", + "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 5b6330f11e..0f57d596c5 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/plugins/allure/package.json b/plugins/allure/package.json index ae0f87fb1e..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", + "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 1d16fce74d..149215b223 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index dc3dd253f4..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", + "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 aa40bfc9b2..b4dee086eb 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index b66067cb6e..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", + "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 630587a60e..de93784591 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 1a5c8ae07c..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", + "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 3a56b728fe..5ef5cb16f9 100644 --- a/plugins/apollo-explorer/CHANGELOG.md +++ b/plugins/apollo-explorer/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json index 3e2f958fe7..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", + "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 ba4a4b9ea4..81c2020152 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,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 + ## 0.3.37 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index d3b14e6afd..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", + "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 11c6cedb8e..a65fa417d8 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 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 23482f8eb7..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", + "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 ab1229ea69..aac75b87e9 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index f657a2df01..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", + "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 499fc62929..b8f6b73865 100644 --- a/plugins/auth-node/src/DefaultIdentityClient.ts +++ b/plugins/auth-node/src/DefaultIdentityClient.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { AuthenticationError } from '@backstage/errors'; import { @@ -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; diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index c7721c604d..ae57ae23a9 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-azure-devops-backend +## 0.3.17-next.1 + +### Patch Changes + +- 62f284e394: - Adjusted the asset parser to accept case sensitive + - Fixed fetching data that was using the deprecated function + +## 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 diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index e429240ab3..50a99dbc2a 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", + "version": "0.3.17-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index 2bf2988a98..24c13020b6 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -408,14 +408,15 @@ export class AzureDevOpsApi { content: string; }> { const url = buildEncodedUrl(host, org, project, repo, 'README.md'); - const response = await this.urlReader.read(url); + const response = await this.urlReader.readUrl(url); + const buffer = await response.buffer(); const content = await replaceReadme( this.urlReader, host, org, project, repo, - response.toString(), + buffer.toString(), ); return { url, content }; } diff --git a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts index f044a20968..74932c9653 100644 --- a/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts +++ b/plugins/azure-devops-backend/src/utils/azure-devops-utils.test.ts @@ -167,30 +167,30 @@ describe('extractAssets', () => { it('should return assets', () => { const readme = ` ## Images - ![Image 1](./images/sample-4(2).png) + ![Image 1](./images/sample-4(2).PNG) ![Image 2](./images/cdCSj+-012340.jpg) ![Image 3](/images/test-4(2)))).jpeg) ![Image 4](./images/test-2211jd.webp) - ![Image 5](/images/sa)mple.gif) + ![Image 5](/images/sa)mple.GIf) `; const result = extractAssets(readme); expect(result).toEqual([ - '[Image 1](./images/sample-4(2).png)', + '[Image 1](./images/sample-4(2).PNG)', '[Image 2](./images/cdCSj+-012340.jpg)', '[Image 3](/images/test-4(2)))).jpeg)', '[Image 4](./images/test-2211jd.webp)', - '[Image 5](/images/sa)mple.gif)', + '[Image 5](/images/sa)mple.GIf)', ]); }); }); describe('extractPartsFromAsset', () => { it('should return parts from asset - PNG', () => { - const result = extractPartsFromAsset('[Image 1](./images/sample-4(2).png)'); + const result = extractPartsFromAsset('[Image 1](./images/sample-4(2).PNG)'); expect(result).toEqual({ label: 'Image 1', path: '/images/sample-4(2)', - ext: '.png', + ext: '.PNG', }); }); @@ -207,12 +207,12 @@ describe('extractPartsFromAsset', () => { it('should return parts from asset - JPEG', () => { const result = extractPartsFromAsset( - '[Image 2](/images/test-4(2)))).jpeg)', + '[Image 2](/images/test-4(2)))).JpEg)', ); expect(result).toEqual({ label: 'Image 2', path: '/images/test-4(2))))', - ext: '.jpeg', + ext: '.JpEg', }); }); @@ -247,10 +247,15 @@ describe('replaceReadme', () => { `; const reader: UrlReader = { - read: url => new Promise(resolve => resolve(Buffer.from(url))), + readUrl: url => + Promise.resolve({ + buffer: async () => Buffer.from(url), + etag: 'buffer', + stream: jest.fn(), + }), readTree: jest.fn(), search: jest.fn(), - readUrl: jest.fn(), + read: jest.fn(), }; const result = await replaceReadme( diff --git a/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts b/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts index 191486a925..0aafb54a0a 100644 --- a/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts +++ b/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts @@ -223,8 +223,9 @@ export async function replaceReadme( const { label, path, ext } = extractPartsFromAsset(filePath); const data = mime.lookup(ext); const url = buildEncodedUrl(host, org, project, repo, path + ext); - const buffer = await urlReader.read(url); - const file = await buffer.toString('base64'); + const response = await urlReader.readUrl(url); + const buffer = await response.buffer(); + const file = buffer.toString('base64'); return content.replace( filePath, `[${label}](data:${data};base64,${file})`, @@ -320,7 +321,7 @@ export function extractPartsFromAsset(content: string): { ext: string; } { const regExp = - /\[(.*?)\]\((?!https?:\/\/)(.*?)(\.png|\.jpg|\.jpeg|\.gif|\.webp)(.*)\)/; + /\[(.*?)\]\((?!https?:\/\/)(.*?)(\.png|\.jpg|\.jpeg|\.gif|\.webp)(.*)\)/i; const [_, label, path, ext] = regExp.exec(content) || []; return { ext, diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 7c5c8983bf..1317547b7f 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 3e1a69b3dc..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", + "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 8e71d4f13f..c88b7cf763 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,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 + ## 0.1.31 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 0ca4eccccf..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", + "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 d2f5756609..f736588933 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 0d212b70b5..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", + "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( + -+ ++ + -+ -+ ++ ++ + {/* ...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 522d84aeec..eb0542a504 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.25", + "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 c9befa2959..d3ab58bf5a 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @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 diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index f7f1ffceac..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", + "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 9e6d7a9306..2ec214ea00 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 4240262c07..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", + "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 47dbcd7f53..59ab7970f2 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,35 @@ # @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 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 02bb1782e1..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", + "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 7c40b06228..7fed636045 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,27 @@ # @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 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 d3233be0e5..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", + "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 a024b12275..47cc94f257 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index f3564463cc..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", + "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 9c9b61247f..1224da8bdd 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,26 @@ # @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 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 11f8c880dd..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", + "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 ebc6e7b9df..571c18de00 100644 --- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index 2d4eae3b8f..8ee6661b01 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket", - "version": "0.2.4", + "version": "0.2.5-next.0", "deprecated": true, "main": "src/index.ts", "types": "src/index.ts", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index e5ba22ced6..a6babaf4fc 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,34 @@ # @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 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 249fb381de..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", + "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 9c89e00de4..b89231a422 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,51 @@ # @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 diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 5b94f7169e..a9587e172b 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -12,7 +12,7 @@ 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'; @@ -20,6 +20,7 @@ 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 { TokenManager } from '@backstage/backend-common'; // @public export class GithubDiscoveryProcessor implements CatalogProcessor { @@ -111,6 +112,7 @@ export class GithubLocationAnalyzer implements ScmLocationAnalyzer { export type GithubLocationAnalyzerOptions = { config: Config; discovery: PluginEndpointDiscovery; + tokenManager: TokenManager; }; // @public @@ -160,7 +162,7 @@ export class GithubOrgEntityProvider implements EntityProvider { constructor(options: { id: string; orgUrl: string; - gitHubConfig: GitHubIntegrationConfig; + gitHubConfig: GithubIntegrationConfig; logger: Logger; githubCredentialsProvider?: GithubCredentialsProvider; }); diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index e1a3ed39b8..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", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts index 936f8a3576..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,7 +32,10 @@ jest.mock('@octokit/rest', () => { return { Octokit }; }); -import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { + PluginEndpointDiscovery, + TokenManager, +} from '@backstage/backend-common'; import { GithubLocationAnalyzer } from './GithubLocationAnalyzer'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { setupServer } from 'msw/node'; @@ -46,6 +49,10 @@ describe('GithubLocationAnalyzer', () => { 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: [ @@ -117,6 +124,7 @@ describe('GithubLocationAnalyzer', () => { const analyzer = new GithubLocationAnalyzer({ discovery: mockDiscoveryApi, config, + tokenManager: mockTokenManager, }); const result = await analyzer.analyze({ url: 'https://github.com/foo/bar', @@ -142,6 +150,7 @@ describe('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 index 32ef8bf946..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,36 @@ 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 = { config: Config; discovery: PluginEndpointDiscovery; + tokenManager: TokenManager; }; /** @public */ export class GithubLocationAnalyzer implements ScmLocationAnalyzer { private readonly catalogClient: CatalogApi; - private readonly config: Config; + private readonly githubCredentialsProvider: GithubCredentialsProvider; + private readonly integrations: ScmIntegrationRegistry; + private readonly tokenManager: TokenManager; constructor(options: GithubLocationAnalyzerOptions) { - this.config = options.config; 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'; } @@ -55,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, }); @@ -82,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/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/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/providers/GithubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index a63069d552..cc9a9bfee3 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -174,6 +174,11 @@ describe('GithubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), @@ -271,6 +276,11 @@ describe('GithubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), @@ -345,6 +355,11 @@ describe('GithubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), @@ -386,6 +401,110 @@ describe('GithubEntityProvider', () => { }); }); + 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: { @@ -440,6 +559,11 @@ describe('GithubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, { name: 'test-repo-2', @@ -458,6 +582,11 @@ describe('GithubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, { name: 'test-repo-3', @@ -473,6 +602,11 @@ describe('GithubEntityProvider', () => { defaultBranchRef: { name: 'main', }, + catalogInfoFile: { + __typename: 'Blob', + id: 'abc123', + text: 'some yaml', + }, }, ], }), diff --git a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 2004cb7469..6d3badf865 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -19,8 +19,8 @@ import { Config } from '@backstage/config'; import { GithubCredentialsProvider, ScmIntegrations, - GitHubIntegrationConfig, - GitHubIntegration, + GithubIntegrationConfig, + GithubIntegration, SingleInstanceGithubCredentialsProvider, } from '@backstage/integration'; import { @@ -51,7 +51,7 @@ import { satisfiesTopicFilter } from '../lib/util'; 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; @@ -101,7 +101,7 @@ export class GithubEntityProvider implements EntityProvider { private constructor( config: GithubEntityProviderConfig, - integration: GitHubIntegration, + integration: GithubIntegration, logger: Logger, taskRunner: TaskRunner, ) { @@ -178,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({ @@ -192,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 index c80546bdff..0d9b5ce65b 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts @@ -112,6 +112,7 @@ describe('readProviderConfigs', () => { }, }, schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[1]).toEqual({ id: 'providerCustomCatalogPath', @@ -127,6 +128,7 @@ describe('readProviderConfigs', () => { }, }, schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[2]).toEqual({ id: 'providerWithRepositoryFilter', @@ -142,6 +144,7 @@ describe('readProviderConfigs', () => { }, }, schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[3]).toEqual({ id: 'providerWithBranchFilter', @@ -157,6 +160,7 @@ describe('readProviderConfigs', () => { }, }, schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[4]).toEqual({ id: 'providerWithTopicFilter', @@ -172,6 +176,7 @@ describe('readProviderConfigs', () => { }, }, schedule: undefined, + validateLocationsExist: false, }); expect(providerConfigs[5]).toEqual({ id: 'providerWithHost', @@ -186,6 +191,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + validateLocationsExist: false, schedule: undefined, }); expect(providerConfigs[6]).toEqual({ @@ -207,6 +213,38 @@ describe('readProviderConfigs', () => { 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 index 18c0a3915d..e888743258 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts @@ -33,6 +33,7 @@ export type GithubEntityProviderConfig = { branch?: string; topic?: GithubTopicFilters; }; + validateLocationsExist: boolean; schedule?: TaskScheduleDefinition; }; @@ -77,6 +78,16 @@ 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')) @@ -98,6 +109,7 @@ function readProviderConfig( }, }, schedule, + validateLocationsExist, }; } diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index 1d8d125d91..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,7 +18,7 @@ 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'; @@ -87,7 +87,7 @@ describe('GithubOrgEntityProvider', () => { }; const logger = getVoidLogger(); - const gitHubConfig: GitHubIntegrationConfig = { + const gitHubConfig: GithubIntegrationConfig = { host: 'https://github.com', }; diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index f8fafbe53c..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'; @@ -134,7 +134,7 @@ export class GithubOrgEntityProvider implements EntityProvider { private options: { id: string; orgUrl: string; - gitHubConfig: GitHubIntegrationConfig; + gitHubConfig: GithubIntegrationConfig; logger: Logger; githubCredentialsProvider?: GithubCredentialsProvider; }, diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 65979281e7..18cce9e01e 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,27 @@ # @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 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 b21733875c..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", + "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 8045a29598..8a58fce1f5 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index eb97de27d0..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", + "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 4112931043..8b8d4479d3 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,23 @@ # @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 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 df63bc71dd..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", + "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 a11be4a5f2..0dc46b07a7 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 1f32a1a4f2..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", + "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 60ecdae3b9..494f332c12 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @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 diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d3e44d47b1..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.5.0", + "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-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 2ecbca904f..d1378e9418 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index b700002f9c..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", + "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 3f659df9b3..04be429ab6 100644 --- a/plugins/catalog-customized/CHANGELOG.md +++ b/plugins/catalog-customized/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/catalog-customized/package.json b/plugins/catalog-customized/package.json index bb5564c135..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", + "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 a6a2f5770b..7f8e1e0277 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index ccfbd5160e..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", + "version": "0.2.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graphql/CHANGELOG.md b/plugins/catalog-graphql/CHANGELOG.md index 1546bcb395..97b2a49835 100644 --- a/plugins/catalog-graphql/CHANGELOG.md +++ b/plugins/catalog-graphql/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 0.3.14 ### Patch Changes diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 0a48e3d58b..503c7eb3b0 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-graphql", "description": "An experimental Backstage catalog GraphQL module", - "version": "0.3.14", + "version": "0.3.15-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 3bb70d5d17..8562b77510 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,23 @@ # @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 + ## 0.9.0 ### Minor Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 66b4648c05..b8cacc4450 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-import", "description": "A Backstage plugin the helps you import entities into your catalog", - "version": "0.9.0", + "version": "0.9.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.ts b/plugins/catalog-import/src/api/CatalogImportClient.ts index 0663122253..4981331c89 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.ts @@ -21,7 +21,7 @@ import { IdentityApi, } from '@backstage/core-plugin-api'; import { - GitHubIntegrationConfig, + GithubIntegrationConfig, ScmIntegrationRegistry, } from '@backstage/integration'; import { ScmAuthApi } from '@backstage/integration-react'; @@ -239,7 +239,7 @@ the component will become available.\n\nFor more information, read an \ body: string; fileContent: string; repositoryUrl: string; - githubIntegrationConfig: GitHubIntegrationConfig; + githubIntegrationConfig: GithubIntegrationConfig; }): Promise<{ link: string; location: string }> { const { owner, diff --git a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx index 47f0139e5e..dac83a4817 100644 --- a/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx +++ b/plugins/catalog-import/src/components/EntityListComponent/EntityListComponent.tsx @@ -98,7 +98,7 @@ export const EntityListComponent = (props: EntityListComponentProps) => { onItemClick?.call(this, r.target)} + onClick={() => onItemClick?.(r.target)} > {locationListItemIcon(r.target)} diff --git a/plugins/catalog-node/CHANGELOG.md b/plugins/catalog-node/CHANGELOG.md index cbce789d3c..ffbd18cd8e 100644 --- a/plugins/catalog-node/CHANGELOG.md +++ b/plugins/catalog-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 + ## 1.2.0 ### Minor Changes diff --git a/plugins/catalog-node/package.json b/plugins/catalog-node/package.json index 14e1986745..320f372249 100644 --- a/plugins/catalog-node/package.json +++ b/plugins/catalog-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-node", "description": "The plugin-catalog-node module for @backstage/plugin-catalog-backend", - "version": "1.2.0", + "version": "1.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index a58a75687c..3019049086 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,25 @@ # @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 + ## 1.2.0 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 6a66a2e21a..c2363fdc76 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.2.0", + "version": "1.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx index 4623cf5d9e..525c757dea 100644 --- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx @@ -16,7 +16,6 @@ import React from 'react'; import { fireEvent, waitFor } from '@testing-library/react'; -import { capitalize } from 'lodash'; import { Entity } from '@backstage/catalog-model'; import { EntityTypePicker } from './EntityTypePicker'; import { MockEntityListContextProvider } from '../../testUtils/providers'; @@ -99,11 +98,11 @@ describe('', () => { const input = rendered.getByTestId('select'); fireEvent.click(input); - await waitFor(() => rendered.getByText('Service')); + await waitFor(() => rendered.getByText('service')); entities.forEach(entity => { expect( - rendered.getByText(capitalize(entity.spec!.type as string)), + rendered.getByText(entity.spec!.type as string), ).toBeInTheDocument(); }); }); @@ -125,15 +124,15 @@ describe('', () => { const input = rendered.getByTestId('select'); fireEvent.click(input); - await waitFor(() => rendered.getByText('Service')); - fireEvent.click(rendered.getByText('Service')); + await waitFor(() => rendered.getByText('service')); + fireEvent.click(rendered.getByText('service')); expect(updateFilters).toHaveBeenLastCalledWith({ type: new EntityTypeFilter(['service']), }); fireEvent.click(input); - fireEvent.click(rendered.getByText('All')); + fireEvent.click(rendered.getByText('all')); expect(updateFilters).toHaveBeenLastCalledWith({ type: undefined }); }); diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx index a0770ef30a..3e641e8e37 100644 --- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx +++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.tsx @@ -15,7 +15,6 @@ */ import React, { useEffect } from 'react'; -import capitalize from 'lodash/capitalize'; import { Box } from '@material-ui/core'; import { useEntityTypeFilter } from '../../hooks/useEntityTypeFilter'; @@ -54,10 +53,10 @@ export const EntityTypePicker = (props: EntityTypePickerProps) => { if (availableTypes.length === 0 || error) return null; const items = [ - { value: 'all', label: 'All' }, + { value: 'all', label: 'all' }, ...availableTypes.map((type: string) => ({ value: type, - label: capitalize(type), + label: type, })), ]; diff --git a/plugins/catalog-react/src/hooks/useEntity.test.tsx b/plugins/catalog-react/src/hooks/useEntity.test.tsx index 5e86a94180..3a1e5010d3 100644 --- a/plugins/catalog-react/src/hooks/useEntity.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.test.tsx @@ -23,6 +23,11 @@ import { AsyncEntityProvider, } from './useEntity'; import { Entity } from '@backstage/catalog-model'; +import { analyticsApiRef, useAnalytics } from '@backstage/core-plugin-api'; +import { MockAnalyticsApi, TestApiRegistry } from '@backstage/test-utils'; +import { ApiProvider } from '@backstage/core-app-api'; + +const entity = { metadata: { name: 'my-entity' }, kind: 'MyKind' } as Entity; describe('useEntity', () => { it('should throw if no entity is provided', async () => { @@ -34,7 +39,6 @@ describe('useEntity', () => { }); it('should provide an entity', async () => { - const entity = { kind: 'MyEntity' } as Entity; const { result } = renderHook(() => useEntity(), { wrapper: ({ children }) => ( @@ -43,6 +47,24 @@ describe('useEntity', () => { expect(result.current.entity).toBe(entity); }); + + it('should provide entityRef analytics context', () => { + const analyticsSpy = new MockAnalyticsApi(); + const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]); + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + + + + ), + }); + + result.current.captureEvent('test', 'value'); + + expect(analyticsSpy.getEvents()[0]).toMatchObject({ + context: { entityRef: 'mykind:default/my-entity' }, + }); + }); }); describe('useAsyncEntity', () => { @@ -60,7 +82,6 @@ describe('useAsyncEntity', () => { }); it('should provide an entity', async () => { - const entity = { kind: 'MyEntity' } as Entity; const refresh = () => {}; const { result } = renderHook(() => useAsyncEntity(), { wrapper: ({ children }) => ( @@ -96,4 +117,43 @@ describe('useAsyncEntity', () => { expect(result.current.error).toBe(error); expect(result.current.refresh).toBe(undefined); }); + + it('should provide entityRef analytics context', () => { + const analyticsSpy = new MockAnalyticsApi(); + const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]); + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + + {}} + children={children} + /> + + ), + }); + + result.current.captureEvent('test', 'value'); + + expect(analyticsSpy.getEvents()[0]).toMatchObject({ + context: { entityRef: 'mykind:default/my-entity' }, + }); + }); + + it('should omit entityRef analytics context', () => { + const analyticsSpy = new MockAnalyticsApi(); + const apis = TestApiRegistry.from([analyticsApiRef, analyticsSpy]); + const { result } = renderHook(() => useAnalytics(), { + wrapper: ({ children }) => ( + + + + ), + }); + + result.current.captureEvent('test', 'value'); + + expect(analyticsSpy.getEvents()[0].context).not.toHaveProperty('entityRef'); + }); }); diff --git a/plugins/catalog-react/src/hooks/useEntity.tsx b/plugins/catalog-react/src/hooks/useEntity.tsx index f654cff777..84b7883c8a 100644 --- a/plugins/catalog-react/src/hooks/useEntity.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.tsx @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { AnalyticsContext } from '@backstage/core-plugin-api'; import { createVersionedContext, createVersionedValueMap, @@ -66,7 +67,13 @@ export const AsyncEntityProvider = ({ // consumers might be doing things like `useContext(EntityContext)` return ( - {children} + + {children} + ); }; diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index b40f04f501..e95afd8617 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,24 @@ # @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 + ## 1.6.0 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index ad7e9a75c2..de2833c470 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "1.6.0", + "version": "1.6.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index f2fee7d9f4..c77787f717 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -216,7 +216,7 @@ describe('', () => { ); }); - it('renders without "view source" link', async () => { + it('renders disabled "view source" link', async () => { const entity = { apiVersion: 'v1', kind: 'Component', @@ -250,7 +250,8 @@ describe('', () => { }, }, ); - expect(getByText('View Source').closest('a')).not.toHaveAttribute('href'); + expect(getByText('View Source')).toBeVisible(); + expect(getByText('View Source').closest('a')).toBeNull(); }); it.each([ @@ -445,7 +446,8 @@ describe('', () => { }, ); - expect(getByText('View TechDocs').closest('a')).not.toHaveAttribute('href'); + expect(getByText('View TechDocs')).toBeVisible(); + expect(getByText('View TechDocs').closest('a')).toBeNull(); }); it('renders disabled techdocs link when route is not bound', async () => { @@ -497,6 +499,7 @@ describe('', () => { }, ); - expect(getByText('View TechDocs').closest('a')).not.toHaveAttribute('href'); + expect(getByText('View TechDocs')).toBeVisible(); + expect(getByText('View TechDocs').closest('a')).toBeNull(); }); }); diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.test.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.test.tsx index 04ee1dc99e..8972cd2f9e 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.test.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.test.tsx @@ -24,8 +24,10 @@ describe('EntityLinksCard', () => { const createEntity = (links: EntityLink[] = []): Entity => ({ metadata: { + name: 'mock', links, }, + kind: 'MockKind', } as Entity); const createLink = ({ diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx index 9588327c3b..4ee9d9c820 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx @@ -46,7 +46,9 @@ describe('EntitySwitch', () => { const rendered = render( - + {content} , @@ -58,7 +60,9 @@ describe('EntitySwitch', () => { rendered.rerender( - + {content} , @@ -70,7 +74,9 @@ describe('EntitySwitch', () => { rendered.rerender( - + {content} , @@ -94,7 +100,7 @@ describe('EntitySwitch', () => { }); it('should switch child when filters switch', () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const rendered = render( @@ -126,7 +132,7 @@ describe('EntitySwitch', () => { }); it('should switch with async condition that is true', async () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const shouldRender = () => Promise.resolve(true); const rendered = render( @@ -145,7 +151,7 @@ describe('EntitySwitch', () => { }); it('should switch with sync condition that is false', async () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const shouldRender = () => Promise.resolve(false); const rendered = render( @@ -164,7 +170,7 @@ describe('EntitySwitch', () => { }); it('should switch with sync condition that throws', async () => { - const entity = { kind: 'component' } as Entity; + const entity = { metadata: { name: 'mock' }, kind: 'component' } as Entity; const shouldRender = () => Promise.reject(); const rendered = render( diff --git a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md index 2d3dbc8eed..e394418c24 100644 --- a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md +++ b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 0.1.6 ### Patch Changes diff --git a/plugins/cicd-statistics-module-gitlab/package.json b/plugins/cicd-statistics-module-gitlab/package.json index e0fdf00b7b..5f6d5039bf 100644 --- a/plugins/cicd-statistics-module-gitlab/package.json +++ b/plugins/cicd-statistics-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics-module-gitlab", "description": "CI/CD Statistics plugin module; Gitlab CICD", - "version": "0.1.6", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cicd-statistics/CHANGELOG.md b/plugins/cicd-statistics/CHANGELOG.md index 24e4a2f6e3..29852c597f 100644 --- a/plugins/cicd-statistics/CHANGELOG.md +++ b/plugins/cicd-statistics/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 0.1.12 ### Patch Changes diff --git a/plugins/cicd-statistics/package.json b/plugins/cicd-statistics/package.json index d198ff0934..5cfac4666e 100644 --- a/plugins/cicd-statistics/package.json +++ b/plugins/cicd-statistics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics", "description": "A frontend plugin visualizing CI/CD pipeline statistics (build time)", - "version": "0.1.12", + "version": "0.1.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 8d5199d550..b9d916f75a 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 + ## 0.3.10 ### Patch Changes diff --git a/plugins/circleci/README.md b/plugins/circleci/README.md index fa80469991..014a2a3f99 100644 --- a/plugins/circleci/README.md +++ b/plugins/circleci/README.md @@ -2,8 +2,11 @@ Website: [https://circleci.com/](https://circleci.com/) - - +## Screenshots + + + + ## Setup @@ -43,7 +46,7 @@ proxy: ``` 5. Get and provide a `CIRCLECI_AUTH_TOKEN` as an environment variable (see the [CircleCI docs](https://circleci.com/docs/api/#add-an-api-token)). -6. Add a `circleci.com/project-slug` annotation to your respective `catalog-info.yaml` files, on the format // (https://backstage.io/docs/architecture-decisions/adrs-adr002#format). +6. Add an annotation to your respective `catalog-info.yaml` files, with the format `circleci.com/project-slug: //` (See reference in [ADR002](https://backstage.io/docs/architecture-decisions/adrs-adr002#format)). ```yaml # Example catalog-info.yaml entity definition file diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 98b6334c35..d5f36ba7d6 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.3.10", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/circleci/src/assets/screenshot-1.png b/plugins/circleci/src/assets/screenshot-1.png deleted file mode 100644 index bef8a5a06f..0000000000 Binary files a/plugins/circleci/src/assets/screenshot-1.png and /dev/null differ diff --git a/plugins/circleci/src/assets/screenshot-2.png b/plugins/circleci/src/assets/screenshot-2.png deleted file mode 100644 index e4b8299013..0000000000 Binary files a/plugins/circleci/src/assets/screenshot-2.png and /dev/null differ diff --git a/plugins/circleci/src/assets/screenshot-build-details.png b/plugins/circleci/src/assets/screenshot-build-details.png new file mode 100644 index 0000000000..fc83ae56b5 Binary files /dev/null and b/plugins/circleci/src/assets/screenshot-build-details.png differ diff --git a/plugins/circleci/src/assets/screenshot-build-failure.png b/plugins/circleci/src/assets/screenshot-build-failure.png new file mode 100644 index 0000000000..d1a263ca9f Binary files /dev/null and b/plugins/circleci/src/assets/screenshot-build-failure.png differ diff --git a/plugins/circleci/src/assets/screenshot-pipeline-list.png b/plugins/circleci/src/assets/screenshot-pipeline-list.png new file mode 100644 index 0000000000..94f8cc8be4 Binary files /dev/null and b/plugins/circleci/src/assets/screenshot-pipeline-list.png differ diff --git a/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx index 137833ee0b..8d48be915e 100644 --- a/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx +++ b/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx @@ -17,32 +17,25 @@ import React, { useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { BuildWithSteps, BuildStepAction } from '../../api'; -import { - Grid, - Box, - IconButton, - Typography, - Link as MaterialLink, -} from '@material-ui/core'; +import { Grid, Box, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { ActionOutput } from './lib/ActionOutput/ActionOutput'; import LaunchIcon from '@material-ui/icons/Launch'; import { useBuildWithSteps } from '../../state/useBuildWithSteps'; import { Breadcrumbs, + Button, InfoCard, Progress, Link, } from '@backstage/core-components'; -const IconLink = IconButton as any as typeof MaterialLink; - const BuildName = ({ build }: { build?: BuildWithSteps }) => ( #{build?.build_num} - {build?.subject} - + ); diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 30a7410a08..bc83c46375 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 + ## 0.3.10 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index eb01b8df4a..ee2cf54177 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cloudbuild", "description": "A Backstage plugin that integrates towards Google Cloud Build", - "version": "0.3.10", + "version": "0.3.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index 0e532178e1..aacb437e7b 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/cloudbuild/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -17,7 +17,6 @@ import { Entity } from '@backstage/catalog-model'; import { Box, LinearProgress, - Link as MaterialLink, makeStyles, Paper, Table, @@ -135,10 +134,10 @@ export const WorkflowRunDetails = (props: { entity: Entity }) => { {details.value?.logUrl && ( - + Workflow runs on Google{' '} - + )} diff --git a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index a8e2a12ea8..736e641dbd 100644 --- a/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/cloudbuild/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -14,10 +14,9 @@ * limitations under the License. */ import React from 'react'; -import { Link, Typography, Box, IconButton, Tooltip } from '@material-ui/core'; +import { Typography, Box, IconButton, Tooltip } from '@material-ui/core'; import RetryIcon from '@material-ui/icons/Replay'; import GoogleIcon from '@material-ui/icons/CloudCircle'; -import { Link as RouterLink } from 'react-router-dom'; import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import SyncIcon from '@material-ui/icons/Sync'; @@ -25,7 +24,7 @@ import { useProjectName } from '../useProjectName'; import { Entity } from '@backstage/catalog-model'; import { buildRouteRef } from '../../routes'; import { DateTime } from 'luxon'; -import { Table, TableColumn } from '@backstage/core-components'; +import { Table, TableColumn, Link } from '@backstage/core-components'; import { useRouteRef } from '@backstage/core-plugin-api'; const generatedColumns: TableColumn[] = [ @@ -59,11 +58,7 @@ const generatedColumns: TableColumn[] = [ const LinkWrapper = () => { const routeLink = useRouteRef(buildRouteRef); return ( - + {row.message} ); diff --git a/plugins/code-climate/CHANGELOG.md b/plugins/code-climate/CHANGELOG.md index 367eb96673..20965a33a5 100644 --- a/plugins/code-climate/CHANGELOG.md +++ b/plugins/code-climate/CHANGELOG.md @@ -1,5 +1,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 + ## 0.1.10 ### Patch Changes diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index 548f93233c..735c5d2c5e 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-code-climate", - "version": "0.1.10", + "version": "0.1.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 891194c7a4..251cfa1b33 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 + ## 0.2.3 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 147a2b92ac..70be7268cf 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage-backend", "description": "A Backstage backend plugin that helps you keep track of your code coverage", - "version": "0.2.3", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index c724283ddb..5ccbf4eed0 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 + ## 0.2.3 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index f8507e60a4..647c7d2fb0 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.2.3", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,7 +37,7 @@ "highlight.js": "^10.6.0", "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/codescene/CHANGELOG.md b/plugins/codescene/CHANGELOG.md index cdfaf94a71..d21ec8c71d 100644 --- a/plugins/codescene/CHANGELOG.md +++ b/plugins/codescene/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 + ## 0.1.5 ### Patch Changes diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json index 4f8f9a365f..ffd297e8ed 100644 --- a/plugins/codescene/package.json +++ b/plugins/codescene/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-codescene", - "version": "0.1.5", + "version": "0.1.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index af63c96be1..6d62429875 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 + ## 0.1.33 ### Patch Changes diff --git a/plugins/config-schema/dev/example-schema.json b/plugins/config-schema/dev/example-schema.json index 86f05d500d..bb90595340 100644 --- a/plugins/config-schema/dev/example-schema.json +++ b/plugins/config-schema/dev/example-schema.json @@ -499,28 +499,6 @@ } } }, - "homepage": { - "type": "object", - "properties": { - "clocks": { - "type": "array", - "items": { - "type": "object", - "required": ["label", "timezone"], - "properties": { - "label": { - "visibility": "frontend", - "type": "string" - }, - "timezone": { - "visibility": "frontend", - "type": "string" - } - } - } - } - } - }, "integrations": { "description": "Configuration for integrations towards various external repository provider systems", "type": "object", diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index b6a23dd2aa..b111738405 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-config-schema", "description": "A Backstage plugin that lets you browse the configuration schema of your app", - "version": "0.1.33", + "version": "0.1.34-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 02f9902fce..ce062fa7e1 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,22 @@ # @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 + ## 0.11.32 ### Patch Changes diff --git a/plugins/cost-insights/README.md b/plugins/cost-insights/README.md index 2b20da2f26..cc90b4d7a2 100644 --- a/plugins/cost-insights/README.md +++ b/plugins/cost-insights/README.md @@ -136,7 +136,7 @@ For showing cost breakdowns you can define a map of cloud products. They must be You can optionally supply a product `icon` to display in Cost Insights navigation. See the [type file](https://github.com/backstage/backstage/blob/master/plugins/cost-insights/src/types/Icon.ts) for supported types and Material UI icon [mappings](https://github.com/backstage/backstage/blob/master/plugins/cost-insights/src/utils/navigation.tsx). -**Note:** Product keys should be unique and camelCased. Backstage does not support underscores in configuration keys. +**Note:** Product keys should be unique and on `camelCase` form. Backstage does not support underscores in configuration keys. ```yaml ## ./app-config.yaml diff --git a/plugins/cost-insights/api-report.md b/plugins/cost-insights/api-report.md index f4c8c9bd6f..f50f0b0c81 100644 --- a/plugins/cost-insights/api-report.md +++ b/plugins/cost-insights/api-report.md @@ -11,14 +11,12 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { BackstageTheme } from '@backstage/theme'; import { ChangeStatistic as ChangeStatistic_2 } from '@backstage/plugin-cost-insights-common'; import * as common from '@backstage/plugin-cost-insights-common'; -import { ContentRenderer } from 'recharts'; import { Dispatch } from 'react'; import { ForwardRefExoticComponent } from 'react'; import { Maybe as Maybe_2 } from '@backstage/plugin-cost-insights-common'; import { PaletteOptions } from '@material-ui/core/styles/createPalette'; import { PropsWithChildren } from 'react'; import { ReactNode } from 'react'; -import { RechartsFunction } from 'recharts'; import { RefAttributes } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SetStateAction } from 'react'; @@ -178,10 +176,10 @@ export type BarChartProps = { resources: ResourceData[]; responsive?: boolean; displayAmount?: number; - options?: Partial; - tooltip?: ContentRenderer; - onClick?: RechartsFunction; - onMouseMove?: RechartsFunction; + options?: Partial; + tooltip?: TooltipRenderer; + onClick?: (...args: any[]) => void; + onMouseMove?: (...args: any[]) => void; }; // @public (undocumented) @@ -586,10 +584,15 @@ export interface ResourceData { // @public (undocumented) export type TooltipItem = { fill: string; - label: string; - value: string; + label?: string; + value?: string; }; +// @public (undocumented) +export type TooltipRenderer = ( + props: TooltipProps, +) => ReactNode; + // @public @deprecated (undocumented) export type Trendline = common.Trendline; diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index e325f75e0a..b5859fa886 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cost-insights", "description": "A Backstage plugin that helps you keep track of your cloud spend", - "version": "0.11.32", + "version": "0.12.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -49,7 +49,7 @@ "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" }, diff --git a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx index 4e52fd6f02..c1a674a813 100644 --- a/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx +++ b/plugins/cost-insights/src/alerts/ProjectGrowthAlert.test.tsx @@ -69,6 +69,23 @@ class CustomProjectGrowthAlert extends ProjectGrowthAlert { } describe('ProjectGrowthAlert', () => { + 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(); + }); + describe('constructor', () => { it('should create a project growth alert', async () => { const alert = new ProjectGrowthAlert(mockData); diff --git a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx index bcd6c335a5..a4f78ae8f5 100644 --- a/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx +++ b/plugins/cost-insights/src/alerts/UnlabeledDataflowAlert.test.tsx @@ -66,6 +66,22 @@ class CustomUnlabeledDataflowAlert extends UnlabeledDataflowAlert { } describe('UnlabeledDataflowAlert', () => { + 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(); + }); + describe('constructor', () => { it('should create an unlabeled dataflow alert', async () => { const alert = new UnlabeledDataflowAlert(mockData); diff --git a/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx b/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx index 2ab4b7ebc5..4e3ca9d78c 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChart.test.tsx @@ -55,6 +55,22 @@ const renderWithProps = ({ }; describe('', () => { + 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('Renders without exploding', async () => { const rendered = await renderWithProps({} as BarChartProps); expect(rendered.getByText('test-id-10')).toBeInTheDocument(); diff --git a/plugins/cost-insights/src/components/BarChart/BarChart.tsx b/plugins/cost-insights/src/components/BarChart/BarChart.tsx index 145819db08..4f4dea8ddd 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChart.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChart.tsx @@ -19,11 +19,8 @@ import { Bar, BarChart as RechartsBarChart, CartesianGrid, - ContentRenderer, - TooltipProps as RechartsTooltipProps, - RechartsFunction, - ResponsiveContainer, Tooltip as RechartsTooltip, + ResponsiveContainer, XAxis, YAxis, } from 'recharts'; @@ -34,20 +31,18 @@ import { BarChartTooltip } from './BarChartTooltip'; import { BarChartTooltipItem } from './BarChartTooltipItem'; import { currencyFormatter } from '../../utils/formatters'; import { - BarChartData, ResourceData, DataKey, CostInsightsTheme, + BarChartOptions, } from '../../types'; import { notEmpty } from '../../utils/assert'; import { useBarChartStyles } from '../../utils/styles'; import { resourceSort } from '../../utils/sort'; import { isInvalid, titleOf, tooltipItemOf } from '../../utils/graphs'; +import { TooltipRenderer } from '../../types/Tooltip'; -export const defaultTooltip: ContentRenderer = ({ - label, - payload = [], -}) => { +export const defaultTooltip: TooltipRenderer = ({ label, payload = [] }) => { if (isInvalid({ label, payload })) return null; const title = titleOf(label); @@ -66,10 +61,10 @@ export type BarChartProps = { resources: ResourceData[]; responsive?: boolean; displayAmount?: number; - options?: Partial; - tooltip?: ContentRenderer; - onClick?: RechartsFunction; - onMouseMove?: RechartsFunction; + options?: Partial; + tooltip?: TooltipRenderer; + onClick?: (...args: any[]) => void; + onMouseMove?: (...args: any[]) => void; }; /** @public */ diff --git a/plugins/cost-insights/src/components/BarChart/BarChartTooltipItem.tsx b/plugins/cost-insights/src/components/BarChart/BarChartTooltipItem.tsx index fc50ab979a..123720cbc0 100644 --- a/plugins/cost-insights/src/components/BarChart/BarChartTooltipItem.tsx +++ b/plugins/cost-insights/src/components/BarChart/BarChartTooltipItem.tsx @@ -22,8 +22,8 @@ import { useTooltipStyles as useStyles } from '../../utils/styles'; /** @public */ export type TooltipItem = { fill: string; - label: string; - value: string; + label?: string; + value?: string; }; /** @public */ diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx index 5b912c09a6..f6463b65ed 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx @@ -25,8 +25,6 @@ import { import { default as FullScreenIcon } from '@material-ui/icons/Fullscreen'; import { AreaChart, - ContentRenderer, - TooltipProps, XAxis, YAxis, Tooltip as RechartsTooltip, @@ -52,6 +50,7 @@ import { getPreviousPeriodTotalCost } from '../../utils/change'; import { formatPeriod } from '../../utils/formatters'; import { aggregationSum } from '../../utils/sum'; import { BarChartLegendOptions } from '../BarChart/BarChartLegend'; +import { TooltipRenderer } from '../../types/Tooltip'; export type CostOverviewBreakdownChartProps = { costBreakdown: Cost[]; @@ -168,17 +167,15 @@ export const CostOverviewBreakdownChart = ({ fill={color} onClick={() => setExpanded(true)} style={{ - cursor: breakdown === 'Other' && !isExpanded ? 'pointer' : null, + cursor: + breakdown === 'Other' && !isExpanded ? 'pointer' : undefined, }} /> ); }); }; - const tooltipRenderer: ContentRenderer = ({ - label, - payload = [], - }) => { + const tooltipRenderer: TooltipRenderer = ({ label, payload = [] }) => { if (isInvalid({ label, payload })) return null; const date = @@ -186,10 +183,10 @@ export const CostOverviewBreakdownChart = ({ ? DateTime.fromMillis(label) : DateTime.fromISO(label!); const dateTitle = date.toUTC().toFormat(DEFAULT_DATE_FORMAT); - const items = payload.map(p => ({ + const items = payload.map((p, i) => ({ label: p.dataKey as string, - value: formatGraphValue(p.value as number), - fill: p.fill!, + value: formatGraphValue(Number(p.value), i), + fill: p.color!, })); const expandText = ( diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.test.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.test.tsx index 084c9bb92f..e3de9593d9 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.test.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.test.tsx @@ -53,6 +53,22 @@ function renderInContext(children: JSX.Element) { } describe('', () => { + beforeEach(() => { + // @ts-expect-error: Since we have strictNullChecks enabled, this will throw an error as window.ResizeObserver + // it's not an optional operand + delete window.ResizeObserver; + window.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + })); + }); + + afterEach(() => { + window.ResizeObserver = ResizeObserver; + jest.restoreAllMocks(); + }); + it('Renders without exploding', async () => { const { getByText } = await renderInContext( , diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx index e7b478ba07..1c58bf58e6 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx @@ -18,8 +18,6 @@ import { DateTime } from 'luxon'; import { useTheme, Box } from '@material-ui/core'; import { ComposedChart, - ContentRenderer, - TooltipProps, XAxis, YAxis, Tooltip as RechartsTooltip, @@ -50,6 +48,7 @@ import { useCostOverviewStyles as useStyles } from '../../utils/styles'; import { groupByDate, toDataMax, trendFrom } from '../../utils/charts'; import { aggregationSort } from '../../utils/sort'; import { CostOverviewLegend } from './CostOverviewLegend'; +import { TooltipRenderer } from '../../types/Tooltip'; type CostOverviewChartProps = { metric: Maybe; @@ -98,10 +97,7 @@ export const CostOverviewChart = ({ : {}), })); - const tooltipRenderer: ContentRenderer = ({ - label, - payload = [], - }) => { + const tooltipRenderer: TooltipRenderer = ({ label, payload = [] }) => { if (isInvalid({ label, payload })) return null; const dataKeys = [data.dailyCost.dataKey, data.metric.dataKey]; @@ -112,15 +108,15 @@ export const CostOverviewChart = ({ const title = date.toUTC().toFormat(DEFAULT_DATE_FORMAT); const items = payload .filter(p => dataKeys.includes(p.dataKey as string)) - .map(p => ({ + .map((p, i) => ({ label: p.dataKey === data.dailyCost.dataKey ? data.dailyCost.name : data.metric.name, value: p.dataKey === data.dailyCost.dataKey - ? formatGraphValue(p.value as number, data.dailyCost.format) - : formatGraphValue(p.value as number, data.metric.format), + ? formatGraphValue(Number(p.value), i, data.dailyCost.format) + : formatGraphValue(Number(p.value), i, data.metric.format), fill: p.dataKey === data.dailyCost.dataKey ? theme.palette.blue @@ -186,7 +182,6 @@ export const CostOverviewChart = ({ dataKey="trend" dot={false} isAnimationActive={false} - label={false} strokeWidth={2} stroke={theme.palette.blue} yAxisId={data.dailyCost.dataKey} @@ -196,7 +191,6 @@ export const CostOverviewChart = ({ dataKey={data.metric.dataKey} dot={false} isAnimationActive={false} - label={false} strokeWidth={2} stroke={theme.palette.magenta} yAxisId={data.metric.dataKey} diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx index 9fc816bbbf..737019e77a 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.test.tsx @@ -74,6 +74,22 @@ const renderProductInsightsCardInTestApp = async ( ); describe('', () => { + 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 the right subheader for products with cost data', async () => { const entity = { ...mockProductCost, diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx index d33f65bb83..b77b415e1e 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsChart.tsx @@ -15,11 +15,6 @@ */ import React, { useMemo, useState } from 'react'; -import { - ContentRenderer, - TooltipProps as RechartsTooltipProps, - RechartsFunction, -} from 'recharts'; import pluralize from 'pluralize'; import { Box, Typography } from '@material-ui/core'; import { default as FullScreenIcon } from '@material-ui/icons/Fullscreen'; @@ -55,6 +50,7 @@ import { } from '../../utils/styles'; import { Duration, Entity, Maybe } from '../../types'; import { choose } from '../../utils/change'; +import { TooltipRenderer } from '../../types/Tooltip'; export type ProductInsightsChartProps = { billingDate: string; @@ -96,7 +92,7 @@ export const ProductInsightsChart = ({ currentName: formatPeriod(duration, billingDate, true), }; - const onMouseMove: RechartsFunction = ( + const onMouseMove: (...args: any[]) => void = ( data: Record<'activeLabel', string | undefined>, ) => { if (isLabeled(data)) { @@ -108,7 +104,9 @@ export const ProductInsightsChart = ({ } }; - const onClick: RechartsFunction = (data: Record<'activeLabel', string>) => { + const onClick: (...args: any[]) => void = ( + data: Record<'activeLabel', string>, + ) => { if (isLabeled(data)) { setSelected(data.activeLabel); } else if (isUnlabeled(data)) { @@ -118,7 +116,7 @@ export const ProductInsightsChart = ({ } }; - const renderProductInsightsTooltip: ContentRenderer = ({ + const renderProductInsightsTooltip: TooltipRenderer = ({ label, payload = [], }) => { diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx index 5c4d56ae62..5541bf3d24 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx @@ -45,6 +45,22 @@ const MockProjectGrowthAlert = createMockProjectGrowthData(data => ({ })); describe('', () => { + 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('renders the correct title and subheader for multiple services', async () => { const subheader = new RegExp( `${MockAlertCosts.length} products, sorted by cost`, diff --git a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx index 5c226d4cb3..06353535a5 100644 --- a/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx +++ b/plugins/cost-insights/src/components/UnlabeledDataflowAlertCard/UnlabeledDataflowAlertCard.test.tsx @@ -40,6 +40,21 @@ const MockUnlabeledDataflowAlertSingleProject = createMockUnlabeledDataflowData( ); describe('', () => { + 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('renders the correct subheader for multiple projects', async () => { const subheader = new RegExp( `Showing costs from ${MockUnlabeledDataflowAlertMultipleProjects.projects.length} ` + diff --git a/packages/core-components/src/layout/HomepageTimer/index.ts b/plugins/cost-insights/src/types/Tooltip.ts similarity index 77% rename from packages/core-components/src/layout/HomepageTimer/index.ts rename to plugins/cost-insights/src/types/Tooltip.ts index bc004fc757..d94803d2d8 100644 --- a/packages/core-components/src/layout/HomepageTimer/index.ts +++ b/plugins/cost-insights/src/types/Tooltip.ts @@ -14,4 +14,10 @@ * limitations under the License. */ -export { HomepageTimer } from './HomepageTimer'; +import { ReactNode } from 'react'; +import { TooltipProps } from 'recharts'; + +/** @public */ +export type TooltipRenderer = ( + props: TooltipProps, +) => ReactNode; diff --git a/plugins/cost-insights/src/types/index.ts b/plugins/cost-insights/src/types/index.ts index 750114d3ec..a3a2ba0fae 100644 --- a/plugins/cost-insights/src/types/index.ts +++ b/plugins/cost-insights/src/types/index.ts @@ -27,6 +27,7 @@ export * from './Filters'; export * from './Icon'; export * from './Loading'; export * from './Theme'; +export * from './Tooltip'; /** * Deprecated types moved to `@backstage/plugin-cost-insights-common` diff --git a/plugins/cost-insights/src/utils/graphs.ts b/plugins/cost-insights/src/utils/graphs.ts index 31d8e6bc51..172f6cd7e3 100644 --- a/plugins/cost-insights/src/utils/graphs.ts +++ b/plugins/cost-insights/src/utils/graphs.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { TooltipPayload, TooltipProps } from 'recharts'; +import { TooltipProps } from 'recharts'; +import { Payload } from 'recharts/types/component/DefaultTooltipContent'; import { AlertCost, DataKey, Entity, ResourceData } from '../types'; import { currencyFormatter, @@ -22,7 +23,11 @@ import { lengthyCurrencyFormatter, } from './formatters'; -export function formatGraphValue(value: number, format?: string) { +export function formatGraphValue( + value: number, + _index: number, + format?: string, +) { if (format === 'number') { return value.toLocaleString(); } @@ -37,12 +42,12 @@ export function formatGraphValue(value: number, format?: string) { export const overviewGraphTickFormatter = (millis: string | number) => typeof millis === 'number' ? dateFormatter.format(millis) : millis; -export const tooltipItemOf = (payload: TooltipPayload) => { +export const tooltipItemOf = (payload: Payload) => { const value = typeof payload.value === 'number' ? currencyFormatter.format(payload.value) - : (payload.value as string); - const fill = payload.fill as string; + : payload.value; + const fill = payload.color as string; switch (payload.dataKey) { case DataKey.Current: @@ -67,7 +72,7 @@ export const titleOf = (label?: string | number) => { return label ? String(label) : 'Unlabeled'; }; -export const isInvalid = ({ label, payload }: TooltipProps) => { +export const isInvalid = ({ label, payload }: TooltipProps) => { // null labels are empty strings, which are valid return label === undefined || !payload || !payload.length; }; diff --git a/plugins/dynatrace/CHANGELOG.md b/plugins/dynatrace/CHANGELOG.md index 125976f131..4ab45e9df6 100644 --- a/plugins/dynatrace/CHANGELOG.md +++ b/plugins/dynatrace/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 + ## 1.0.0 ### Patch Changes diff --git a/plugins/dynatrace/README.md b/plugins/dynatrace/README.md index 8ed72ebb5d..e5684b2017 100644 --- a/plugins/dynatrace/README.md +++ b/plugins/dynatrace/README.md @@ -29,7 +29,7 @@ proxy: Authorization: 'Api-Token ${DYNATRACE_ACCESS_TOKEN}' ``` -It also requires a baseUrl for rendering links to problems in the table like so: +It also requires a `baseUrl` for rendering links to problems in the table like so: ```yaml dynatrace: diff --git a/plugins/dynatrace/package.json b/plugins/dynatrace/package.json index 7e024c439b..bdd54bf865 100644 --- a/plugins/dynatrace/package.json +++ b/plugins/dynatrace/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-dynatrace", - "version": "1.0.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx index aaa3ea0024..5b9c0d7b3f 100644 --- a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx +++ b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx @@ -31,6 +31,10 @@ describe('ProblemsTable', () => { , , ); - expect(await rendered.findByTitle('Search')).toBeInTheDocument(); + + // Checking for Title from mocked problems + expect( + await rendered.findByText('this IS a big problem'), + ).toBeInTheDocument(); }); }); diff --git a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx index 78d26e3fe2..12570be6ef 100644 --- a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx +++ b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { Table, TableColumn } from '@backstage/core-components'; import { DynatraceProblem } from '../../../api/DynatraceApi'; import { ProblemStatus } from '../ProblemStatus'; -import { Link } from '@material-ui/core'; +import { Link } from '@backstage/core-components'; type ProblemsTableProps = { problems: DynatraceProblem[]; @@ -36,7 +36,7 @@ export const ProblemsTable = (props: ProblemsTableProps) => { field: 'title', render: (row: Partial) => ( {row.title} diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 08eb87afc6..df0c013892 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 + ## 1.0.6 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 5bffe602ab..9d6419a76d 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.6", + "version": "1.0.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index 09b70db880..747dd912c0 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list-common +## 1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.1-next.0 + ## 1.0.5 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 17d67e99f7..23a16e10c7 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-common", - "version": "1.0.5", + "version": "1.0.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 9ce9eaae57..f08094181d 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 1.0.6 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 8bbf1f2e20..0b12ae6208 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.6", + "version": "1.0.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md index 19e15c302f..c139c37348 100644 --- a/plugins/explore-react/CHANGELOG.md +++ b/plugins/explore-react/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-explore-react +## 0.0.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.1.0-next.0 + ## 0.0.22 ### Patch Changes diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 9b5d3792cc..27ee4c23df 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore-react", "description": "A frontend library for Backstage plugins that want to interact with the explore plugin", - "version": "0.0.22", + "version": "0.0.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 09569f7206..bc31cd1ec6 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 + ## 0.3.41 ### Patch Changes diff --git a/plugins/explore/dev/index.tsx b/plugins/explore/dev/index.tsx index 71795a95b2..19e43d4d0f 100644 --- a/plugins/explore/dev/index.tsx +++ b/plugins/explore/dev/index.tsx @@ -16,12 +16,60 @@ import { Entity } from '@backstage/catalog-model'; import { createDevApp } from '@backstage/dev-utils'; +import { CatalogEntityPage, EntityLayout } from '@backstage/plugin-catalog'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { exploreToolsConfigRef } from '@backstage/plugin-explore-react'; import React from 'react'; import { ExplorePage, explorePlugin } from '../src'; import { exampleTools } from '../src/util/examples'; +const catalogApi: Partial = { + async getEntityByRef(): Promise { + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'example', + annotations: { + 'backstage.io/managed-by-location': 'file:/path/to/catalog-info.yaml', + }, + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'guest', + }, + } as Entity; + }, + async getEntities() { + const domainNames = [ + 'playback', + 'artists', + 'payments', + 'analytics', + 'songs', + 'devops', + ]; + return { + items: domainNames.map( + (n, i) => + ({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Domain', + metadata: { + name: n, + description: `Everything about ${n}`, + tags: i % 2 === 0 ? [n] : undefined, + }, + spec: { + owner: `${n}@example.com`, + }, + } as Entity), + ), + }; + }, +}; + createDevApp() .registerPlugin(explorePlugin) .registerApi({ @@ -36,37 +84,18 @@ createDevApp() .registerApi({ api: catalogApiRef, deps: {}, - factory: () => - ({ - async getEntities() { - const domainNames = [ - 'playback', - 'artists', - 'payments', - 'analytics', - 'songs', - 'devops', - ]; - - return { - items: domainNames.map( - (n, i) => - ({ - apiVersion: 'backstage.io/v1alpha1', - kind: 'Domain', - metadata: { - name: n, - description: `Everything about ${n}`, - tags: i % 2 === 0 ? [n] : undefined, - }, - spec: { - owner: `${n}@example.com`, - }, - } as Entity), - ), - }; - }, - } as CatalogApi), + factory: () => catalogApi as CatalogApi, }) .addPage({ element: , title: 'Explore' }) + .addPage({ + path: '/catalog/:namespace/:kind/:name', + element: , + children: ( + + +

Some Content Here

+
+
+ ), + }) .render(); diff --git a/plugins/explore/package.json b/plugins/explore/package.json index a148c3f789..7ff382b9ce 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore", "description": "A Backstage plugin for building an exploration page of your software ecosystem", - "version": "0.3.41", + "version": "0.3.42-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -54,6 +54,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/plugin-catalog": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md index 3516fa3d45..cd01270cbe 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 + ## 0.1.27 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 97831712ef..2c455fbd82 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-firehydrant", "description": "A Backstage plugin that integrates towards FireHydrant", - "version": "0.1.27", + "version": "0.1.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 231655bedc..bd43f4d7b8 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 + ## 0.2.42 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index c5be2f84cb..32d9da5146 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-fossa", "description": "A Backstage plugin that integrates towards FOSSA", - "version": "0.2.42", + "version": "0.2.43-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gcalendar/CHANGELOG.md b/plugins/gcalendar/CHANGELOG.md index 3dbedd4f66..3e4cc2473c 100644 --- a/plugins/gcalendar/CHANGELOG.md +++ b/plugins/gcalendar/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 + ## 0.3.6 ### Patch Changes diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index 96b15932e6..c6f748c6a2 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcalendar", - "version": "0.3.6", + "version": "0.3.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx index 89705a5a7b..5ea538df86 100644 --- a/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarEvent.tsx @@ -22,10 +22,10 @@ import { import React, { useState } from 'react'; import { useAnalytics } from '@backstage/core-plugin-api'; +import { Link } from '@backstage/core-components'; import { Box, - Link, Paper, Popover, Tooltip, @@ -133,12 +133,12 @@ export const CalendarEvent = ({ event }: { event: GCalendarEvent }) => { { e.stopPropagation(); analytics.captureEvent('click', 'zoom link'); }} + noTrack > Zoom link diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx index 4af33171d4..45e793470b 100644 --- a/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarEventPopoverContent.tsx @@ -18,12 +18,11 @@ import React from 'react'; import DOMPurify from 'dompurify'; import { useAnalytics } from '@backstage/core-plugin-api'; - +import { Link } from '@backstage/core-components'; import { Box, Divider, IconButton, - Link, Tooltip, Typography, makeStyles, @@ -77,11 +76,11 @@ export const CalendarEventPopoverContent = ({ analytics.captureEvent('click', 'open in calendar') } + noTrack > @@ -92,9 +91,9 @@ export const CalendarEventPopoverContent = ({
{zoomLink && ( analytics.captureEvent('click', 'zoom link')} + noTrack > Join Zoom Meeting diff --git a/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx b/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx index dbaa9051b6..8f563c63eb 100644 --- a/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/HomePageCalendar.test.tsx @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React from 'react'; import { googleAuthApiRef, storageApiRef } from '@backstage/core-plugin-api'; import { MockStorageApi, TestApiProvider, renderInTestApp, } from '@backstage/test-utils'; - -import { HomePageCalendar } from '.'; -import { gcalendarApiRef, gcalendarPlugin } from '../..'; +import { HomePageCalendar } from './HomePageCalendar'; +import { gcalendarApiRef } from '../../api'; +import { gcalendarPlugin } from '../../plugin'; describe('', () => { const primaryCalendar = { diff --git a/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx b/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx index 922c18da49..544d761102 100644 --- a/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/SignInContent.tsx @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; - import { Box, Button, styled } from '@material-ui/core'; - import { CalendarEvent } from './CalendarEvent'; import { eventsMock } from './signInEventMock'; -import { GCalendarEvent } from '../..'; +import { GCalendarEvent } from '../../api'; type Props = { handleAuthClick: React.MouseEventHandler; diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index 82e4638637..3e4f8df529 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 0.3.29 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index b180c40f45..6b1ea7a0d4 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gcp-projects", "description": "A Backstage plugin that helps you manage projects in GCP", - "version": "0.3.29", + "version": "0.3.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index 9280dc38c2..00b079b7cd 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 + ## 0.3.23 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index dee542a967..9b3533d994 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-git-release-manager", "description": "A Backstage plugin that helps you manage releases in git", - "version": "0.3.23", + "version": "0.3.24-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ "luxon": "^3.0.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", diff --git a/plugins/git-release-manager/src/api/GitReleaseClient.ts b/plugins/git-release-manager/src/api/GitReleaseClient.ts index 996b8bfdd3..91b1dbbecc 100644 --- a/plugins/git-release-manager/src/api/GitReleaseClient.ts +++ b/plugins/git-release-manager/src/api/GitReleaseClient.ts @@ -15,7 +15,7 @@ */ import { Octokit } from '@octokit/rest'; -import { GitHubIntegration, ScmIntegrations } from '@backstage/integration'; +import { GithubIntegration, ScmIntegrations } from '@backstage/integration'; import { DISABLE_CACHE } from '../constants/constants'; import { Project } from '../contexts/ProjectContext'; @@ -50,7 +50,7 @@ export class GitReleaseClient implements GitReleaseApi { private getGithubIntegrationConfig({ gitHubIntegrations, }: { - gitHubIntegrations: GitHubIntegration[]; + gitHubIntegrations: GithubIntegration[]; }) { const defaultIntegration = gitHubIntegrations.find( ({ config: { host } }) => host === 'github.com', diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index a4c0eed8c2..b3570d20cf 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 + ## 0.5.10 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 3540e00549..8247c48fa5 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-actions", "description": "A Backstage plugin that integrates towards GitHub Actions", - "version": "0.5.10", + "version": "0.5.11-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-actions/src/api/GithubActionsClient.ts b/plugins/github-actions/src/api/GithubActionsClient.ts index ebe3d92c4f..724081320f 100644 --- a/plugins/github-actions/src/api/GithubActionsClient.ts +++ b/plugins/github-actions/src/api/GithubActionsClient.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { GithubActionsApi } from './GithubActionsApi'; import { Octokit, RestEndpointMethodTypes } from '@octokit/rest'; import { ConfigApi, OAuthApi } from '@backstage/core-plugin-api'; @@ -36,7 +36,7 @@ export class GithubActionsClient implements GithubActionsApi { private async getOctokit(hostname?: string): Promise { // TODO: Get access token for the specified hostname const token = await this.githubAuthApi.getAccessToken(['repo']); - const configs = readGitHubIntegrationConfigs( + const configs = readGithubIntegrationConfigs( this.configApi.getOptionalConfigArray('integrations.github') ?? [], ); const githubIntegrationConfig = configs.find( diff --git a/plugins/github-actions/src/components/Cards/Cards.tsx b/plugins/github-actions/src/components/Cards/Cards.tsx index 79777ed8c3..e23f7f7d59 100644 --- a/plugins/github-actions/src/components/Cards/Cards.tsx +++ b/plugins/github-actions/src/components/Cards/Cards.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { useEntity } from '@backstage/plugin-catalog-react'; import { LinearProgress, @@ -88,7 +88,7 @@ export const LatestWorkflowRunCard = (props: { const config = useApi(configApiRef); const errorApi = useApi(errorApiRef); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = ( diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx index 931b5b676e..89fd40da33 100644 --- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx +++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { useEntity } from '@backstage/plugin-catalog-react'; import React, { useEffect } from 'react'; import { Link as RouterLink } from 'react-router-dom'; @@ -52,7 +52,7 @@ export const RecentWorkflowRunsCard = (props: { const errorApi = useApi(errorApiRef); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index 4e920a4e3a..f05ea692f5 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { Entity } from '@backstage/catalog-model'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { Accordion, AccordionDetails, @@ -22,7 +22,6 @@ import { Box, CircularProgress, LinearProgress, - Link as MaterialLink, ListItemText, makeStyles, Paper, @@ -170,7 +169,7 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = (projectName && projectName.split('/')) || []; @@ -246,10 +245,10 @@ export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { {details.value?.html_url && ( - + Workflow runs on GitHub{' '} - + )} diff --git a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx index bf66399288..075b73dd98 100644 --- a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx +++ b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { LogViewer } from '@backstage/core-components'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { Accordion, AccordionSummary, @@ -80,7 +80,7 @@ export const WorkflowRunLogs = ({ const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = (projectName && projectName.split('/')) || []; diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index c8c42eb1bc..88f8567b90 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -15,7 +15,6 @@ */ import React from 'react'; import { - Link, Typography, Box, IconButton, @@ -31,9 +30,14 @@ import SyncIcon from '@material-ui/icons/Sync'; import { buildRouteRef } from '../../routes'; import { getProjectNameFromEntity } from '../getProjectNameFromEntity'; import { Entity } from '@backstage/catalog-model'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; -import { EmptyState, Table, TableColumn } from '@backstage/core-components'; +import { + EmptyState, + Table, + TableColumn, + Link, +} from '@backstage/core-components'; import { configApiRef, useApi, useRouteRef } from '@backstage/core-plugin-api'; const generatedColumns: TableColumn[] = [ @@ -159,7 +163,7 @@ export const WorkflowRunsTable = ({ const config = useApi(configApiRef); const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation - const hostname = readGitHubIntegrationConfigs( + const hostname = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; const [owner, repo] = (projectName ?? '/').split('/'); diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 120740492c..0a9a4215df 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 + ## 0.1.41 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index eaaaac9ae1..a25fab05db 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-deployments", "description": "A Backstage plugin that integrates towards GitHub Deployments", - "version": "0.1.41", + "version": "0.1.42-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-issues/CHANGELOG.md b/plugins/github-issues/CHANGELOG.md index 8d5aa17129..a812495a5f 100644 --- a/plugins/github-issues/CHANGELOG.md +++ b/plugins/github-issues/CHANGELOG.md @@ -1,5 +1,25 @@ # @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 + ## 0.1.2 ### Patch Changes diff --git a/plugins/github-issues/api-report.md b/plugins/github-issues/api-report.md index 41b4f87064..4e57eeda46 100644 --- a/plugins/github-issues/api-report.md +++ b/plugins/github-issues/api-report.md @@ -9,7 +9,15 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { RouteRef } from '@backstage/core-plugin-api'; // @public (undocumented) -export const GitHubIssuesCard: (props: GitHubIssuesProps) => JSX.Element; +export interface GithubIssuesByRepoOptions { + // (undocumented) + filterBy?: GithubIssuesFilters; + // (undocumented) + orderBy?: GithubIssuesOrdering; +} + +// @public (undocumented) +export const GithubIssuesCard: (props: GithubIssuesProps) => JSX.Element; // @public (undocumented) export interface GithubIssuesFilters { @@ -36,10 +44,10 @@ export interface GithubIssuesOrdering { } // @public (undocumented) -export const GitHubIssuesPage: (props: GitHubIssuesProps) => JSX.Element; +export const GithubIssuesPage: (props: GithubIssuesProps) => JSX.Element; // @public (undocumented) -export const gitHubIssuesPlugin: BackstagePlugin< +export const githubIssuesPlugin: BackstagePlugin< { root: RouteRef; }, @@ -48,20 +56,12 @@ export const gitHubIssuesPlugin: BackstagePlugin< >; // @public (undocumented) -export type GitHubIssuesProps = { +export type GithubIssuesProps = { itemsPerPage?: number; itemsPerRepo?: number; filterBy?: GithubIssuesFilters; orderBy?: GithubIssuesOrdering; }; -// @public (undocumented) -export interface GitubIssuesByRepoOptions { - // (undocumented) - filterBy?: GithubIssuesFilters; - // (undocumented) - orderBy?: GithubIssuesOrdering; -} - // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/github-issues/dev/index.tsx b/plugins/github-issues/dev/index.tsx index a5f20ab9b9..05592cfa63 100644 --- a/plugins/github-issues/dev/index.tsx +++ b/plugins/github-issues/dev/index.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; import { @@ -20,21 +21,20 @@ import { catalogApiRef, EntityProvider, } from '@backstage/plugin-catalog-react'; - -import { gitHubIssuesPlugin, GitHubIssuesPage } from '../src'; -import { GitHubIssuesApi, gitHubIssuesApiRef } from '../src/api'; +import { githubIssuesPlugin, GithubIssuesPage } from '../src'; +import { GithubIssuesApi, githubIssuesApiRef } from '../src/api'; import testData from './__fixtures__/component-issues-data.json'; createDevApp() - .registerPlugin(gitHubIssuesPlugin) + .registerPlugin(githubIssuesPlugin) .registerApi({ - api: gitHubIssuesApiRef, + api: githubIssuesApiRef, deps: {}, factory: () => ({ - fetchIssuesByRepoFromGitHub: async () => testData, - } as GitHubIssuesApi), + fetchIssuesByRepoFromGithub: async () => testData, + } as GithubIssuesApi), }) .registerApi({ api: catalogApiRef, @@ -59,7 +59,7 @@ createDevApp() kind: 'Component', }} > - +
), }) diff --git a/plugins/github-issues/package.json b/plugins/github-issues/package.json index 0249fe3a94..3853e845a6 100644 --- a/plugins/github-issues/package.json +++ b/plugins/github-issues/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-issues", - "version": "0.1.2", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-issues/src/api/gitHubIssuesApi.test.ts b/plugins/github-issues/src/api/githubIssuesApi.test.ts similarity index 93% rename from plugins/github-issues/src/api/gitHubIssuesApi.test.ts rename to plugins/github-issues/src/api/githubIssuesApi.test.ts index b2be54a2d1..e960d3123b 100644 --- a/plugins/github-issues/src/api/gitHubIssuesApi.test.ts +++ b/plugins/github-issues/src/api/githubIssuesApi.test.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + const mockGraphQLQuery = jest.fn(() => ({})); jest.mock('octokit', () => ({ Octokit: jest.fn(() => ({ graphql: mockGraphQLQuery })), @@ -20,8 +21,8 @@ jest.mock('octokit', () => ({ import { ConfigApi, ErrorApi } from '@backstage/core-plugin-api'; import { ForwardedError } from '@backstage/errors'; -import { createFilterByClause, gitHubIssuesApi } from './gitHubIssuesApi'; -import type { GithubIssuesFilters } from './gitHubIssuesApi'; +import { createFilterByClause, githubIssuesApi } from './githubIssuesApi'; +import type { GithubIssuesFilters } from './githubIssuesApi'; function getFragment( filterBy = '', @@ -84,21 +85,21 @@ function getFragment( ' ...issues\n' + ' }\n' + ' \n' + - ' } \n' + + ' }\n' + ' ' ); } -describe('gitHubIssuesApi', () => { - describe('fetchIssuesByRepoFromGitHub', () => { - let api: ReturnType; +describe('githubIssuesApi', () => { + describe('fetchIssuesByRepoFromGithub', () => { + let api: ReturnType; afterEach(() => { jest.clearAllMocks(); }); beforeEach(() => { - api = gitHubIssuesApi( + api = githubIssuesApi( { getAccessToken: jest.fn() }, { getOptionalConfigArray: jest.fn(), @@ -108,7 +109,7 @@ describe('gitHubIssuesApi', () => { }); it('should call GitHub API with correct query with fragment for each repo', async () => { - await api.fetchIssuesByRepoFromGitHub( + await api.fetchIssuesByRepoFromGithub( ['mrwolny/yo-yo', 'mrwolny/yoyo', 'mrwolny/yo.yo'], 10, ); @@ -118,7 +119,7 @@ describe('gitHubIssuesApi', () => { }); it('should call Github API with the correct filterBy and orderBy clauses', async () => { - await api.fetchIssuesByRepoFromGitHub( + await api.fetchIssuesByRepoFromGithub( ['mrwolny/yo-yo', 'mrwolny/yoyo', 'mrwolny/yo.yo'], 10, { @@ -221,7 +222,7 @@ describe('gitHubIssuesApi', () => { }), ); - const api = gitHubIssuesApi( + const api = githubIssuesApi( { getAccessToken: jest.fn() }, { getOptionalConfigArray: jest.fn(), @@ -229,7 +230,7 @@ describe('gitHubIssuesApi', () => { { post: jest.fn() } as unknown as ErrorApi, ); - const data = await api.fetchIssuesByRepoFromGitHub( + const data = await api.fetchIssuesByRepoFromGithub( ['mrwolny/yo-yo', 'mrwolny/notfound'], 10, ); @@ -291,7 +292,7 @@ describe('gitHubIssuesApi', () => { }), ); - const api = gitHubIssuesApi( + const api = githubIssuesApi( { getAccessToken: jest.fn() }, { getOptionalConfigArray: jest.fn(), @@ -299,7 +300,7 @@ describe('gitHubIssuesApi', () => { { post: jest.fn() } as unknown as ErrorApi, ); - const data = await api.fetchIssuesByRepoFromGitHub( + const data = await api.fetchIssuesByRepoFromGithub( ['mrwolny/notfound'], 10, ); @@ -332,7 +333,7 @@ describe('gitHubIssuesApi', () => { const mockErrorApi = { post: jest.fn() }; - const api = gitHubIssuesApi( + const api = githubIssuesApi( { getAccessToken: jest.fn() }, { getOptionalConfigArray: jest.fn(), @@ -340,7 +341,7 @@ describe('gitHubIssuesApi', () => { mockErrorApi as unknown as ErrorApi, ); - await api.fetchIssuesByRepoFromGitHub(['mrwolny/notfound'], 10); + await api.fetchIssuesByRepoFromGithub(['mrwolny/notfound'], 10); expect(mockErrorApi.post).toHaveBeenCalledTimes(1); expect(mockErrorApi.post).toHaveBeenCalledWith( diff --git a/plugins/github-issues/src/api/gitHubIssuesApi.ts b/plugins/github-issues/src/api/githubIssuesApi.ts similarity index 91% rename from plugins/github-issues/src/api/gitHubIssuesApi.ts rename to plugins/github-issues/src/api/githubIssuesApi.ts index b4be445d13..32331ef6b1 100644 --- a/plugins/github-issues/src/api/gitHubIssuesApi.ts +++ b/plugins/github-issues/src/api/githubIssuesApi.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Octokit } from 'octokit'; import { createApiRef, @@ -20,7 +21,7 @@ import { ErrorApi, OAuthApi, } from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; import { ForwardedError } from '@backstage/errors'; /** @internal */ @@ -71,7 +72,7 @@ export type RepoIssues = { export type IssuesByRepo = Record; /** @internal */ -export type GitHubIssuesApi = ReturnType; +export type GithubIssuesApi = ReturnType; /** * @public @@ -96,18 +97,18 @@ export interface GithubIssuesOrdering { /** * @public */ -export interface GitubIssuesByRepoOptions { +export interface GithubIssuesByRepoOptions { filterBy?: GithubIssuesFilters; orderBy?: GithubIssuesOrdering; } /** @internal */ -export const gitHubIssuesApiRef = createApiRef({ +export const githubIssuesApiRef = createApiRef({ id: 'plugin.githubissues.service', }); /** @internal */ -export const gitHubIssuesApi = ( +export const githubIssuesApi = ( githubAuthApi: OAuthApi, configApi: ConfigApi, errorApi: ErrorApi, @@ -115,7 +116,7 @@ export const gitHubIssuesApi = ( let octokit: Octokit; const getOctokit = async () => { - const baseUrl = readGitHubIntegrationConfigs( + const baseUrl = readGithubIntegrationConfigs( configApi.getOptionalConfigArray('integrations.github') ?? [], )[0].apiBaseUrl; @@ -128,7 +129,7 @@ export const gitHubIssuesApi = ( return octokit.graphql; }; - const fetchIssuesByRepoFromGitHub = async ( + const fetchIssuesByRepoFromGithub = async ( repos: Array, itemsPerRepo: number, { @@ -137,7 +138,7 @@ export const gitHubIssuesApi = ( field: 'UPDATED_AT', direction: 'DESC', }, - }: GitubIssuesByRepoOptions = {}, + }: GithubIssuesByRepoOptions = {}, ): Promise => { const graphql = await getOctokit(); const safeNames: Array = []; @@ -186,7 +187,7 @@ export const gitHubIssuesApi = ( }, {} as IssuesByRepo); }; - return { fetchIssuesByRepoFromGitHub }; + return { fetchIssuesByRepoFromGithub }; }; function formatFilterValue( @@ -224,7 +225,7 @@ function createIssueByRepoQuery( owner: string; }>, itemsPerRepo: number, - { filterBy, orderBy }: GitubIssuesByRepoOptions, + { filterBy, orderBy }: GithubIssuesByRepoOptions, ): string { const fragment = ` fragment issues on Repository { @@ -278,7 +279,7 @@ function createIssueByRepoQuery( } `, )} - } + } `; return query; diff --git a/plugins/github-issues/src/api/index.ts b/plugins/github-issues/src/api/index.ts index 4cc3372c45..91f058f399 100644 --- a/plugins/github-issues/src/api/index.ts +++ b/plugins/github-issues/src/api/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './gitHubIssuesApi'; + +export * from './githubIssuesApi'; diff --git a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.test.tsx b/plugins/github-issues/src/components/GithubIssues/GithubIssues.test.tsx similarity index 90% rename from plugins/github-issues/src/components/GitHubIssues/GitHubIssues.test.tsx rename to plugins/github-issues/src/components/GithubIssues/GithubIssues.test.tsx index 33eec79359..3f9e829de9 100644 --- a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.test.tsx +++ b/plugins/github-issues/src/components/GithubIssues/GithubIssues.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { @@ -21,10 +22,8 @@ import { CatalogApi, } from '@backstage/plugin-catalog-react'; import { Entity } from '@backstage/catalog-model'; - -import { GitHubIssuesApi, gitHubIssuesApiRef, Issue } from '../../api'; - -import { GitHubIssues } from './GitHubIssues'; +import { GithubIssuesApi, githubIssuesApiRef, Issue } from '../../api'; +import { GithubIssues } from './GithubIssues'; const getTestIssue = (overwrites: Partial = {}): { node: Issue } => ({ node: { @@ -80,10 +79,10 @@ const mockCatalogApi = { getEntities: () => ({}), } as CatalogApi; -describe('GitHubIssues', () => { +describe('GithubIssues', () => { it('should render correctly when there are no issues in GitHub', async () => { const mockApi = { - fetchIssuesByRepoFromGitHub: async () => ({ + fetchIssuesByRepoFromGithub: async () => ({ backstage: { issues: { totalCount: 0, @@ -91,17 +90,17 @@ describe('GitHubIssues', () => { }, }, }), - } as GitHubIssuesApi; + } as GithubIssuesApi; const apis = [ - [gitHubIssuesApiRef, mockApi], + [githubIssuesApiRef, mockApi], [catalogApiRef, mockCatalogApi], ] as const; const { getByTestId } = await renderInTestApp( - + , ); @@ -118,7 +117,7 @@ describe('GitHubIssues', () => { }); const mockApi = { - fetchIssuesByRepoFromGitHub: async () => ({ + fetchIssuesByRepoFromGithub: async () => ({ backstage: { issues: { totalCount: 1, @@ -126,16 +125,16 @@ describe('GitHubIssues', () => { }, }, }), - } as GitHubIssuesApi; + } as GithubIssuesApi; const apis = [ - [gitHubIssuesApiRef, mockApi], + [githubIssuesApiRef, mockApi], [catalogApiRef, mockCatalogApi], ] as const; const { getByText, getByTestId } = await renderInTestApp( - + , ); diff --git a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx b/plugins/github-issues/src/components/GithubIssues/GithubIssues.tsx similarity index 79% rename from plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx rename to plugins/github-issues/src/components/GithubIssues/GithubIssues.tsx index cb6639c83c..faae7fd3a5 100644 --- a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx +++ b/plugins/github-issues/src/components/GithubIssues/GithubIssues.tsx @@ -13,41 +13,39 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React from 'react'; import { Box, IconButton, Typography } from '@material-ui/core'; import { InfoCard, Progress } from '@backstage/core-components'; import RefreshIcon from '@material-ui/icons/Refresh'; - -import { useEntityGitHubRepositories } from '../../hooks/useEntityGitHubRepositories'; -import { useGetIssuesByRepoFromGitHub } from '../../hooks/useGetIssuesByRepoFromGitHub'; - +import { useEntityGithubRepositories } from '../../hooks/useEntityGithubRepositories'; +import { useGetIssuesByRepoFromGithub } from '../../hooks/useGetIssuesByRepoFromGithub'; import { IssuesList } from './IssuesList'; import { NoRepositoriesInfo } from './NoRepositoriesInfo'; import type { GithubIssuesFilters, GithubIssuesOrdering, -} from '../../api/gitHubIssuesApi'; +} from '../../api/githubIssuesApi'; /** * @public */ -export type GitHubIssuesProps = { +export type GithubIssuesProps = { itemsPerPage?: number; itemsPerRepo?: number; filterBy?: GithubIssuesFilters; orderBy?: GithubIssuesOrdering; }; -export const GitHubIssues = (props: GitHubIssuesProps) => { +export const GithubIssues = (props: GithubIssuesProps) => { const { itemsPerPage = 10, itemsPerRepo = 40, filterBy, orderBy } = props; - const { repositories } = useEntityGitHubRepositories(); + const { repositories } = useEntityGithubRepositories(); const { isLoading, - gitHubIssuesByRepo: issuesByRepository, + githubIssuesByRepo: issuesByRepository, retry, - } = useGetIssuesByRepoFromGitHub(repositories, itemsPerRepo, { + } = useGetIssuesByRepoFromGithub(repositories, itemsPerRepo, { filterBy, orderBy, }); diff --git a/plugins/github-issues/src/components/GitHubIssues/IssueCard/Assignees.tsx b/plugins/github-issues/src/components/GithubIssues/IssueCard/Assignees.tsx similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssueCard/Assignees.tsx rename to plugins/github-issues/src/components/GithubIssues/IssueCard/Assignees.tsx index 173c8d9439..4b66048039 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssueCard/Assignees.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/Assignees.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { Typography, Box, Avatar, makeStyles } from '@material-ui/core'; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssueCard/CommentsCount.tsx b/plugins/github-issues/src/components/GithubIssues/IssueCard/CommentsCount.tsx similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssueCard/CommentsCount.tsx rename to plugins/github-issues/src/components/GithubIssues/IssueCard/CommentsCount.tsx index 80a365cbbb..35352d10d2 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssueCard/CommentsCount.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/CommentsCount.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { ChatIcon } from '@backstage/core-components'; import { Box, Badge } from '@material-ui/core'; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssueCard/IssueCard.tsx b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx similarity index 92% rename from plugins/github-issues/src/components/GitHubIssues/IssueCard/IssueCard.tsx rename to plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx index ccf30a9a84..a5a5b79ac0 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssueCard/IssueCard.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/IssueCard.tsx @@ -13,21 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { DateTime } from 'luxon'; -import { - Box, - Paper, - Typography, - CardActionArea, - Link, -} from '@material-ui/core'; +import { Link } from '@backstage/core-components'; +import { Box, CardActionArea, Paper, Typography } from '@material-ui/core'; +import Divider from '@material-ui/core/Divider'; +import { DateTime } from 'luxon'; +import React from 'react'; import { Assignees } from './Assignees'; import { CommentsCount } from './CommentsCount'; -import Divider from '@material-ui/core/Divider'; - type IssueCardProps = { title: string; createdAt: string; @@ -64,10 +58,7 @@ export const IssueCard = (props: IssueCardProps) => { - + {repositoryName} diff --git a/plugins/github-issues/src/components/GitHubIssues/IssueCard/index.ts b/plugins/github-issues/src/components/GithubIssues/IssueCard/index.ts similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssueCard/index.ts rename to plugins/github-issues/src/components/GithubIssues/IssueCard/index.ts index 5c60abf6ab..635bcd89f4 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssueCard/index.ts +++ b/plugins/github-issues/src/components/GithubIssues/IssueCard/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { IssueCard } from './IssueCard'; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/Filters.tsx b/plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/Filters.tsx similarity index 98% rename from plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/Filters.tsx rename to plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/Filters.tsx index de071ab5bb..f9e22980db 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/Filters.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/Filters.tsx @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import React from 'react'; import { Select, SelectedItems, SelectItem } from '@backstage/core-components'; import { makeStyles, Box, Typography } from '@material-ui/core'; type RepositoryFiltersProps = { items: Array; - totalIssuesInGitHub: number; + totalIssuesInGithub: number; placeholder: string; onChange: (active: Array) => void; }; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/index.ts b/plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/index.ts similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/index.ts rename to plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/index.ts index 331d017455..a9e4f87642 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssuesList/Filters/index.ts +++ b/plugins/github-issues/src/components/GithubIssues/IssuesList/Filters/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export * from './Filters'; diff --git a/plugins/github-issues/src/components/GitHubIssues/IssuesList/IssuesList.tsx b/plugins/github-issues/src/components/GithubIssues/IssuesList/IssuesList.tsx similarity index 97% rename from plugins/github-issues/src/components/GitHubIssues/IssuesList/IssuesList.tsx rename to plugins/github-issues/src/components/GithubIssues/IssuesList/IssuesList.tsx index b46eab22b6..27f6375308 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssuesList/IssuesList.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssuesList/IssuesList.tsx @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React from 'react'; import { Box } from '@material-ui/core'; import { Pagination } from '@material-ui/lab'; - import { IssueCard } from '../IssueCard'; import { IssuesByRepo } from '../../../api'; import { RepositoryFilters } from './Filters'; @@ -60,7 +59,7 @@ export const IssuesList = ({ [issuesByRepository], ); - const totalIssuesInGitHub = React.useMemo( + const totalIssuesInGithub = React.useMemo( () => issuesByRepository ? Object.values(issuesByRepository).reduce( @@ -113,12 +112,12 @@ export const IssuesList = ({ {issues.length > 0 && ( )} diff --git a/plugins/github-issues/src/components/GitHubIssues/IssuesList/index.tsx b/plugins/github-issues/src/components/GithubIssues/IssuesList/index.tsx similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/IssuesList/index.tsx rename to plugins/github-issues/src/components/GithubIssues/IssuesList/index.tsx index 0b7514c720..ca6da2e0bd 100644 --- a/plugins/github-issues/src/components/GitHubIssues/IssuesList/index.tsx +++ b/plugins/github-issues/src/components/GithubIssues/IssuesList/index.tsx @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export * from './IssuesList'; diff --git a/plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx b/plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx similarity index 100% rename from plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx rename to plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx index dca4b872ae..07fb4019dc 100644 --- a/plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx +++ b/plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/NoRepositoriesInfo.tsx @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React from 'react'; import { EmptyState } from '@backstage/core-components'; export const NoRepositoriesInfo = () => { diff --git a/plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/index.tsx b/plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/index.tsx similarity index 99% rename from plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/index.tsx rename to plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/index.tsx index 577bb475e3..ea4cef40a6 100644 --- a/plugins/github-issues/src/components/GitHubIssues/NoRepositoriesInfo/index.tsx +++ b/plugins/github-issues/src/components/GithubIssues/NoRepositoriesInfo/index.tsx @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export * from './NoRepositoriesInfo'; diff --git a/plugins/github-issues/src/components/GithubIssues/index.ts b/plugins/github-issues/src/components/GithubIssues/index.ts new file mode 100644 index 0000000000..865168b3e2 --- /dev/null +++ b/plugins/github-issues/src/components/GithubIssues/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 './GithubIssues'; diff --git a/plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts b/plugins/github-issues/src/hooks/useEntityGithubRepositories.ts similarity index 97% rename from plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts rename to plugins/github-issues/src/hooks/useEntityGithubRepositories.ts index d7b60575ad..f3fab72090 100644 --- a/plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts +++ b/plugins/github-issues/src/hooks/useEntityGithubRepositories.ts @@ -25,7 +25,7 @@ export const getProjectNameFromEntity = (entity: Entity): string => { return entity?.metadata.annotations?.[GITHUB_PROJECT_SLUG_ANNOTATION] ?? ''; }; -export function useEntityGitHubRepositories() { +export function useEntityGithubRepositories() { const { entity } = useEntity(); const catalogApi = useApi(catalogApiRef); diff --git a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGithub.ts similarity index 75% rename from plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts rename to plugins/github-issues/src/hooks/useGetIssuesByRepoFromGithub.ts index 693be9e4d9..8ec5f3ec65 100644 --- a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts +++ b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGithub.ts @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { useApi } from '@backstage/core-plugin-api'; - import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { gitHubIssuesApiRef, GitubIssuesByRepoOptions } from '../api'; +import { githubIssuesApiRef, GithubIssuesByRepoOptions } from '../api'; -export const useGetIssuesByRepoFromGitHub = ( +export const useGetIssuesByRepoFromGithub = ( repos: Array, itemsPerRepo: number, - options?: GitubIssuesByRepoOptions, + options?: GithubIssuesByRepoOptions, ) => { - const gitHubIssuesApi = useApi(gitHubIssuesApiRef); + const githubIssuesApi = useApi(githubIssuesApiRef); const { value: issues, @@ -31,7 +31,7 @@ export const useGetIssuesByRepoFromGitHub = ( retry, } = useAsyncRetry(async () => { if (repos.length > 0) { - return await gitHubIssuesApi.fetchIssuesByRepoFromGitHub( + return await githubIssuesApi.fetchIssuesByRepoFromGithub( repos, itemsPerRepo, options, @@ -41,5 +41,5 @@ export const useGetIssuesByRepoFromGitHub = ( return {}; }, [repos]); - return { isLoading, gitHubIssuesByRepo: issues, retry }; + return { isLoading, githubIssuesByRepo: issues, retry }; }; diff --git a/plugins/github-issues/src/index.ts b/plugins/github-issues/src/index.ts index 7717ba5f33..b44c1e8773 100644 --- a/plugins/github-issues/src/index.ts +++ b/plugins/github-issues/src/index.ts @@ -13,15 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { - gitHubIssuesPlugin, - GitHubIssuesPage, - GitHubIssuesCard, -} from './plugin'; -export type { GitHubIssuesProps } from './components/GitHubIssues'; +export { + githubIssuesPlugin, + GithubIssuesPage, + GithubIssuesCard, +} from './plugin'; +export type { GithubIssuesProps } from './components/GithubIssues'; export type { GithubIssuesFilters, GithubIssuesOrdering, - GitubIssuesByRepoOptions, -} from './api/gitHubIssuesApi'; + GithubIssuesByRepoOptions, +} from './api/githubIssuesApi'; diff --git a/plugins/github-issues/src/plugin.test.ts b/plugins/github-issues/src/plugin.test.ts index e6749dd970..f611e1b1e2 100644 --- a/plugins/github-issues/src/plugin.test.ts +++ b/plugins/github-issues/src/plugin.test.ts @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { gitHubIssuesPlugin } from './plugin'; + +import { githubIssuesPlugin } from './plugin'; describe('github-issues', () => { it('should export plugin', () => { - expect(gitHubIssuesPlugin).toBeDefined(); + expect(githubIssuesPlugin).toBeDefined(); }); }); diff --git a/plugins/github-issues/src/plugin.ts b/plugins/github-issues/src/plugin.ts index 3d845f4adf..52446efe87 100644 --- a/plugins/github-issues/src/plugin.ts +++ b/plugins/github-issues/src/plugin.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { createPlugin, createApiFactory, @@ -22,23 +23,22 @@ import { errorApiRef, githubAuthApiRef, } from '@backstage/core-plugin-api'; -import { gitHubIssuesApi, gitHubIssuesApiRef } from './api'; - +import { githubIssuesApi, githubIssuesApiRef } from './api'; import { rootRouteRef } from './routes'; /** @public */ -export const gitHubIssuesPlugin = createPlugin({ +export const githubIssuesPlugin = createPlugin({ id: 'github-issues', apis: [ createApiFactory({ - api: gitHubIssuesApiRef, + api: githubIssuesApiRef, deps: { configApi: configApiRef, githubAuthApi: githubAuthApiRef, errorApi: errorApiRef, }, factory: ({ configApi, githubAuthApi, errorApi }) => - gitHubIssuesApi(githubAuthApi, configApi, errorApi), + githubIssuesApi(githubAuthApi, configApi, errorApi), }), ], routes: { @@ -47,21 +47,21 @@ export const gitHubIssuesPlugin = createPlugin({ }); /** @public */ -export const GitHubIssuesCard = gitHubIssuesPlugin.provide( +export const GithubIssuesCard = githubIssuesPlugin.provide( createComponentExtension({ - name: 'GitHubIssuesCard', + name: 'GithubIssuesCard', component: { - lazy: () => import('./components/GitHubIssues').then(m => m.GitHubIssues), + lazy: () => import('./components/GithubIssues').then(m => m.GithubIssues), }, }), ); /** @public */ -export const GitHubIssuesPage = gitHubIssuesPlugin.provide( +export const GithubIssuesPage = githubIssuesPlugin.provide( createRoutableExtension({ - name: 'GitHubIssuesPage', + name: 'GithubIssuesPage', component: () => - import('./components/GitHubIssues').then(m => m.GitHubIssues), + import('./components/GithubIssues').then(m => m.GithubIssues), mountPoint: rootRouteRef, }), ); diff --git a/plugins/github-pull-requests-board/CHANGELOG.md b/plugins/github-pull-requests-board/CHANGELOG.md index d5c65069dd..a91ceae5f0 100644 --- a/plugins/github-pull-requests-board/CHANGELOG.md +++ b/plugins/github-pull-requests-board/CHANGELOG.md @@ -1,9 +1,28 @@ # @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 + ## 0.1.4 ### Patch Changes +- 80d75adf3a: Replace the momentjs dependency with luxon. - 719ccbb963: Properly filter on relations instead of the spec, when finding by owner - Updated dependencies - @backstage/catalog-model@1.1.2 diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index e4e9d0827a..c6f58ebf0f 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-pull-requests-board", "description": "A Backstage plugin that allows you to see all open Pull Requests for all the repositories owned by your team", - "version": "0.1.4", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -45,6 +45,7 @@ "@material-ui/lab": "4.0.0-alpha.57", "@octokit/rest": "^19.0.3", "luxon": "^3.0.0", + "p-limit": "^4.0.0", "react-use": "^17.2.4" }, "devDependencies": { diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts index d44c36b54d..3f818c63fc 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts @@ -33,6 +33,9 @@ export const useGetPullRequestDetails = () => { id repository { name + owner { + login + } } title url diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts index 2295a01442..e1daf491cc 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts @@ -33,7 +33,7 @@ export const useGetPullRequestsFromRepository = () => { const limit = pullRequestLimit ?? PULL_REQUEST_LIMIT; const [organisation, repositoryName] = repo.split('/'); - return await getPullRequestEdges( + return await getPullRequestNodes( graphql, repositoryName, organisation, @@ -45,7 +45,7 @@ export const useGetPullRequestsFromRepository = () => { return fn.current; }; -async function getPullRequestEdges( +async function getPullRequestNodes( graphql: ( path: string, options?: any, @@ -54,7 +54,7 @@ async function getPullRequestEdges( organisation: string, pullRequestLimit: number, ): Promise { - const pullRequestEdges: PullRequestsNumber[] = []; + const pullRequestNodes: PullRequestsNumber[] = []; let result: GraphQlPullRequests | undefined = undefined; do { @@ -68,10 +68,8 @@ async function getPullRequestEdges( ) { repository(name: $name, owner: $owner) { pullRequests(states: OPEN, first: $first, after: $endCursor) { - edges { - node { - number - } + nodes { + number } pageInfo { hasNextPage @@ -94,10 +92,10 @@ async function getPullRequestEdges( }, ); - pullRequestEdges.push(...result.repository.pullRequests.edges); + pullRequestNodes.push(...result.repository.pullRequests.nodes); - if (pullRequestEdges.length >= pullRequestLimit) return pullRequestEdges; + if (pullRequestNodes.length >= pullRequestLimit) return pullRequestNodes; } while (result.repository.pullRequests.pageInfo.hasNextPage); - return pullRequestEdges; + return pullRequestNodes; } diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.ts new file mode 100644 index 0000000000..fb4fb66bd3 --- /dev/null +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromUser.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 React from 'react'; + +import { + GraphQlUserPullRequests, + PullRequestsNumberAndOwner, +} from '../utils/types'; +import { useOctokitGraphQl } from './useOctokitGraphQl'; + +const PULL_REQUEST_LIMIT = 10; +const GITHUB_GRAPHQL_MAX_ITEMS = 100; + +export const useGetPullRequestsFromUser = () => { + const graphql = + useOctokitGraphQl>(); + + const fn = React.useRef( + async ( + userLogin: string, + organization?: string, + pullRequestLimit?: number, + ): Promise => { + const limit = pullRequestLimit ?? PULL_REQUEST_LIMIT; + + return await getPullRequestNodes(graphql, userLogin, limit, organization); + }, + ); + + return fn.current; +}; + +async function getPullRequestNodes( + graphql: ( + path: string, + options?: any, + ) => Promise>, + userLogin: string, + pullRequestLimit: number, + organization?: string, +): Promise { + const pullRequestNodes: PullRequestsNumberAndOwner[] = []; + let result: + | GraphQlUserPullRequests + | undefined = undefined; + + do { + result = await graphql( + ` + query ($login: String!, $first: Int, $endCursor: String) { + user(login: $login) { + pullRequests(states: OPEN, first: $first, after: $endCursor) { + nodes { + number + repository { + name + owner { + login + } + } + } + pageInfo { + hasNextPage + endCursor + } + } + } + } + `, + { + login: userLogin, + first: + pullRequestLimit > GITHUB_GRAPHQL_MAX_ITEMS + ? GITHUB_GRAPHQL_MAX_ITEMS + : pullRequestLimit, + endCursor: result + ? result.user.pullRequests.pageInfo.endCursor + : undefined, + }, + ); + + pullRequestNodes.push( + ...result.user.pullRequests.nodes.filter( + edge => + !organization || + edge?.repository?.owner?.login?.toLocaleLowerCase('en-US') === + organization.toLocaleLowerCase('en-US'), + ), + ); + + if (pullRequestNodes.length >= pullRequestLimit) return pullRequestNodes; + } while (result.user.pullRequests.pageInfo.hasNextPage); + + return pullRequestNodes; +} diff --git a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts index a864f9950e..6dadcef6d2 100644 --- a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts +++ b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts @@ -19,7 +19,7 @@ import { githubAuthApiRef, configApiRef, } from '@backstage/core-plugin-api'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; +import { readGithubIntegrationConfigs } from '@backstage/integration'; let octokit: any; @@ -27,7 +27,7 @@ export const useOctokitGraphQl = () => { const auth = useApi(githubAuthApiRef); const config = useApi(configApiRef); - const baseUrl = readGitHubIntegrationConfigs( + const baseUrl = readGithubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].apiBaseUrl; diff --git a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx index 2ae33c6b07..95d7745c62 100644 --- a/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx @@ -16,6 +16,7 @@ import React, { useState } from 'react'; import { Grid, Typography } from '@material-ui/core'; import FullscreenIcon from '@material-ui/icons/Fullscreen'; +import PeopleIcon from '@material-ui/icons/People'; import { Progress, InfoCard } from '@backstage/core-components'; @@ -25,8 +26,9 @@ import { Wrapper } from '../Wrapper'; import { PullRequestCard } from '../PullRequestCard'; import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; import { PRCardFormating } from '../../utils/types'; +import { shouldDisplayCard } from '../../utils/functions'; import { DraftPrIcon } from '../icons/DraftPr'; -import { useUserRepositories } from '../../hooks/useUserRepositories'; +import { useUserRepositoriesAndTeam } from '../../hooks/useUserRepositoriesAndTeam'; /** @public */ export interface EntityTeamPullRequestsCardProps { @@ -36,9 +38,20 @@ export interface EntityTeamPullRequestsCardProps { const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { const { pullRequestLimit } = props; const [infoCardFormat, setInfoCardFormat] = useState([]); - const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam( + const { + loading: loadingReposAndTeam, repositories, + teamMembers, + teamMembersOrganization, + } = useUserRepositoriesAndTeam(); + const { + loading: loadingPRs, + pullRequests, + refreshPullRequests, + } = usePullRequestsByTeam( + repositories, + teamMembers, + teamMembersOrganization, pullRequestLimit, ); @@ -48,6 +61,11 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { onClickOption={newFormats => setInfoCardFormat(newFormats)} value={infoCardFormat} options={[ + { + icon: , + value: 'team', + ariaLabel: 'Show PRs from your team', + }, { icon: , value: 'draft', @@ -56,7 +74,7 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { { icon: , value: 'fullscreen', - ariaLabel: 'Info card is set to fullscreen', + ariaLabel: 'Set card to fullscreen', }, ]} /> @@ -64,7 +82,7 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { ); const getContent = () => { - if (loading) { + if (loadingReposAndTeam || loadingPRs) { return ; } @@ -92,7 +110,14 @@ const EntityTeamPullRequestsCard = (props: EntityTeamPullRequestsCardProps) => { }, index, ) => - infoCardFormat.includes('draft') === isDraft && ( + shouldDisplayCard( + repository, + author, + repositories, + teamMembers, + infoCardFormat, + isDraft, + ) && ( { const { pullRequestLimit } = props; const [infoCardFormat, setInfoCardFormat] = useState([]); - const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam( + const { + loading: loadingReposAndTeam, repositories, + teamMembers, + teamMembersOrganization, + } = useUserRepositoriesAndTeam(); + const { + loading: loadingPRs, + pullRequests, + refreshPullRequests, + } = usePullRequestsByTeam( + repositories, + teamMembers, + teamMembersOrganization, pullRequestLimit, ); @@ -48,6 +61,11 @@ const EntityTeamPullRequestsContent = ( onClickOption={newFormats => setInfoCardFormat(newFormats)} value={infoCardFormat} options={[ + { + icon: , + value: 'team', + ariaLabel: 'Show PRs from your team', + }, { icon: , value: 'draft', @@ -59,7 +77,7 @@ const EntityTeamPullRequestsContent = ( ); const getContent = () => { - if (loading) { + if (loadingReposAndTeam || loadingPRs) { return ; } @@ -84,7 +102,14 @@ const EntityTeamPullRequestsContent = ( }, index, ) => - infoCardFormat.includes('draft') === isDraft && ( + shouldDisplayCard( + repository, + author, + repositories, + teamMembers, + infoCardFormat, + isDraft, + ) && ( ( width="16" data-view-component="true" > - - + + + + ); diff --git a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx index 2e6553f778..de36d806ff 100644 --- a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx +++ b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx @@ -13,48 +13,93 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import pLimit from 'p-limit'; import { useCallback, useEffect, useState } from 'react'; import { formatPRsByReviewDecision } from '../utils/functions'; import { PullRequests, PullRequestsColumn } from '../utils/types'; import { useGetPullRequestsFromRepository } from '../api/useGetPullRequestsFromRepository'; +import { useGetPullRequestsFromUser } from '../api/useGetPullRequestsFromUser'; import { useGetPullRequestDetails } from '../api/useGetPullRequestDetails'; export function usePullRequestsByTeam( repositories: string[], + members: string[], + organization?: string, pullRequestLimit?: number, ) { const [pullRequests, setPullRequests] = useState([]); const [loading, setLoading] = useState(true); - const getPullRequests = useGetPullRequestsFromRepository(); + const getPullRequestsFromRepository = useGetPullRequestsFromRepository(); + const getPullRequestsFromUser = useGetPullRequestsFromUser(); const getPullRequestDetails = useGetPullRequestDetails(); const getPRsPerRepository = useCallback( async (repository: string): Promise => { - const pullRequestsNumbers = await getPullRequests( + const concurrencyLimit = pLimit(5); + const pullRequestsNumbers = await getPullRequestsFromRepository( repository, pullRequestLimit, ); const pullRequestsWithDetails = await Promise.all( - pullRequestsNumbers.map(({ node }) => - getPullRequestDetails(repository, node.number), + pullRequestsNumbers.map(node => + concurrencyLimit(() => + getPullRequestDetails(repository, node.number), + ), ), ); return pullRequestsWithDetails; }, - [getPullRequests, getPullRequestDetails, pullRequestLimit], + [getPullRequestsFromRepository, getPullRequestDetails, pullRequestLimit], + ); + + const getPRsPerTeamMember = useCallback( + async ( + teamMember: string, + teamOrganization?: string, + ): Promise => { + const concurrencyLimit = pLimit(3); + const pullRequestsNumbers = await getPullRequestsFromUser( + teamMember, + teamOrganization, + pullRequestLimit, + ); + + const pullRequestsWithDetails = await Promise.all( + pullRequestsNumbers.map(node => + concurrencyLimit(() => + getPullRequestDetails( + `${node.repository.owner.login}/${node.repository.name}`, + node.number, + ), + ), + ), + ); + + return pullRequestsWithDetails; + }, + [getPullRequestsFromUser, getPullRequestDetails, pullRequestLimit], ); const getPRsFromTeam = useCallback( - async (teamRepositories: string[]): Promise => { + async ( + teamRepositories: string[], + teamMembers: string[], + teamOrganization?: string, + ): Promise => { const teamRepositoriesPromises = teamRepositories.map(repository => getPRsPerRepository(repository), ); - const teamPullRequests = await Promise.allSettled( - teamRepositoriesPromises, - ).then(promises => + const teamMembersPromises = teamMembers.map(teamMember => + getPRsPerTeamMember(teamMember, teamOrganization), + ); + + const teamPullRequests = await Promise.allSettled([ + ...teamRepositoriesPromises, + ...teamMembersPromises, + ]).then(promises => promises.reduce((acc, curr) => { if (curr.status === 'fulfilled') { return [...acc, ...curr.value]; @@ -63,18 +108,26 @@ export function usePullRequestsByTeam( }, [] as PullRequests), ); - return teamPullRequests; + const uniqueTeamPullRequests = teamPullRequests.filter( + (lhs, i) => teamPullRequests.findIndex(rhs => lhs.id === rhs.id) === i, + ); + + return uniqueTeamPullRequests; }, - [getPRsPerRepository], + [getPRsPerRepository, getPRsPerTeamMember], ); const getAllPullRequests = useCallback(async () => { setLoading(true); - const teamPullRequests = await getPRsFromTeam(repositories); + const teamPullRequests = await getPRsFromTeam( + repositories, + members, + organization, + ); setPullRequests(formatPRsByReviewDecision(teamPullRequests)); setLoading(false); - }, [getPRsFromTeam, repositories]); + }, [getPRsFromTeam, repositories, members, organization]); useEffect(() => { getAllPullRequests(); diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx deleted file mode 100644 index ed0d3580f0..0000000000 --- a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 { stringifyEntityRef } from '@backstage/catalog-model'; -import { useApi } from '@backstage/core-plugin-api'; -import { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; -import { useCallback, useEffect, useState } from 'react'; -import { getProjectNameFromEntity } from '../utils/functions'; - -export function useUserRepositories() { - const { entity: teamEntity } = useEntity(); - const catalogApi = useApi(catalogApiRef); - const [repositories, setRepositories] = useState([]); - - const getRepositoriesNames = useCallback(async () => { - const entitiesList = await catalogApi.getEntities({ - filter: { 'relations.ownedBy': stringifyEntityRef(teamEntity) }, - }); - - const entitiesNames: string[] = entitiesList.items.map(componentEntity => - getProjectNameFromEntity(componentEntity), - ); - - setRepositories([...new Set(entitiesNames)]); - }, [catalogApi, teamEntity]); - - useEffect(() => { - getRepositoriesNames(); - }, [getRepositoriesNames]); - - return { - repositories, - }; -} diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx new file mode 100644 index 0000000000..162dadcfbf --- /dev/null +++ b/plugins/github-pull-requests-board/src/hooks/useUserRepositoriesAndTeam.tsx @@ -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 { stringifyEntityRef } from '@backstage/catalog-model'; +import { useApi } from '@backstage/core-plugin-api'; +import { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; +import { useCallback, useEffect, useState } from 'react'; +import { + getProjectNameFromEntity, + getGithubOrganizationFromEntity, + getUserNameFromEntity, +} from '../utils/functions'; + +export function useUserRepositoriesAndTeam() { + const { entity: teamEntity } = useEntity(); + const catalogApi = useApi(catalogApiRef); + const [loading, setLoading] = useState(true); + const [teamData, setTeamData] = useState<{ + repositories: string[]; + teamMembers: string[]; + }>({ + repositories: [], + teamMembers: [], + }); + + const getTeamData = useCallback(async () => { + setLoading(true); + + // get team repositories and members + const entitiesList = await catalogApi.getEntities({ + filter: [ + { 'relations.ownedBy': stringifyEntityRef(teamEntity) }, + { 'relations.memberOf': stringifyEntityRef(teamEntity) }, + ], + }); + + const repositories = entitiesList.items.filter( + entity => entity.kind === 'Component', + ); + const repositoriesNames: string[] = repositories + .map(componentEntity => getProjectNameFromEntity(componentEntity) ?? '') + .filter(projectName => !!projectName); + + const teamMembers = entitiesList.items.filter( + entity => entity.kind === 'User', + ); + const teamMembersNames: string[] = teamMembers + .map(componentEntity => getUserNameFromEntity(componentEntity) ?? '') + .filter(userName => !!userName); + + setTeamData({ + repositories: [...new Set(repositoriesNames)], + teamMembers: [...new Set(teamMembersNames)], + }); + setLoading(false); + }, [catalogApi, teamEntity]); + + useEffect(() => { + getTeamData(); + }, [getTeamData]); + + return { + loading, + repositories: teamData.repositories, + teamMembers: teamData.teamMembers, + teamMembersOrganization: getGithubOrganizationFromEntity(teamEntity), + }; +} diff --git a/plugins/github-pull-requests-board/src/utils/functions.ts b/plugins/github-pull-requests-board/src/utils/functions.ts index 5644530689..227cad459c 100644 --- a/plugins/github-pull-requests-board/src/utils/functions.ts +++ b/plugins/github-pull-requests-board/src/utils/functions.ts @@ -21,13 +21,29 @@ import { ReviewDecision, PullRequestsColumn, Author, + PRCardFormating, + Repository, } from './types'; import { COLUMNS } from './constants'; const GITHUB_PULL_REQUESTS_ANNOTATION = 'github.com/project-slug'; +const GITHUB_USER_LOGIN_ANNOTATION = 'github.com/user-login'; -export const getProjectNameFromEntity = (entity: Entity): string => { - return entity?.metadata.annotations?.[GITHUB_PULL_REQUESTS_ANNOTATION] ?? ''; +export const getProjectNameFromEntity = ( + entity: Entity, +): string | undefined => { + return entity?.metadata.annotations?.[GITHUB_PULL_REQUESTS_ANNOTATION]; +}; + +export const getUserNameFromEntity = (entity: Entity): string | undefined => { + return entity?.metadata.annotations?.[GITHUB_USER_LOGIN_ANNOTATION]; +}; + +export const getGithubOrganizationFromEntity = (entity: Entity): string => { + return ( + entity?.metadata?.annotations?.['github.com/team-slug']?.split('/')?.[0] ?? + '' + ); }; export const getApprovedReviews = (reviews: Reviews = []): Reviews => { @@ -107,3 +123,32 @@ export const formatPRsByReviewDecision = ( { title: COLUMNS.APPROVED, content: reviewDecisions.APPROVED }, ]; }; + +export const shouldDisplayCard = ( + repository: Repository, + author: Author, + teamRepositories: string[], + teamMembers: string[], + infoCardFormat: PRCardFormating[], + isDraft: boolean, +) => { + // hide draft PRs unless "draft" filter is toggled + if (infoCardFormat.includes('draft') !== isDraft) { + return false; + } + + // when "team" filter is toggled on, only shows PR from team members + if (infoCardFormat.includes('team')) { + return teamMembers.includes(author.login); + } + + const fullRepoName = + `${repository.owner.login}/${repository.name}`.toLocaleLowerCase('en-US'); + + const repositories = teamRepositories.map(repo => + repo.toLocaleLowerCase('en-US'), + ); + + // when "team" filter is toggled off, only shows PR on team repos + return repositories.includes(fullRepoName); +}; diff --git a/plugins/github-pull-requests-board/src/utils/types.tsx b/plugins/github-pull-requests-board/src/utils/types.tsx index a1e2dbfda3..8a9f15b967 100644 --- a/plugins/github-pull-requests-board/src/utils/types.tsx +++ b/plugins/github-pull-requests-board/src/utils/types.tsx @@ -19,22 +19,33 @@ export type GraphQlPullRequest = { }; }; +export type Connection = { + nodes: T; + pageInfo: { + hasNextPage: boolean; + endCursor?: string; + }; +}; + export type GraphQlPullRequests = { repository: { - pullRequests: { - edges: T; - pageInfo: { - hasNextPage: boolean; - endCursor?: string; - }; - }; + pullRequests: Connection; + }; +}; + +export type GraphQlUserPullRequests = { + user: { + pullRequests: Connection; }; }; export type PullRequestsNumber = { - node: { - number: number; - }; + number: number; +}; + +export type PullRequestsNumberAndOwner = { + number: number; + repository: Repository; }; export type Review = { @@ -57,11 +68,16 @@ export type Author = { name: string; }; +export type Repository = { + name: string; + owner: { + login: string; + }; +}; + export type PullRequest = { id: string; - repository: { - name: string; - }; + repository: Repository; title: string; url: string; lastEditedAt: string; @@ -83,6 +99,8 @@ export type PullRequestsColumn = { content: PullRequests; }; -export type PRCardFormating = 'compacted' | 'fullscreen' | 'draft'; +export type PRCardFormating = 'compacted' | 'fullscreen' | 'draft' | 'team'; export type ReviewDecision = 'IN_PROGRESS' | 'APPROVED' | 'REVIEW_REQUIRED'; + +export type PullRequestsSourceType = 'TeamRepositories' | 'TeamMembers'; diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index b8512bcd2e..a578fe2077 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 + ## 0.3.28 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 02e8256eec..000f1c4b6c 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gitops-profiles", "description": "A Backstage plugin that helps you manage GitOps profiles", - "version": "0.3.28", + "version": "0.3.29-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx index 117159cc56..fbc2d06ce0 100644 --- a/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx +++ b/plugins/gitops-profiles/src/components/ClusterPage/ClusterPage.tsx @@ -15,7 +15,6 @@ */ import React, { useEffect, useState } from 'react'; -import { Link } from '@material-ui/core'; import { useParams } from 'react-router-dom'; import { gitOpsApiRef, Status } from '../../api'; import { transformRunStatus } from '../ProfileCatalog'; @@ -27,6 +26,7 @@ import { Table, Progress, HeaderLabel, + Link, } from '@backstage/core-components'; import { useApi, githubAuthApiRef } from '@backstage/core-plugin-api'; @@ -94,12 +94,7 @@ const ClusterPage = () => { data={transformRunStatus(runStatus)} columns={columns} /> - + Details diff --git a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx index caab33217d..e5cef084b5 100644 --- a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx +++ b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.tsx @@ -15,7 +15,7 @@ */ import React, { useEffect, useState } from 'react'; -import { TextField, List, ListItem, Link } from '@material-ui/core'; +import { TextField, List, ListItem } from '@material-ui/core'; import ClusterTemplateCardList from '../ClusterTemplateCardList'; import ProfileCardList from '../ProfileCardList'; @@ -32,6 +32,7 @@ import { SimpleStepper, SimpleStepperStep, InfoCard, + Link, Progress, Table, StatusWarning, @@ -340,9 +341,7 @@ const ProfileCatalog = () => { /> Details diff --git a/plugins/gocd/CHANGELOG.md b/plugins/gocd/CHANGELOG.md index 3d140260f4..4409bc5861 100644 --- a/plugins/gocd/CHANGELOG.md +++ b/plugins/gocd/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 + ## 0.1.16 ### Patch Changes diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index d455c6bb98..e8ba4b9374 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gocd", "description": "A Backstage plugin that integrates towards GoCD", - "version": "0.1.16", + "version": "0.1.17-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx index 7f16f40a6a..ebc3502901 100644 --- a/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx +++ b/plugins/gocd/src/components/GoCdBuildsComponent/GoCdBuildsComponent.test.tsx @@ -36,7 +36,9 @@ describe('GoCdArtifactsComponent', () => { baseUrl: 'gocd.baseurl.com', }, }); - const entityValue = { entity: { metadata: {} } as Entity }; + const entityValue = { + entity: { metadata: { name: 'mock' }, kind: 'MockKind' } as Entity, + }; const renderComponent = () => renderWithEffects( diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index c4cbfa1646..912c4e972b 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 0.2.42 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 245155fc13..6f10a179a7 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.42", + "version": "0.2.43-next.0", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/plugins/graphql-backend/CHANGELOG.md b/plugins/graphql-backend/CHANGELOG.md index 427ac6206d..2fa54c680f 100644 --- a/plugins/graphql-backend/CHANGELOG.md +++ b/plugins/graphql-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 + ## 0.1.27 ### Patch Changes diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json index 0f44b3be6b..70c8fb0c6f 100644 --- a/plugins/graphql-backend/package.json +++ b/plugins/graphql-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphql-backend", "description": "An experimental Backstage backend plugin for GraphQL", - "version": "0.1.27", + "version": "0.1.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index e64ac0be7e..b9c43b0af8 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 + ## 0.4.26 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index 516a91cd74..8c267f4d25 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home", "description": "A Backstage plugin that helps you build a home page", - "version": "0.4.26", + "version": "0.4.27-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index dc2a3c1959..d1810178cc 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,21 @@ # @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 + ## 0.1.36 ### Patch Changes diff --git a/plugins/ilert/api-report.md b/plugins/ilert/api-report.md index 96576a8358..6cc0c12a8f 100644 --- a/plugins/ilert/api-report.md +++ b/plugins/ilert/api-report.md @@ -16,11 +16,96 @@ import { RouteRef } from '@backstage/core-plugin-api'; // @public (undocumented) export const ACCEPTED = 'ACCEPTED'; +// @public (undocumented) +export interface Alert { + // (undocumented) + alertKey: string; + // (undocumented) + alertSource: AlertSource | null; + // (undocumented) + assignedTo: User | null; + // (undocumented) + commentPublishToSubscribers: boolean; + // (undocumented) + commentText: string; + // (undocumented) + details: string; + // (undocumented) + id: number; + // (undocumented) + images: Image_2[]; + // (undocumented) + links: Link[]; + // (undocumented) + logEntries: LogEntry[]; + // (undocumented) + priority: AlertPriority; + // (undocumented) + reportTime: string; + // (undocumented) + resolvedOn: string; + // (undocumented) + responders: Responder[]; + // (undocumented) + status: AlertStatus; + // (undocumented) + subscribers: Subscriber[]; + // (undocumented) + summary: string; +} + +// @public (undocumented) +export interface AlertAction { + // (undocumented) + extensionId?: string; + // (undocumented) + history?: AlertActionHistory[]; + // (undocumented) + name: string; + // (undocumented) + type: string; + // (undocumented) + webhookId: string; +} + +// @public (undocumented) +export interface AlertActionHistory { + // (undocumented) + actor: User; + // (undocumented) + alertId: number; + // (undocumented) + id: string; + // (undocumented) + success: boolean; + // (undocumented) + webhookId: string; +} + +// @public (undocumented) +export type AlertPriority = 'HIGH' | 'LOW'; + +// @public (undocumented) +export interface AlertResponder { + // (undocumented) + disabled: boolean; + // (undocumented) + group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; + // (undocumented) + id: number; + // (undocumented) + name: string; +} + // @public (undocumented) export interface AlertSource { // (undocumented) active?: boolean; // (undocumented) + alertCreation?: AlertSourceAlertCreation; + // (undocumented) + alertPriorityRule?: AlertSourceAlertPriorityRule; + // (undocumented) autoResolutionTimeout?: string; // (undocumented) autotaskMetadata?: AlertSourceAutotaskMetadata; @@ -45,10 +130,6 @@ export interface AlertSource { // (undocumented) id: number; // (undocumented) - incidentCreation?: AlertSourceIncidentCreation; - // (undocumented) - incidentPriorityRule?: AlertSourceIncidentPriorityRule; - // (undocumented) integrationKey?: string; // (undocumented) integrationType: AlertSourceIntegrationType; @@ -66,6 +147,21 @@ export interface AlertSource { teams: TeamShort[]; } +// @public (undocumented) +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 (undocumented) +export type AlertSourceAlertPriorityRule = + | 'HIGH' + | 'LOW' + | 'HIGH_DURING_SUPPORT_HOURS' + | 'LOW_DURING_SUPPORT_HOURS'; + // @public (undocumented) export interface AlertSourceAutotaskMetadata { // (undocumented) @@ -109,21 +205,6 @@ export interface AlertSourceHeartbeat { summary: string; } -// @public (undocumented) -export type AlertSourceIncidentCreation = - | 'ONE_INCIDENT_PER_EMAIL' - | 'ONE_INCIDENT_PER_EMAIL_SUBJECT' - | 'ONE_PENDING_INCIDENT_ALLOWED' - | 'ONE_OPEN_INCIDENT_ALLOWED' - | 'OPEN_RESOLVE_ON_EXTRACTION'; - -// @public (undocumented) -export type AlertSourceIncidentPriorityRule = - | 'HIGH' - | 'LOW' - | 'HIGH_DURING_SUPPORT_HOURS' - | 'LOW_DURING_SUPPORT_HOURS'; - // @public (undocumented) export type AlertSourceIntegrationType = | 'NAGIOS' @@ -192,7 +273,7 @@ export interface AlertSourceSupportDay { // @public (undocumented) export interface AlertSourceSupportHours { // (undocumented) - autoRaiseIncidents: boolean; + autoRaiseAlerts: boolean; // (undocumented) supportDays: { MONDAY: AlertSourceSupportDay; @@ -214,6 +295,12 @@ export type AlertSourceTimeZone = | 'America/Los_Angeles' | 'Asia/Istanbul'; +// @public (undocumented) +export type AlertStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; + +// @public (undocumented) +export const DEGRADED = 'DEGRADED'; + // @public (undocumented) export const EntityILertCard: () => JSX.Element; @@ -255,72 +342,85 @@ export type EventRequest = { }; // @public (undocumented) -export type GetIncidentsCountOpts = { - states?: IncidentStatus[]; +export type GetAlertsCountOpts = { + states?: AlertStatus[]; }; // @public (undocumented) -export type GetIncidentsOpts = { +export type GetAlertsOpts = { maxResults?: number; startIndex?: number; - states?: IncidentStatus[]; + states?: AlertStatus[]; alertSources?: number[]; }; +// @public (undocumented) +export type GetServicesOpts = { + maxResults?: number; + startIndex?: number; +}; + +// @public (undocumented) +export type GetStatusPagesOpts = { + maxResults?: number; + startIndex?: number; +}; + // @public (undocumented) export interface ILertApi { // (undocumented) - acceptIncident(incident: Incident, userName: string): Promise; + acceptAlert(alert: Alert, userName: string): Promise; // (undocumented) addImmediateMaintenance( alertSourceId: number, minutes: number, ): Promise; // (undocumented) - assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise; + assignAlert(alert: Alert, responder: AlertResponder): Promise; // (undocumented) - createIncident(eventRequest: EventRequest): Promise; + createAlert(eventRequest: EventRequest): Promise; // (undocumented) disableAlertSource(alertSource: AlertSource): Promise; // (undocumented) enableAlertSource(alertSource: AlertSource): Promise; // (undocumented) + fetchAlert(id: number): Promise; + // (undocumented) + fetchAlertActions(alert: Alert): Promise; + // (undocumented) + fetchAlertResponders(alert: Alert): Promise; + // (undocumented) + fetchAlerts(opts?: GetAlertsOpts): Promise; + // (undocumented) + fetchAlertsCount(opts?: GetAlertsCountOpts): Promise; + // (undocumented) fetchAlertSource(idOrIntegrationKey: number | string): Promise; // (undocumented) fetchAlertSourceOnCalls(alertSource: AlertSource): Promise; // (undocumented) fetchAlertSources(): Promise; // (undocumented) - fetchIncident(id: number): Promise; - // (undocumented) - fetchIncidentActions(incident: Incident): Promise; - // (undocumented) - fetchIncidentResponders(incident: Incident): Promise; - // (undocumented) - fetchIncidents(opts?: GetIncidentsOpts): Promise; - // (undocumented) - fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; - // (undocumented) fetchOnCallSchedules(): Promise; // (undocumented) - fetchUptimeMonitor(id: number): Promise; + fetchServices(opts?: GetServicesOpts): Promise; // (undocumented) - fetchUptimeMonitors(): Promise; + fetchStatusPages(opts?: GetStatusPagesOpts): Promise; // (undocumented) fetchUsers(): Promise; // (undocumented) + getAlertDetailsURL(alert: Alert): string; + // (undocumented) getAlertSourceDetailsURL(alertSource: AlertSource | null): string; // (undocumented) getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; // (undocumented) - getIncidentDetailsURL(incident: Incident): string; - // (undocumented) getScheduleDetailsURL(schedule: Schedule): string; // (undocumented) - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; + getServiceDetailsURL(service: Service): string; + // (undocumented) + getStatusPageDetailsURL(statusPage: StatusPage): string; + // (undocumented) + getStatusPageURL(statusPage: StatusPage): string; // (undocumented) getUserInitials(user: User | null): string; // (undocumented) @@ -333,16 +433,9 @@ export interface ILertApi { end: string, ): Promise; // (undocumented) - pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; + resolveAlert(alert: Alert, userName: string): Promise; // (undocumented) - resolveIncident(incident: Incident, userName: string): Promise; - // (undocumented) - resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - // (undocumented) - triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise; + triggerAlertAction(alert: Alert, action: AlertAction): Promise; } // @public (undocumented) @@ -359,45 +452,42 @@ export class ILertClient implements ILertApi { proxyPath: string; }); // (undocumented) - acceptIncident(incident: Incident, userName: string): Promise; + acceptAlert(alert: Alert, userName: string): Promise; // (undocumented) addImmediateMaintenance( alertSourceId: number, minutes: number, ): Promise; // (undocumented) - assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise; + assignAlert(alert: Alert, responder: AlertResponder): Promise; // (undocumented) - createIncident(eventRequest: EventRequest): Promise; + createAlert(eventRequest: EventRequest): Promise; // (undocumented) disableAlertSource(alertSource: AlertSource): Promise; // (undocumented) enableAlertSource(alertSource: AlertSource): Promise; // (undocumented) + fetchAlert(id: number): Promise; + // (undocumented) + fetchAlertActions(alert: Alert): Promise; + // (undocumented) + fetchAlertResponders(alert: Alert): Promise; + // (undocumented) + fetchAlerts(opts?: GetAlertsOpts): Promise; + // (undocumented) + fetchAlertsCount(opts?: GetAlertsCountOpts): Promise; + // (undocumented) fetchAlertSource(idOrIntegrationKey: number | string): Promise; // (undocumented) fetchAlertSourceOnCalls(alertSource: AlertSource): Promise; // (undocumented) fetchAlertSources(): Promise; // (undocumented) - fetchIncident(id: number): Promise; - // (undocumented) - fetchIncidentActions(incident: Incident): Promise; - // (undocumented) - fetchIncidentResponders(incident: Incident): Promise; - // (undocumented) - fetchIncidents(opts?: GetIncidentsOpts): Promise; - // (undocumented) - fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; - // (undocumented) fetchOnCallSchedules(): Promise; // (undocumented) - fetchUptimeMonitor(id: number): Promise; + fetchServices(opts?: GetServicesOpts): Promise; // (undocumented) - fetchUptimeMonitors(): Promise; + fetchStatusPages(opts?: GetStatusPagesOpts): Promise; // (undocumented) fetchUsers(): Promise; // (undocumented) @@ -406,15 +496,19 @@ export class ILertClient implements ILertApi { discoveryApi: DiscoveryApi, ): ILertClient; // (undocumented) + getAlertDetailsURL(alert: Alert): string; + // (undocumented) getAlertSourceDetailsURL(alertSource: AlertSource | null): string; // (undocumented) getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; // (undocumented) - getIncidentDetailsURL(incident: Incident): string; - // (undocumented) getScheduleDetailsURL(schedule: Schedule): string; // (undocumented) - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; + getServiceDetailsURL(service: Service): string; + // (undocumented) + getStatusPageDetailsURL(statusPage: StatusPage): string; + // (undocumented) + getStatusPageURL(statusPage: StatusPage): string; // (undocumented) getUserInitials(user: User | null): string; // (undocumented) @@ -427,16 +521,9 @@ export class ILertClient implements ILertApi { end: string, ): Promise; // (undocumented) - pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; + resolveAlert(alert: Alert, userName: string): Promise; // (undocumented) - resolveIncident(incident: Incident, userName: string): Promise; - // (undocumented) - resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - // (undocumented) - triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise; + triggerAlertAction(alert: Alert, action: AlertAction): Promise; } // @public (undocumented) @@ -470,88 +557,6 @@ interface Image_2 { } export { Image_2 as Image }; -// @public (undocumented) -export interface Incident { - // (undocumented) - alertSource: AlertSource | null; - // (undocumented) - assignedTo: User | null; - // (undocumented) - commentPublishToSubscribers: boolean; - // (undocumented) - commentText: string; - // (undocumented) - details: string; - // (undocumented) - id: number; - // (undocumented) - images: Image_2[]; - // (undocumented) - incidentKey: string; - // (undocumented) - links: Link[]; - // (undocumented) - logEntries: LogEntry[]; - // (undocumented) - priority: IncidentPriority; - // (undocumented) - reportTime: string; - // (undocumented) - resolvedOn: string; - // (undocumented) - status: IncidentStatus; - // (undocumented) - subscribers: Subscriber[]; - // (undocumented) - summary: string; -} - -// @public (undocumented) -export interface IncidentAction { - // (undocumented) - extensionId?: string; - // (undocumented) - history?: IncidentActionHistory[]; - // (undocumented) - name: string; - // (undocumented) - type: string; - // (undocumented) - webhookId: string; -} - -// @public (undocumented) -export interface IncidentActionHistory { - // (undocumented) - actor: User; - // (undocumented) - id: string; - // (undocumented) - incidentId: number; - // (undocumented) - success: boolean; - // (undocumented) - webhookId: string; -} - -// @public (undocumented) -export type IncidentPriority = 'HIGH' | 'LOW'; - -// @public (undocumented) -export interface IncidentResponder { - // (undocumented) - disabled: boolean; - // (undocumented) - group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; - // (undocumented) - id: number; - // (undocumented) - name: string; -} - -// @public (undocumented) -export type IncidentStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; - // @public (undocumented) const isPluginApplicableToEntity: (entity: Entity) => boolean; export { isPluginApplicableToEntity as isILertAvailable }; @@ -570,6 +575,8 @@ export interface Link { // @public (undocumented) export interface LogEntry { + // (undocumented) + alertId?: number; // (undocumented) filterTypes?: string[]; // (undocumented) @@ -579,8 +586,6 @@ export interface LogEntry { // (undocumented) id: number; // (undocumented) - incidentId?: number; - // (undocumented) logEntryType: string; // (undocumented) text: string; @@ -588,6 +593,9 @@ export interface LogEntry { timestamp: string; } +// @public (undocumented) +export const MAJOR_OUTAGE = 'MAJOR_OUTAGE'; + // @public (undocumented) export interface OnCall { // (undocumented) @@ -604,6 +612,12 @@ export interface OnCall { user: User; } +// @public (undocumented) +export const OPERATIONAL = 'OPERATIONAL'; + +// @public (undocumented) +export const PARTIAL_OUTAGE = 'PARTIAL_OUTAGE'; + // @public (undocumented) export const PENDING = 'PENDING'; @@ -615,9 +629,25 @@ export interface Phone { regionCode: string; } +// @public (undocumented) +export const PRIVATE = 'PRIVATE'; + +// @public (undocumented) +export const PUBLIC = 'PUBLIC'; + // @public (undocumented) export const RESOLVED = 'RESOLVED'; +// @public (undocumented) +export interface Responder { + // (undocumented) + acceptedAt?: string; + // (undocumented) + status: string; + // (undocumented) + user: User; +} + // @public (undocumented) export const Router: () => JSX.Element; @@ -643,6 +673,26 @@ export interface Schedule { timezone: string; } +// @public (undocumented) +export interface Service { + // (undocumented) + id: number; + // (undocumented) + name: string; + // (undocumented) + status: ServiceStatus; + // (undocumented) + uptime: Uptime; +} + +// @public (undocumented) +export type ServiceStatus = + | typeof OPERATIONAL + | typeof UNDER_MAINTENANCE + | typeof DEGRADED + | typeof PARTIAL_OUTAGE + | typeof MAJOR_OUTAGE; + // @public (undocumented) export interface Shift { // (undocumented) @@ -653,6 +703,25 @@ export interface Shift { user: User; } +// @public (undocumented) +export interface StatusPage { + // (undocumented) + domain: string; + // (undocumented) + id: number; + // (undocumented) + name: string; + // (undocumented) + status: ServiceStatus; + // (undocumented) + subdomain: string; + // (undocumented) + visibility: StatusPageVisibility; +} + +// @public (undocumented) +export type StatusPageVisibility = typeof PRIVATE | typeof PUBLIC; + // @public (undocumented) export interface Subscriber { // (undocumented) @@ -689,47 +758,18 @@ export interface TeamShort { } // @public (undocumented) -export interface UptimeMonitor { +export const UNDER_MAINTENANCE = 'UNDER_MAINTENANCE'; + +// @public (undocumented) +export interface Uptime { // (undocumented) - checkParams: UptimeMonitorCheckParams; - // (undocumented) - checkType: 'http' | 'tcp' | 'udp' | 'ping'; - // (undocumented) - createIncidentAfterFailedChecks: number; - // (undocumented) - embedUrl: string; - // (undocumented) - escalationPolicy: EscalationPolicy; - // (undocumented) - id: number; - // (undocumented) - intervalSec: number; - // (undocumented) - lastStatusChange: string; - // (undocumented) - name: string; - // (undocumented) - paused: boolean; - // (undocumented) - region: 'EU' | 'US'; - // (undocumented) - shareUrl: string; - // (undocumented) - status: string; - // (undocumented) - teams: TeamShort[]; - // (undocumented) - timeoutMs: number; + uptimePercentage: UptimePercentage; } // @public (undocumented) -export interface UptimeMonitorCheckParams { +export interface UptimePercentage { // (undocumented) - host?: string; - // (undocumented) - port?: number; - // (undocumented) - url?: string; + p90: number; } // @public (undocumented) diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index ffe74c2572..5399a19bd7 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-ilert", "description": "A Backstage plugin that integrates towards iLert", - "version": "0.1.36", + "version": "0.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/ilert/src/api/client.ts b/plugins/ilert/src/api/client.ts index 7ab9a6d08c..9eb60c8c6a 100644 --- a/plugins/ilert/src/api/client.ts +++ b/plugins/ilert/src/api/client.ts @@ -13,30 +13,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { AuthenticationError, ResponseError } from '@backstage/errors'; -import { - AlertSource, - EscalationPolicy, - Incident, - IncidentAction, - IncidentResponder, - OnCall, - Schedule, - UptimeMonitor, - User, -} from '../types'; -import { - ILertApi, - GetIncidentsOpts, - GetIncidentsCountOpts, - EventRequest, -} from './types'; -import { DateTime as dt } from 'luxon'; import { ConfigApi, createApiRef, DiscoveryApi, } from '@backstage/core-plugin-api'; +import { AuthenticationError, ResponseError } from '@backstage/errors'; +import { DateTime as dt } from 'luxon'; +import { + Alert, + AlertAction, + AlertResponder, + AlertSource, + EscalationPolicy, + OnCall, + Schedule, + Service, + StatusPage, + User, +} from '../types'; +import { + EventRequest, + GetAlertsCountOpts, + GetAlertsOpts, + GetServicesOpts, + GetStatusPagesOpts, + ILertApi, +} from './types'; /** @public */ export const ilertApiRef = createApiRef({ @@ -102,7 +105,7 @@ export class ILertClient implements ILertApi { return await response.json(); } - async fetchIncidents(opts?: GetIncidentsOpts): Promise { + async fetchAlerts(opts?: GetAlertsOpts): Promise { const init = { headers: JSON_HEADERS, }; @@ -126,15 +129,11 @@ export class ILertClient implements ILertApi { query.append('state', state); }); } - const response = await this.fetch( - `/api/v1/incidents?${query.toString()}`, - init, - ); - + const response = await this.fetch(`/api/alerts?${query.toString()}`, init); return response; } - async fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise { + async fetchAlertsCount(opts?: GetAlertsCountOpts): Promise { const init = { headers: JSON_HEADERS, }; @@ -145,96 +144,85 @@ export class ILertClient implements ILertApi { }); } const response = await this.fetch( - `/api/v1/incidents/count?${query.toString()}`, + `/api/alerts/count?${query.toString()}`, init, ); return response && response.count ? response.count : 0; } - async fetchIncident(id: number): Promise { + async fetchAlert(id: number): Promise { const init = { headers: JSON_HEADERS, }; const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent(id)}`, + `/api/alerts/${encodeURIComponent(id)}`, init, ); return response; } - async fetchIncidentResponders( - incident: Incident, - ): Promise { + async fetchAlertResponders(alert: Alert): Promise { const init = { headers: JSON_HEADERS, }; const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent(incident.id)}/responders`, + `/api/alerts/${encodeURIComponent(alert.id)}/responders`, init, ); return response; } - async fetchIncidentActions(incident: Incident): Promise { + async fetchAlertActions(alert: Alert): Promise { const init = { headers: JSON_HEADERS, }; const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent(incident.id)}/actions`, + `/api/alerts/${encodeURIComponent(alert.id)}/actions`, init, ); return response; } - async acceptIncident( - incident: Incident, - userName: string, - ): Promise { + async acceptAlert(alert: Alert, userName: string): Promise { const init = { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({ - apiKey: incident.alertSource?.integrationKey || '', - incidentKey: incident.incidentKey, + apiKey: alert.alertSource?.integrationKey || '', + alertKey: alert.alertKey, summary: `from ${userName} via Backstage plugin`, eventType: 'ACCEPT', }), }; - await this.fetch('/api/v1/events', init); - return this.fetchIncident(incident.id); + await this.fetch('/api/events', init); + return this.fetchAlert(alert.id); } - async resolveIncident( - incident: Incident, - userName: string, - ): Promise { + async resolveAlert(alert: Alert, userName: string): Promise { const init = { method: 'POST', headers: JSON_HEADERS, body: JSON.stringify({ - apiKey: incident.alertSource?.integrationKey || '', - incidentKey: incident.incidentKey, + apiKey: alert.alertSource?.integrationKey || '', + alertKey: alert.alertKey, summary: `from ${userName} via Backstage plugin`, eventType: 'RESOLVE', }), }; - await this.fetch('/api/v1/events', init); - return this.fetchIncident(incident.id); + await this.fetch('/api/events', init); + return this.fetchAlert(alert.id); } - async assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise { + async assignAlert(alert: Alert, responder: AlertResponder): Promise { const init = { method: 'PUT', headers: JSON_HEADERS, @@ -254,19 +242,14 @@ export class ILertClient implements ILertApi { } const response = await this.fetch( - `/api/v1/incidents/${encodeURIComponent( - incident.id, - )}/assign?${query.toString()}`, + `/api/alerts/${encodeURIComponent(alert.id)}/assign?${query.toString()}`, init, ); return response; } - async triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise { + async triggerAlertAction(alert: Alert, action: AlertAction): Promise { const init = { method: 'POST', headers: JSON_HEADERS, @@ -279,12 +262,12 @@ export class ILertClient implements ILertApi { }; await this.fetch( - `/api/v1/incidents/${encodeURIComponent(incident.id)}/actions`, + `/api/alerts/${encodeURIComponent(alert.id)}/actions`, init, ); } - async createIncident(eventRequest: EventRequest): Promise { + async createAlert(eventRequest: EventRequest): Promise { const init = { method: 'POST', headers: JSON_HEADERS, @@ -305,64 +288,7 @@ export class ILertClient implements ILertApi { }), }; - const response = await this.fetch('/api/v1/events', init); - return response; - } - - async fetchUptimeMonitors(): Promise { - const init = { - headers: JSON_HEADERS, - }; - - const response = await this.fetch('/api/v1/uptime-monitors', init); - - return response; - } - - async fetchUptimeMonitor(id: number): Promise { - const init = { - headers: JSON_HEADERS, - }; - - const response: UptimeMonitor = await this.fetch( - `/api/v1/uptime-monitors/${encodeURIComponent(id)}`, - init, - ); - - return response; - } - - async pauseUptimeMonitor( - uptimeMonitor: UptimeMonitor, - ): Promise { - const init = { - method: 'PUT', - headers: JSON_HEADERS, - body: JSON.stringify({ ...uptimeMonitor, paused: true }), - }; - - const response = await this.fetch( - `/api/v1/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, - init, - ); - - return response; - } - - async resumeUptimeMonitor( - uptimeMonitor: UptimeMonitor, - ): Promise { - const init = { - method: 'PUT', - headers: JSON_HEADERS, - body: JSON.stringify({ ...uptimeMonitor, paused: false }), - }; - - const response = await this.fetch( - `/api/v1/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, - init, - ); - + const response = await this.fetch('/api/events', init); return response; } @@ -371,7 +297,7 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/alert-sources', init); + const response = await this.fetch('/api/alert-sources', init); return response; } @@ -384,7 +310,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/alert-sources/${encodeURIComponent(idOrIntegrationKey)}`, + `/api/alert-sources/${encodeURIComponent(idOrIntegrationKey)}`, init, ); @@ -397,7 +323,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/on-calls?policies=${encodeURIComponent( + `/api/on-calls?policies=${encodeURIComponent( alertSource.escalationPolicy.id, )}&expand=user&expand=escalationPolicy&timezone=${dt.local().zoneName}`, init, @@ -414,7 +340,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/alert-sources/${encodeURIComponent(alertSource.id)}`, + `/api/alert-sources/${encodeURIComponent(alertSource.id)}`, init, ); @@ -429,7 +355,7 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/alert-sources/${encodeURIComponent(alertSource.id)}`, + `/api/alert-sources/${encodeURIComponent(alertSource.id)}`, init, ); @@ -453,7 +379,7 @@ export class ILertClient implements ILertApi { }), }; - const response = await this.fetch('/api/v1/maintenance-windows', init); + const response = await this.fetch('/api/maintenance-windows', init); return response; } @@ -463,7 +389,10 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/schedules', init); + const response = await this.fetch( + '/api/schedules?include=currentShift&include=nextShift', + init, + ); return response; } @@ -473,7 +402,7 @@ export class ILertClient implements ILertApi { headers: JSON_HEADERS, }; - const response = await this.fetch('/api/v1/users', init); + const response = await this.fetch('/api/users', init); return response; } @@ -491,17 +420,57 @@ export class ILertClient implements ILertApi { }; const response = await this.fetch( - `/api/v1/schedules/${encodeURIComponent(scheduleId)}/overrides`, + `/api/schedules/${encodeURIComponent(scheduleId)}/overrides`, init, ); return response; } - getIncidentDetailsURL(incident: Incident): string { - return `${this.baseUrl}/incident/view.jsf?id=${encodeURIComponent( - incident.id, - )}`; + async fetchServices(opts?: GetServicesOpts): Promise { + const init = { + headers: JSON_HEADERS, + }; + const query = new URLSearchParams(); + if (opts?.maxResults !== undefined) { + query.append('max-results', String(opts.maxResults)); + } + if (opts?.startIndex !== undefined) { + query.append('start-index', String(opts.startIndex)); + } + + query.append('include', 'uptime'); + + const response = await this.fetch( + `/api/services?${query.toString()}`, + init, + ); + return response; + } + + async fetchStatusPages(opts?: GetStatusPagesOpts): Promise { + const init = { + headers: JSON_HEADERS, + }; + const query = new URLSearchParams(); + if (opts?.maxResults !== undefined) { + query.append('max-results', String(opts.maxResults)); + } + if (opts?.startIndex !== undefined) { + query.append('start-index', String(opts.startIndex)); + } + + query.append('include', 'subscribed'); + + const response = await this.fetch( + `/api/status-pages?${query.toString()}`, + init, + ); + return response; + } + + getAlertDetailsURL(alert: Alert): string { + return `${this.baseUrl}/alert/view.jsf?id=${encodeURIComponent(alert.id)}`; } getAlertSourceDetailsURL(alertSource: AlertSource | null): string { @@ -519,18 +488,28 @@ export class ILertClient implements ILertApi { )}`; } - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string { - return `${this.baseUrl}/uptime/view.jsf?id=${encodeURIComponent( - uptimeMonitor.id, - )}`; - } - getScheduleDetailsURL(schedule: Schedule): string { return `${this.baseUrl}/schedule/view.jsf?id=${encodeURIComponent( schedule.id, )}`; } + getServiceDetailsURL(service: Service): string { + return `${this.baseUrl}/service/view.jsf?id=${encodeURIComponent( + service.id, + )}`; + } + + getStatusPageDetailsURL(statusPage: StatusPage): string { + return `${this.baseUrl}/status-page/view.jsf?id=${encodeURIComponent( + statusPage.id, + )}`; + } + + getStatusPageURL(statusPage: StatusPage): string { + return statusPage.domain ? statusPage.domain : statusPage.subdomain; + } + getUserPhoneNumber(user: User | null) { return user?.mobile?.number || user?.landline?.number || ''; } @@ -542,7 +521,7 @@ export class ILertClient implements ILertApi { if (!user.firstName && !user.lastName) { return user.username; } - return `${user.firstName} ${user.lastName} (${user.username})`; + return `${user.firstName} ${user.lastName}`; } private async apiUrl() { diff --git a/plugins/ilert/src/api/index.ts b/plugins/ilert/src/api/index.ts index e1163090ec..30120225a4 100644 --- a/plugins/ilert/src/api/index.ts +++ b/plugins/ilert/src/api/index.ts @@ -14,11 +14,13 @@ * limitations under the License. */ -export { ILertClient, ilertApiRef } from './client'; +export { ilertApiRef, ILertClient } from './client'; export type { + EventRequest as EventRequest, + GetAlertsCountOpts, + GetAlertsOpts, + GetServicesOpts, + GetStatusPagesOpts, ILertApi, - EventRequest, - GetIncidentsCountOpts, - GetIncidentsOpts, TableState, } from './types'; diff --git a/plugins/ilert/src/api/types.ts b/plugins/ilert/src/api/types.ts index 2e9ea30f2f..959d7975ac 100644 --- a/plugins/ilert/src/api/types.ts +++ b/plugins/ilert/src/api/types.ts @@ -15,16 +15,17 @@ */ import { + Alert, + AlertAction, + AlertResponder, AlertSource, - Incident, - User, - IncidentStatus, - UptimeMonitor, + AlertStatus, EscalationPolicy, - Schedule, - IncidentResponder, - IncidentAction, OnCall, + Schedule, + Service, + StatusPage, + User, } from '../types'; /** @public */ @@ -34,16 +35,28 @@ export type TableState = { }; /** @public */ -export type GetIncidentsOpts = { +export type GetAlertsOpts = { maxResults?: number; startIndex?: number; - states?: IncidentStatus[]; + states?: AlertStatus[]; alertSources?: number[]; }; /** @public */ -export type GetIncidentsCountOpts = { - states?: IncidentStatus[]; +export type GetAlertsCountOpts = { + states?: AlertStatus[]; +}; + +/** @public */ +export type GetServicesOpts = { + maxResults?: number; + startIndex?: number; +}; + +/** @public */ +export type GetStatusPagesOpts = { + maxResults?: number; + startIndex?: number; }; /** @public */ @@ -57,27 +70,16 @@ export type EventRequest = { /** @public */ export interface ILertApi { - fetchIncidents(opts?: GetIncidentsOpts): Promise; - fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; - fetchIncident(id: number): Promise; - fetchIncidentResponders(incident: Incident): Promise; - fetchIncidentActions(incident: Incident): Promise; - acceptIncident(incident: Incident, userName: string): Promise; - resolveIncident(incident: Incident, userName: string): Promise; - assignIncident( - incident: Incident, - responder: IncidentResponder, - ): Promise; - createIncident(eventRequest: EventRequest): Promise; - triggerIncidentAction( - incident: Incident, - action: IncidentAction, - ): Promise; - - fetchUptimeMonitors(): Promise; - pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; - fetchUptimeMonitor(id: number): Promise; + fetchAlerts(opts?: GetAlertsOpts): Promise; + fetchAlertsCount(opts?: GetAlertsCountOpts): Promise; + fetchAlert(id: number): Promise; + fetchAlertResponders(alert: Alert): Promise; + fetchAlertActions(alert: Alert): Promise; + acceptAlert(alert: Alert, userName: string): Promise; + resolveAlert(alert: Alert, userName: string): Promise; + assignAlert(alert: Alert, responder: AlertResponder): Promise; + createAlert(eventRequest: EventRequest): Promise; + triggerAlertAction(alert: Alert, action: AlertAction): Promise; fetchAlertSources(): Promise; fetchAlertSource(idOrIntegrationKey: number | string): Promise; @@ -100,11 +102,17 @@ export interface ILertApi { end: string, ): Promise; - getIncidentDetailsURL(incident: Incident): string; + fetchServices(opts?: GetServicesOpts): Promise; + + fetchStatusPages(opts?: GetStatusPagesOpts): Promise; + + getAlertDetailsURL(alert: Alert): string; getAlertSourceDetailsURL(alertSource: AlertSource | null): string; getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; - getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; getScheduleDetailsURL(schedule: Schedule): string; + getServiceDetailsURL(service: Service): string; + getStatusPageDetailsURL(statusPage: StatusPage): string; + getStatusPageURL(statusPage: StatusPage): string; getUserPhoneNumber(user: User | null): string; getUserInitials(user: User | null): string; } diff --git a/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx b/plugins/ilert/src/components/Alert/AlertActionsMenu.tsx similarity index 73% rename from plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx rename to plugins/ilert/src/components/Alert/AlertActionsMenu.tsx index 909e4dc362..ff20e39b24 100644 --- a/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx +++ b/plugins/ilert/src/components/Alert/AlertActionsMenu.tsx @@ -13,42 +13,42 @@ * 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 React from 'react'; import { ilertApiRef } from '../../api'; -import { Incident, IncidentAction } from '../../types'; -import { IncidentAssignModal } from './IncidentAssignModal'; -import { useIncidentActions } from '../../hooks/useIncidentActions'; +import { useAlertActions } from '../../hooks/useAlertActions'; +import { Alert, AlertAction } from '../../types'; +import { AlertAssignModal } from './AlertAssignModal'; +import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model'; +import { Link, Progress } from '@backstage/core-components'; import { alertApiRef, - useApi, identityApiRef, + useApi, } from '@backstage/core-plugin-api'; -import { Progress, Link } from '@backstage/core-components'; -import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model'; -export const IncidentActionsMenu = ({ - incident, - onIncidentChanged, +export const AlertActionsMenu = ({ + alert, + onAlertChanged, setIsLoading, }: { - incident: Incident; - onIncidentChanged?: (incident: Incident) => void; + alert: Alert; + onAlertChanged?: (alert: Alert) => void; setIsLoading?: (isLoading: boolean) => void; }) => { const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); const [anchorEl, setAnchorEl] = React.useState(null); - const callback = onIncidentChanged || ((_: Incident): void => {}); + const callback = onAlertChanged || ((_: Alert): void => {}); const setProcessing = setIsLoading || ((_: boolean): void => {}); - const [isAssignIncidentModalOpened, setIsAssignIncidentModalOpened] = + const [isAssignAlertModalOpened, setIsAssignAlertModalOpened] = React.useState(false); - const [{ incidentActions, isLoading }] = useIncidentActions( - incident, + const [{ alertActions, isLoading }] = useAlertActions( + alert, Boolean(anchorEl), ); @@ -70,10 +70,10 @@ export const IncidentActionsMenu = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - const newIncident = await ilertApi.acceptIncident(incident, userName); - alertApi.post({ message: 'Incident accepted.' }); + const newAlert = await ilertApi.acceptAlert(alert, userName); + alertApi.post({ message: 'Alert accepted.' }); - callback(newIncident); + callback(newAlert); setProcessing(false); } catch (err) { setProcessing(false); @@ -90,10 +90,10 @@ export const IncidentActionsMenu = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - const newIncident = await ilertApi.resolveIncident(incident, userName); - alertApi.post({ message: 'Incident resolved.' }); + const newAlert = await ilertApi.resolveAlert(alert, userName); + alertApi.post({ message: 'Alert resolved.' }); - callback(newIncident); + callback(newAlert); setProcessing(false); } catch (err) { setProcessing(false); @@ -103,15 +103,15 @@ export const IncidentActionsMenu = ({ const handleAssign = () => { handleCloseMenu(); - setIsAssignIncidentModalOpened(true); + setIsAssignAlertModalOpened(true); }; - const handleTriggerAction = (action: IncidentAction) => async () => { + const handleTriggerAction = (action: AlertAction) => async () => { try { handleCloseMenu(); setProcessing(true); - await ilertApi.triggerIncidentAction(incident, action); - alertApi.post({ message: 'Incident action triggered.' }); + await ilertApi.triggerAlertAction(alert, action); + alertApi.post({ message: 'Alert action triggered.' }); setProcessing(false); } catch (err) { setProcessing(false); @@ -119,7 +119,7 @@ export const IncidentActionsMenu = ({ } }; - const actions: React.ReactNode[] = incidentActions.map(a => { + const actions: React.ReactNode[] = alertActions.map(a => { const successTrigger = a.history ? a.history.find(h => h.success) : undefined; @@ -152,7 +152,7 @@ export const IncidentActionsMenu = ({ - {incident.status === 'PENDING' ? ( + {alert.status === 'PENDING' ? ( Accept @@ -169,7 +169,7 @@ export const IncidentActionsMenu = ({ ) : null} - {incident.status !== 'RESOLVED' ? ( + {alert.status !== 'RESOLVED' ? ( Resolve @@ -177,7 +177,7 @@ export const IncidentActionsMenu = ({ ) : null} - {incident.status !== 'RESOLVED' ? ( + {alert.status !== 'RESOLVED' ? ( Assign @@ -195,17 +195,15 @@ export const IncidentActionsMenu = ({ - - View in iLert - + View in iLert - ); diff --git a/plugins/ilert/src/components/Incident/IncidentAssignModal.tsx b/plugins/ilert/src/components/Alert/AlertAssignModal.tsx similarity index 76% rename from plugins/ilert/src/components/Incident/IncidentAssignModal.tsx rename to plugins/ilert/src/components/Alert/AlertAssignModal.tsx index 5fd8a97df3..d1ecefafa1 100644 --- a/plugins/ilert/src/components/Incident/IncidentAssignModal.tsx +++ b/plugins/ilert/src/components/Alert/AlertAssignModal.tsx @@ -13,21 +13,21 @@ * 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 Alert from '@material-ui/lab/Alert'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; +import { Typography } from '@material-ui/core'; import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; 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 { makeStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import MUIAlert from '@material-ui/lab/Alert'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import { useAssignIncident } from '../../hooks/useAssignIncident'; -import { Typography } from '@material-ui/core'; +import React from 'react'; import { ilertApiRef } from '../../api'; -import { Incident } from '../../types'; -import { alertApiRef, useApi } from '@backstage/core-plugin-api'; +import { useAssignAlert } from '../../hooks/useAssignAlert'; +import { Alert } from '../../types'; const useStyles = makeStyles(() => ({ container: { @@ -55,45 +55,42 @@ const useStyles = makeStyles(() => ({ }, })); -export const IncidentAssignModal = ({ - incident, +export const AlertAssignModal = ({ + alert, isModalOpened, setIsModalOpened, - onIncidentChanged, + onAlertChanged, }: { - incident: Incident | null; + alert: Alert | null; isModalOpened: boolean; setIsModalOpened: (open: boolean) => void; - onIncidentChanged?: (incident: Incident) => void; + onAlertChanged?: (alert: Alert) => void; }) => { const [ - { incidentRespondersList, incidentResponder, isLoading }, - { setIsLoading, setIncidentResponder, setIncidentRespondersList }, - ] = useAssignIncident(incident, isModalOpened); - const callback = onIncidentChanged || ((_: Incident): void => {}); + { alertRespondersList, alertResponder, isLoading }, + { setIsLoading, setAlertResponder, setAlertRespondersList }, + ] = useAssignAlert(alert, isModalOpened); + const callback = onAlertChanged || ((_: Alert): void => {}); const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const classes = useStyles(); const handleClose = () => { - setIncidentRespondersList([]); + setAlertRespondersList([]); setIsModalOpened(false); }; const handleAssign = () => { - if (!incident || !incidentResponder) { + if (!alert || !alertResponder) { return; } setIsLoading(true); - setIncidentRespondersList([]); + setAlertRespondersList([]); setTimeout(async () => { try { - const newIncident = await ilertApi.assignIncident( - incident, - incidentResponder, - ); - callback(newIncident); - alertApi.post({ message: 'Incident assigned.' }); + const newAlert = await ilertApi.assignAlert(alert, alertResponder); + callback(newAlert); + alertApi.post({ message: 'Alert assigned.' }); } catch (err) { alertApi.post({ message: err, severity: 'error' }); } @@ -102,33 +99,33 @@ export const IncidentAssignModal = ({ }, 250); }; - const canAssign = !!incidentResponder; + const canAssign = !!alertResponder; return ( - + Select responder to assign - + - This action will assign the incident to the selected responder. + This action will assign the alert to the selected responder. - + { - setIncidentResponder(newValue); + setAlertResponder(newValue); }} autoHighlight groupBy={option => { diff --git a/plugins/ilert/src/components/Alert/AlertLink.tsx b/plugins/ilert/src/components/Alert/AlertLink.tsx new file mode 100644 index 0000000000..4382d6608b --- /dev/null +++ b/plugins/ilert/src/components/Alert/AlertLink.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 { Alert } from '../../types'; + +import { Link } from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const AlertLink = ({ alert }: { alert: Alert | null }) => { + const ilertApi = useApi(ilertApiRef); + + if (!alert) { + return null; + } + + return #{alert.id}; +}; diff --git a/plugins/ilert/src/components/Incident/IncidentNewModal.tsx b/plugins/ilert/src/components/Alert/AlertNewModal.tsx similarity index 92% rename from plugins/ilert/src/components/Incident/IncidentNewModal.tsx rename to plugins/ilert/src/components/Alert/AlertNewModal.tsx index 561047d45e..66928e07e3 100644 --- a/plugins/ilert/src/components/Incident/IncidentNewModal.tsx +++ b/plugins/ilert/src/components/Alert/AlertNewModal.tsx @@ -13,27 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; import { DEFAULT_NAMESPACE, parseEntityRef } from '@backstage/catalog-model'; -import { makeStyles } from '@material-ui/core/styles'; -import Alert from '@material-ui/lab/Alert'; -import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; -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 Autocomplete from '@material-ui/lab/Autocomplete'; -import { useNewIncident } from '../../hooks/useNewIncident'; -import { Typography } from '@material-ui/core'; -import useMediaQuery from '@material-ui/core/useMediaQuery'; -import { ilertApiRef } from '../../api'; -import { AlertSource } from '../../types'; import { alertApiRef, identityApiRef, useApi, } from '@backstage/core-plugin-api'; +import { Typography } from '@material-ui/core'; +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 { makeStyles } from '@material-ui/core/styles'; +import TextField from '@material-ui/core/TextField'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; +import Alert from '@material-ui/lab/Alert'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import React from 'react'; +import { ilertApiRef } from '../../api'; +import { useNewAlert } from '../../hooks/useNewAlert'; +import { AlertSource } from '../../types'; const useStyles = makeStyles(() => ({ container: { @@ -61,23 +61,23 @@ const useStyles = makeStyles(() => ({ }, })); -export const IncidentNewModal = ({ +export const AlertNewModal = ({ isModalOpened, setIsModalOpened, - refetchIncidents, + refetchAlerts, initialAlertSource, entityName, }: { isModalOpened: boolean; setIsModalOpened: (open: boolean) => void; - refetchIncidents: () => void; + refetchAlerts: () => void; initialAlertSource?: AlertSource | null; entityName?: string; }) => { const [ { alertSources, alertSource, summary, details, isLoading }, { setAlertSource, setSummary, setDetails, setIsLoading }, - ] = useNewIncident(isModalOpened, initialAlertSource); + ] = useNewAlert(isModalOpened, initialAlertSource); const ilertApi = useApi(ilertApiRef); const alertApi = useApi(alertApiRef); const identityApi = useApi(identityApiRef); @@ -107,15 +107,15 @@ export const IncidentNewModal = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - await ilertApi.createIncident({ + await ilertApi.createAlert({ integrationKey, summary, details, userName, source, }); - alertApi.post({ message: 'Incident created.' }); - refetchIncidents(); + alertApi.post({ message: 'Alert created.' }); + refetchAlerts(); } catch (err) { alertApi.post({ message: err, severity: 'error' }); } @@ -129,16 +129,16 @@ export const IncidentNewModal = ({ - + {entityName ? (
- This action will trigger an incident for{' '} + This action will trigger an alert for{' '} "{entityName}".
) : ( - 'New incident' + 'New alert' )}
diff --git a/plugins/xcmetrics/src/components/BuildsPage/index.ts b/plugins/ilert/src/components/Alert/index.ts similarity index 93% rename from plugins/xcmetrics/src/components/BuildsPage/index.ts rename to plugins/ilert/src/components/Alert/index.ts index 47fe9166df..265fa23f81 100644 --- a/plugins/xcmetrics/src/components/BuildsPage/index.ts +++ b/plugins/ilert/src/components/Alert/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { BuildsPage } from './BuildsPage'; +export * from './AlertActionsMenu'; diff --git a/plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx b/plugins/ilert/src/components/AlertsPage/AlertsPage.tsx similarity index 68% rename from plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx rename to plugins/ilert/src/components/AlertsPage/AlertsPage.tsx index 3dc0907777..053b5a4267 100644 --- a/plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx +++ b/plugins/ilert/src/components/AlertsPage/AlertsPage.tsx @@ -13,42 +13,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { AuthenticationError } from '@backstage/errors'; -import Button from '@material-ui/core/Button'; -import AddIcon from '@material-ui/icons/Add'; -import { IncidentsTable } from './IncidentsTable'; -import { MissingAuthorizationHeaderError } from '../Errors'; -import { useIncidents } from '../../hooks/useIncidents'; -import { IncidentNewModal } from '../Incident/IncidentNewModal'; import { Content, ContentHeader, - SupportButton, ResponseErrorPanel, + SupportButton, } from '@backstage/core-components'; +import Button from '@material-ui/core/Button'; +import AddIcon from '@material-ui/icons/Add'; +import React from 'react'; +import { useAlerts } from '../../hooks/useAlerts'; +import { AlertNewModal } from '../Alert/AlertNewModal'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { AlertsTable } from './AlertsTable'; -export const IncidentsPage = () => { +export const AlertsPage = () => { const [ - { tableState, states, incidents, incidentsCount, isLoading, error }, + { tableState, states, alerts, alertsCount, isLoading, error }, { - onIncidentStatesChange, + onAlertStatesChange, onChangePage, onChangeRowsPerPage, - onIncidentChanged, - refetchIncidents, + onAlertChanged, + refetchAlerts, setIsLoading, }, - ] = useIncidents(true); + ] = useAlerts(true); const [isModalOpened, setIsModalOpened] = React.useState(false); - const handleCreateNewIncidentClick = () => { + const handleCreateNewAlertClick = () => { setIsModalOpened(true); }; if (error) { - if (error instanceof AuthenticationError) { + if (error.name === 'AuthenticationError') { return ( @@ -65,32 +64,32 @@ export const IncidentsPage = () => { return ( - + - This helps you to bring iLert into your developer portal. - ({ }, })); -export const IncidentsTable = ({ - incidents, - incidentsCount, +export const AlertsTable = ({ + alerts, + alertsCount, tableState, states, isLoading, - onIncidentChanged, + onAlertChanged, setIsLoading, - onIncidentStatesChange, + onAlertStatesChange, onChangePage, onChangeRowsPerPage, compact, }: { - incidents: Incident[]; - incidentsCount: number; + alerts: Alert[]; + alertsCount: number; tableState: TableState; - states: IncidentStatus[]; + states: AlertStatus[]; isLoading: boolean; - onIncidentChanged: (incident: Incident) => void; + onAlertChanged: (alert: Alert) => void; setIsLoading: (isLoading: boolean) => void; - onIncidentStatesChange: (states: IncidentStatus[]) => void; + onAlertStatesChange: (states: AlertStatus[]) => void; onChangePage: (page: number) => void; onChangeRowsPerPage: (pageSize: number) => void; compact?: boolean; @@ -86,31 +86,29 @@ export const IncidentsTable = ({ maxWidth: '30%', }; - const idColumn: TableColumn = { + const idColumn: TableColumn = { title: 'ID', field: 'id', highlight: true, cellStyle: smColumnStyle, headerStyle: smColumnStyle, - render: rowData => , + render: rowData => , }; - const summaryColumn: TableColumn = { + const summaryColumn: TableColumn = { title: 'Summary', field: 'summary', cellStyle: !compact ? xlColumnStyle : undefined, headerStyle: !compact ? xlColumnStyle : undefined, - render: rowData => {(rowData as Incident).summary}, + render: rowData => {rowData.summary}, }; - const sourceColumn: TableColumn = { + const sourceColumn: TableColumn = { title: 'Source', field: 'source', cellStyle: mdColumnStyle, headerStyle: mdColumnStyle, - render: rowData => ( - - ), + render: rowData => , }; - const durationColumn: TableColumn = { + const durationColumn: TableColumn = { title: 'Duration', field: 'reportTime', type: 'datetime', @@ -118,20 +116,17 @@ export const IncidentsTable = ({ headerStyle: smColumnStyle, render: rowData => ( - {(rowData as Incident).status !== 'RESOLVED' + {rowData.status !== 'RESOLVED' ? humanizeDuration( - Interval.fromDateTimes( - dt.fromISO((rowData as Incident).reportTime), - dt.now(), - ) + Interval.fromDateTimes(dt.fromISO(rowData.reportTime), dt.now()) .toDuration() .valueOf(), { units: ['h', 'm', 's'], largest: 2, round: true }, ) : humanizeDuration( Interval.fromDateTimes( - dt.fromISO((rowData as Incident).reportTime), - dt.fromISO((rowData as Incident).resolvedOn), + dt.fromISO(rowData.reportTime), + dt.fromISO(rowData.resolvedOn), ) .toDuration() .valueOf(), @@ -140,54 +135,59 @@ export const IncidentsTable = ({ ), }; - const assignedToColumn: TableColumn = { - title: 'Assigned to', - field: 'assignedTo', + const respondersColumn: TableColumn = { + title: 'Responders', + field: 'responders', cellStyle: !compact ? mdColumnStyle : lgColumnStyle, headerStyle: !compact ? mdColumnStyle : lgColumnStyle, render: rowData => ( - - {ilertApi.getUserInitials((rowData as Incident).assignedTo)} + + {rowData.responders.map((value, i, arr) => { + return ( + ilertApi.getUserInitials(value.user) + + (arr.length - 1 !== i ? ', ' : '') + ); + })} ), }; - const priorityColumn: TableColumn = { + const priorityColumn: TableColumn = { title: 'Priority', field: 'priority', cellStyle: smColumnStyle, headerStyle: smColumnStyle, render: rowData => ( - {(rowData as Incident).priority === 'HIGH' ? 'High' : 'Low'} + {rowData.priority === 'HIGH' ? 'High' : 'Low'} ), }; - const statusColumn: TableColumn = { + const statusColumn: TableColumn = { title: 'Status', field: 'status', cellStyle: xsColumnStyle, headerStyle: xsColumnStyle, - render: rowData => , + render: rowData => , }; - const actionsColumn: TableColumn = { + const actionsColumn: TableColumn = { title: '', field: '', cellStyle: xsColumnStyle, headerStyle: xsColumnStyle, render: rowData => ( - ), }; - const columns: TableColumn[] = compact + const columns: TableColumn[] = compact ? [ summaryColumn, durationColumn, - assignedToColumn, + respondersColumn, statusColumn, actionsColumn, ] @@ -196,31 +196,14 @@ export const IncidentsTable = ({ summaryColumn, sourceColumn, durationColumn, - assignedToColumn, + respondersColumn, priorityColumn, statusColumn, actionsColumn, ]; - let tableStyle: React.CSSProperties = {}; - if (compact) { - tableStyle = { - width: '100%', - maxWidth: '100%', - minWidth: '0', - height: 'calc(100% - 10px)', - boxShadow: 'none !important', - borderRadius: 'none !important', - }; - } else { - tableStyle = { - width: '100%', - maxWidth: '100%', - }; - } 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 105a79e2bf..54573cdaec 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 89c3076e7b..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", + "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 cebfb0b0a5..fc9c74184d 100644 --- a/plugins/jenkins-common/CHANGELOG.md +++ b/plugins/jenkins-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/jenkins-common/package.json b/plugins/jenkins-common/package.json index 01215b674d..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", + "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 ff0df1f256..2a15d2917a 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,19 @@ # @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 diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 9927fb38ae..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", + "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 16dbd0612b..5999228785 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 677dad0f01..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", + "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 5b98e9ffb7..ec8563d64b 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 966c3962a6..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", + "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 085617f8af..804112324d 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,26 @@ # @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 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/package.json b/plugins/kubernetes-backend/package.json index 3e96aeb347..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", + "version": "0.8.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", 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 5f561ede04..b85e3c00ee 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 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 afc3aefc34..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", + "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 ebc378d85c..2272804b22 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index d56fb01c55..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", + "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 2ab7368212..8a5a1bee97 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index f7243d2ce6..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", + "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 d8bfa338d2..81efe251d3 100644 --- a/plugins/newrelic-dashboard/CHANGELOG.md +++ b/plugins/newrelic-dashboard/CHANGELOG.md @@ -1,5 +1,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 + ## 0.2.3 ### Patch Changes diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index 803ba77c1f..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", + "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 8722383768..526010f2f8 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 8f8b58835d..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", + "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 d5d0425128..682da2da91 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/plugins/org/package.json b/plugins/org/package.json index 4fde7c83a6..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", + "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 157b091fc9..ec4a152a4d 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 4d22758614..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", + "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 2bedbee959..63ca36cc60 100644 --- a/plugins/periskop-backend/CHANGELOG.md +++ b/plugins/periskop-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index 3db0d54859..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", + "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 f27132f8f7..afec1859f2 100644 --- a/plugins/periskop/CHANGELOG.md +++ b/plugins/periskop/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 1661731d2f..53971e9930 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop", - "version": "0.1.8", + "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 a656f43ed4..d57c79ca08 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 624accca06..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", + "version": "0.5.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index c06b9a2ad1..31c212bffd 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 8fb92a0d2b..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.7.0", + "version": "0.7.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts index bcc6ddd6f6..75eb2b1ff3 100644 --- a/plugins/permission-common/src/PermissionClient.test.ts +++ b/plugins/permission-common/src/PermissionClient.test.ts @@ -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 c5f56be159..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.record(z.any()), + 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 5e7a2bb349..322195a63f 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -15,8 +15,7 @@ */ import { JsonPrimitive } from '@backstage/types'; -import { ResourcePermission } from '.'; -import { Permission } from './permission'; +import { Permission, ResourcePermission } from './permission'; /** * A request with a UUID identifier, so that batched responses can be matched up with the original diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 9082ab8337..2d31993117 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 0111c4505a..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.7.0", + "version": "0.7.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 43fce22e80..c8e1cb072e 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index c852ecec3e..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", + "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 2a3d2061ce..9798c12013 100644 --- a/plugins/playlist-backend/CHANGELOG.md +++ b/plugins/playlist-backend/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-playlist-backend +## 0.2.1-next.1 + +### Patch Changes + +- 605f269f0d: Updated Playlist plugin docs: + + - Updated `playlist` plugin README to include note about installing backend plugin and added images for the various features + - Updated `playlist-backend` plugin README to remove `IdentityClient` import in example as it is not used and made minor change to headings + +## 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 diff --git a/plugins/playlist-backend/README.md b/plugins/playlist-backend/README.md index 14a39ca741..c0055ebdf7 100644 --- a/plugins/playlist-backend/README.md +++ b/plugins/playlist-backend/README.md @@ -2,21 +2,20 @@ Welcome to the playlist backend plugin! -# Installation +## Installation -## Install the package +### Install the package ```bash # From your Backstage root directory yarn add --cwd packages/backend @backstage/plugin-playlist-backend ``` -## Adding the plugin to your `packages/backend` +### Adding the plugin to your `packages/backend` You'll need to add the plugin to the router in your `backend` package. You can do this by creating a file called `packages/backend/src/plugins/playlist.ts` ```tsx -import { IdentityClient } from '@backstage/plugin-auth-node'; import { createRouter } from '@backstage/plugin-playlist-backend'; import { Router } from 'express'; import { PluginEnvironment } from '../types'; diff --git a/plugins/playlist-backend/package.json b/plugins/playlist-backend/package.json index 38e0729844..8548a3e4b8 100644 --- a/plugins/playlist-backend/package.json +++ b/plugins/playlist-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist-backend", - "version": "0.2.0", + "version": "0.2.1-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist-common/CHANGELOG.md b/plugins/playlist-common/CHANGELOG.md index 2887b11c01..7dcf06740e 100644 --- a/plugins/playlist-common/CHANGELOG.md +++ b/plugins/playlist-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @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 diff --git a/plugins/playlist-common/package.json b/plugins/playlist-common/package.json index eccb95faf3..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", + "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 ce6bac5960..d7135e79a0 100644 --- a/plugins/playlist/CHANGELOG.md +++ b/plugins/playlist/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-playlist +## 0.1.2-next.1 + +### Patch Changes + +- 605f269f0d: Updated Playlist plugin docs: + + - Updated `playlist` plugin README to include note about installing backend plugin and added images for the various features + - Updated `playlist-backend` plugin README to remove `IdentityClient` import in example as it is not used and made minor change to headings + +## 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 diff --git a/plugins/playlist/README.md b/plugins/playlist/README.md index 7c814d302b..6ae3fb84ac 100644 --- a/plugins/playlist/README.md +++ b/plugins/playlist/README.md @@ -6,6 +6,14 @@ This plugin allows you to create, share, and follow custom collections of entiti ## Setup +The following sections will help you get the Playlist plugin setup and running + +### Backend + +You need to setup the [Playlist backend plugin](https://github.com/backstage/backstage/tree/master/plugins/playlist-backend) before you move forward with any of these steps if you haven't already + +### Installation + Install this plugin: ```bash @@ -123,6 +131,28 @@ const defaultEntityPage = ( Note: the above only shows an example for the `defaultEntityPage` for a full example of this you can look at [this EntityPage](../../packages/app/src/components/catalog/EntityPage.tsx) +## Features + +### View All Playlists + +![View all playlists example](./docs/playlist-view-all.png) + +### View Playlist + +![View Playlist example](./docs/playlist-view-single.png) + +### Create New Playlist + +![Create New Playlist example](./docs/playlist-create-new.png) + +### Add Entities to Playlist + +![Add Entities to Playlist example](./docs/playlist-add-entities.png) + +### Add to Playlist from Entity + +![Add to Playlist from Entity example](./docs/playlist-add-from-entity.png) + ## Links - [playlist-backend](../playlist-backend) provides the backend API for this frontend. diff --git a/plugins/playlist/docs/playlist-add-entities.png b/plugins/playlist/docs/playlist-add-entities.png new file mode 100644 index 0000000000..a53de49880 Binary files /dev/null and b/plugins/playlist/docs/playlist-add-entities.png differ diff --git a/plugins/playlist/docs/playlist-add-from-entity.png b/plugins/playlist/docs/playlist-add-from-entity.png new file mode 100644 index 0000000000..d41135064a Binary files /dev/null and b/plugins/playlist/docs/playlist-add-from-entity.png differ diff --git a/plugins/playlist/docs/playlist-create-new.png b/plugins/playlist/docs/playlist-create-new.png new file mode 100644 index 0000000000..cc369e26fa Binary files /dev/null and b/plugins/playlist/docs/playlist-create-new.png differ diff --git a/plugins/playlist/docs/playlist-view-all.png b/plugins/playlist/docs/playlist-view-all.png new file mode 100644 index 0000000000..1b65eb1af2 Binary files /dev/null and b/plugins/playlist/docs/playlist-view-all.png differ diff --git a/plugins/playlist/docs/playlist-view-single.png b/plugins/playlist/docs/playlist-view-single.png new file mode 100644 index 0000000000..0890c5013b Binary files /dev/null and b/plugins/playlist/docs/playlist-view-single.png differ diff --git a/plugins/playlist/package.json b/plugins/playlist/package.json index bbc4f52f32..e8fc47a2a9 100644 --- a/plugins/playlist/package.json +++ b/plugins/playlist/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist", - "version": "0.1.1", + "version": "0.1.2-next.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -12,6 +12,11 @@ "backstage": { "role": "frontend-plugin" }, + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/playlist" + }, "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 948d4cb393..91c3419ccd 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index eafad6628c..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", + "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 13dca3c262..962b167902 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 032ba55b21..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", + "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 fc4d626bed..340e06d265 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 84fcdd80a8..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", + "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 cac90c0a56..37d00c6885 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + +## 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 diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 8032ab5e09..939e2801da 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", + "version": "0.2.13-next.1", "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 9abf4e928e..65f0b6cc49 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + +## 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 diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 91da543f0e..8f39024301 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", + "version": "0.4.6-next.1", "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 2f19d95d78..56cc580bbd 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.11-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.8.0-next.1 + +## 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 diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 768ea468ae..cf7a13a7a4 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", + "version": "0.2.11-next.1", "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 3437603d8a..0a86b5e9bb 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-scaffolder-backend +## 1.8.0-next.1 + +### Minor Changes + +- 5921b5ce49: - The GitLab Project ID for the `publish:gitlab:merge-request` action is now passed through the query parameter `project` in the `repoUrl`. It still allows people to not use the `projectid` and use the `repoUrl` with the `owner` and `repo` query parameters instead. This makes it easier to publish to repositories instead of writing the full path to the project. + +## 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 diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index a21423f865..a52adf1cf7 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", + "version": "1.8.0-next.1", "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/actions/builtin/publish/gitlabMergeRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts index 64761f0871..2a71c9f748 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts @@ -42,7 +42,7 @@ export const createPublishGitlabMergeRequestAction = (options: { targetPath?: string; token?: string; commitAction?: 'create' | 'delete' | 'update'; - /** @deprecated Use projectPath instead */ + /** @deprecated projectID passed as query parameters in the repoUrl */ projectid?: string; removeSourceBranch?: boolean; assignee?: string; @@ -58,7 +58,7 @@ export const createPublishGitlabMergeRequestAction = (options: { title: 'Repository Location', description: `Accepts the format 'gitlab.com/group_name/project_name' where 'project_name' is the repository name and 'group_name' is a group or username`, }, - /** @deprecated Use projectPath instead */ + /** @deprecated projectID is passed as query parameters in the repoUrl */ projectid: { type: 'string', title: 'projectid', @@ -146,14 +146,12 @@ export const createPublishGitlabMergeRequestAction = (options: { title, token: providedToken, } = ctx.input; - const { host, owner, repo } = parseRepoUrl(repoUrl, integrations); - const projectPath = `${owner}/${repo}`; - if (ctx.input.projectid) { - const deprecationWarning = `Property "projectid" is deprecated and no longer to needed to create a MR`; - ctx.logger.warn(deprecationWarning); - console.warn(deprecationWarning); - } + const { host, owner, repo, project } = parseRepoUrl( + repoUrl, + integrations, + ); + const repoID = project ? project : `${owner}/${repo}`; const integrationConfig = integrations.gitlab.byHost(host); @@ -211,22 +209,19 @@ export const createPublishGitlabMergeRequestAction = (options: { content: file.content.toString('base64'), execute_filemode: file.executable, })); - const projects = await api.Projects.show(projectPath); + + const projects = await api.Projects.show(repoID); const { default_branch: defaultBranch } = projects; try { - await api.Branches.create( - projectPath, - branchName, - String(defaultBranch), - ); + await api.Branches.create(repoID, branchName, String(defaultBranch)); } catch (e) { throw new InputError(`The branch creation failed ${e}`); } try { - await api.Commits.create(projectPath, branchName, title, actions); + await api.Commits.create(repoID, branchName, ctx.input.title, actions); } catch (e) { throw new InputError( `Committing the changes to ${branchName} failed ${e}`, @@ -235,7 +230,7 @@ export const createPublishGitlabMergeRequestAction = (options: { try { const mergeRequestUrl = await api.MergeRequests.create( - projectPath, + repoID, branchName, String(defaultBranch), title, @@ -247,9 +242,8 @@ export const createPublishGitlabMergeRequestAction = (options: { ).then((mergeRequest: { web_url: string }) => { return mergeRequest.web_url; }); - /** @deprecated */ - ctx.output('projectid', projectPath); - ctx.output('projectPath', projectPath); + ctx.output('projectid', repoID); + ctx.output('projectPath', repoID); ctx.output('mergeRequestUrl', mergeRequestUrl); } catch (e) { throw new InputError(`Merge request creation failed${e}`); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts index cc879d4f55..5446109056 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.test.ts @@ -15,7 +15,9 @@ */ import path from 'path'; -import { getRepoSourceDirectory, isExecutable } from './util'; +import { getRepoSourceDirectory, isExecutable, parseRepoUrl } from './util'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; describe('getRepoSourceDirectory', () => { it('should return workspace root if no sub folder is given', () => { @@ -85,3 +87,95 @@ describe('isExecutable', () => { expect(isExecutable(0o100640)).toBe(false); }); }); + +describe('parseRepoUrl', () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'www.gitlab.com', + token: 'token', + apiBaseUrl: 'https://api.gitlab.com', + }, + ], + bitbucket: [ + { + host: 'www.bitbucket.net', + apiBaseUrl: 'https://api.hosted.bitbucket.net', + }, + { + host: 'www.bitbucket.org', + username: 'username', + appPassword: 'password', + apiBaseUrl: 'https://api.hosted.bitbucket.org', + }, + ], + }, + }); + const integrations = ScmIntegrations.fromConfig(config); + it('should throw an exception if no parameters are passed (gitlab)', () => { + expect(() => parseRepoUrl('www.gitlab.com', integrations)).toThrow( + 'Invalid repo URL passed to publisher: https://www.gitlab.com/, missing owner', + ); + }); + it('should return the project ID in the `project` parameter (gitlab)', () => { + expect( + parseRepoUrl('www.gitlab.com?project=234', integrations), + ).toStrictEqual({ + host: 'www.gitlab.com', + organization: undefined, + owner: undefined, + project: '234', + repo: null, + workspace: undefined, + }); + }); + it('should throw an exception if the repo parameter is missing, but owner parameter is set', () => { + expect(() => + parseRepoUrl('www.gitlab.com?owner=owner', integrations), + ).toThrow( + 'Invalid repo URL passed to publisher: https://www.gitlab.com/?owner=owner, missing repo', + ); + }); + it('should throw an exception if the owner parameter is missing, but repo parameter is set (gitlab)', () => { + expect(() => + parseRepoUrl('www.gitlab.com?repo=repo', integrations), + ).toThrow( + 'Invalid repo URL passed to publisher: https://www.gitlab.com/?repo=repo, missing owner', + ); + }); + it('should return the workspace, project and repo (bitbucket.org)', () => { + expect( + parseRepoUrl( + 'www.bitbucket.org?project=project&workspace=workspace&repo=repo', + integrations, + ), + ).toStrictEqual({ + host: 'www.bitbucket.org', + organization: undefined, + owner: undefined, + project: 'project', + repo: 'repo', + workspace: 'workspace', + }); + }); + it('should return the project and repo (another bitbucket instance)', () => { + expect( + parseRepoUrl('www.bitbucket.net?project=project&repo=repo', integrations), + ).toStrictEqual({ + host: 'www.bitbucket.net', + organization: undefined, + owner: undefined, + project: 'project', + repo: 'repo', + workspace: undefined, + }); + }); + it('should throw an exception if the workspace parameter is missing for bitbucket.org instance (bitbucket.org)', () => { + expect(() => + parseRepoUrl('www.bitbucket.org?project=project&repo=repo', integrations), + ).toThrow( + 'Invalid repo URL passed to publisher: https://www.bitbucket.org/?project=project&repo=repo, missing workspace', + ); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts index fff3213cb7..61bd704a0b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts @@ -71,32 +71,30 @@ export const parseRepoUrl = ( ); } - if (type === 'bitbucket') { - if (host === 'bitbucket.org') { - if (!workspace) { - throw new InputError( - `Invalid repo URL passed to publisher: ${repoUrl}, missing workspace`, - ); + const repo: string = parsed.searchParams.get('repo')!; + switch (type) { + case 'bitbucket': { + if (host === 'www.bitbucket.org') { + checkRequiredParams(parsed, 'workspace'); } + checkRequiredParams(parsed, 'project', 'repo'); + break; } - if (!project) { - throw new InputError( - `Invalid repo URL passed to publisher: ${repoUrl}, missing project`, - ); + case 'gitlab': { + // project is the projectID, and if defined, owner and repo won't be needed. + if (!project) { + checkRequiredParams(parsed, 'owner', 'repo'); + } + break; } - } else { - if (!owner && type !== 'gerrit') { - throw new InputError( - `Invalid repo URL passed to publisher: ${repoUrl}, missing owner`, - ); + case 'gerrit': { + checkRequiredParams(parsed, 'repo'); + break; + } + default: { + checkRequiredParams(parsed, 'repo', 'owner'); + break; } - } - - const repo = parsed.searchParams.get('repo'); - if (!repo) { - throw new InputError( - `Invalid repo URL passed to publisher: ${repoUrl}, missing repo`, - ); } return { host, owner, repo, organization, workspace, project }; @@ -106,3 +104,26 @@ export const isExecutable = (fileMode: number) => { const res = fileMode & executeBitMask; return res > 0; }; + +/** + * This function checks if the required parameters (given as the `params` argument) are present in the URL + * + * @param repoUrl - the URL for the repository + * @param params - a variadic list of URL query parameter names + * + * @throws + * An InputError exception is thrown if any of the required parameters is not in the URL. + * + * @public + */ +function checkRequiredParams(repoUrl: URL, ...params: string[]) { + for (let i = 0; i < params.length; i++) { + if (!repoUrl.searchParams.get(params[i])) { + throw new InputError( + `Invalid repo URL passed to publisher: ${repoUrl.toString()}, missing ${ + params[i] + }`, + ); + } + } +} 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.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 f2db35b47b..b6f8b3e77b 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 2ccb5bdd4f..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", + "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 d972491a64..709809f5f7 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,36 @@ # @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 diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index cf69736bd4..158c75e5bd 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -24,10 +24,12 @@ 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'; @@ -199,6 +201,9 @@ export type NextFieldExtensionOptions< validation?: NextCustomFieldValidator; }; +// @alpha (undocumented) +export const nextRouteRef: RouteRef; + // @alpha export type NextRouterProps = { components?: { @@ -215,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, @@ -287,6 +297,9 @@ export interface RepoUrlPickerUiOptions { }; } +// @public (undocumented) +export const rootRouteRef: RouteRef; + // @public export type RouterProps = { components?: { @@ -302,6 +315,11 @@ export type RouterProps = { filter: (entity: Entity) => boolean; }>; defaultPreviewTemplate?: string; + headerOptions?: { + pageTitleOverride?: string; + title?: string; + subtitle?: string; + }; contextMenu?: { editor?: boolean; actions?: boolean; @@ -508,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 0825e624d6..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", + "version": "1.8.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", 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/index.ts b/plugins/scaffolder/src/index.ts index e68eece8e5..7853667101 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -61,6 +61,12 @@ export { scaffolderPlugin, } from './plugin'; export * from './components'; +export { + rootRouteRef, + nextRouteRef, + selectedTemplateRouteRef, + nextSelectedTemplateRouteRef, +} from './routes'; export type { TaskPageProps } from './components/TaskPage'; /** next exports */ diff --git a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardLink.tsx b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardLink.tsx index 3ec31673db..9bdb3cf7fb 100644 --- a/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardLink.tsx +++ b/plugins/scaffolder/src/next/TemplateListPage/TemplateCard/CardLink.tsx @@ -15,7 +15,8 @@ */ import { IconComponent } from '@backstage/core-plugin-api'; import { BackstageTheme } from '@backstage/theme'; -import { Link, makeStyles } from '@material-ui/core'; +import { Link } from '@backstage/core-components'; +import { makeStyles } from '@material-ui/core'; import React from 'react'; interface CardLinkProps { @@ -37,7 +38,7 @@ export const CardLink = ({ icon: Icon, text, url }: CardLinkProps) => { return (
- + {text || url}
diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index 5e2d191443..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, diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index abccf644e2..30a3367bf4 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index c03084e2ab..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", + "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-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 0a50ee4e4a..dcd0bbdc78 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 355d118005..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", + "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 694e70c1f9..bfcd09dcad 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 121cc66423..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", + "version": "1.0.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 70e7e01a54..c2cc89ac5b 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,20 @@ # @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 diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 7977346d82..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", + "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 732e1e7aeb..4ed96d5397 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @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 diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index 2c68b023a5..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", + "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 8eb6f526f2..d5f100d97c 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index c4d93341e9..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", + "version": "1.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 79fce6a287..5e71299d1e 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,22 @@ # @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 diff --git a/plugins/search/package.json b/plugins/search/package.json index ec3a1d754d..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", + "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 005fa9aaff..661950e6ca 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 9a10fe7214..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", + "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 842b0d70a8..e918aa8608 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index b37c5bba1a..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", + "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 8b34005df1..e752ee9a2b 100644 --- a/plugins/sonarqube-backend/CHANGELOG.md +++ b/plugins/sonarqube-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @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 diff --git a/plugins/sonarqube-backend/package.json b/plugins/sonarqube-backend/package.json index baaa8cd488..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", + "version": "0.1.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index bf72ac2e28..dd5aa80817 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,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 + ## 0.4.2 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 18c1b316a9..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", + "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 fe0a21bea4..02a736b782 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 5543765716..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", + "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 5fc3631f54..50b989079a 100644 --- a/plugins/stack-overflow-backend/CHANGELOG.md +++ b/plugins/stack-overflow-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @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 diff --git a/plugins/stack-overflow-backend/README.md b/plugins/stack-overflow-backend/README.md index 62115b889c..a35a6678cb 100644 --- a/plugins/stack-overflow-backend/README.md +++ b/plugins/stack-overflow-backend/README.md @@ -37,11 +37,11 @@ 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({ - defaultRefreshIntervalSeconds: 600, + schedule, factory: StackOverflowQuestionsCollatorFactory.fromConfig(env.config, { logger: env.logger, requestParams: { 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 2fa1a69470..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", + "version": "0.1.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/stack-overflow/CHANGELOG.md b/plugins/stack-overflow/CHANGELOG.md index 45d6f8a73f..a725475f69 100644 --- a/plugins/stack-overflow/CHANGELOG.md +++ b/plugins/stack-overflow/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index 6d4c2aff09..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", + "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 14e195b249..84a682dc8e 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 9e4c5503c1..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", + "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 db98317e31..b39c640ddb 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,22 @@ # @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 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 402cbc49d5..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", + "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 13f502287e..80db666774 100644 --- a/plugins/tech-insights-common/CHANGELOG.md +++ b/plugins/tech-insights-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @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 diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json index 750a8f9ce7..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", + "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 d9605bd033..8173dbc132 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 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 20cb0152cc..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", + "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 fa13c9a426..f60027e045 100644 --- a/plugins/tech-insights/CHANGELOG.md +++ b/plugins/tech-insights/CHANGELOG.md @@ -1,5 +1,20 @@ # @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 diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index cd32374fb5..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", + "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-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index d7ae58f097..df5bee360a 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,15 @@ # @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 diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index bf20c2b4aa..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", + "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 04388b9c3d..ba3141919a 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs@1.4.0-next.1 + +## 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 diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index e5f52fed60..a7b6abd782 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", + "version": "1.0.6-next.1", "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 f05171ce9d..b038fa202f 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,30 @@ # @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 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/package.json b/plugins/techdocs-backend/package.json index f191f39f67..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.4.0", + "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/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 dd22792833..c56e9cbf12 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 786e167a0e..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", + "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 b1b1d9a4c1..cc8770c34b 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 692ca58fa9..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", + "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-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index fe2801aff2..9418517a6c 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @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 diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 1e0603799d..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", + "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 f266d5d1dd..d146a4d576 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-techdocs +## 1.4.0-next.1 + +### Patch Changes + +- 9e4d8e6198: Fix logic bug that broke techdocs-cli-embedded-app + +## 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 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 4e8a9dfc96..cb03cb9e3f 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", + "version": "1.4.0-next.1", "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 48fe718759..f4827591df 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 3c59254f89..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", + "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 d126f9a9ad..c97576555d 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/todo/package.json b/plugins/todo/package.json index ef061277c4..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", + "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 5cd32e82f5..54a75f3888 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,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 + ## 0.1.1 ### Patch Changes diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 47aa6b5978..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", + "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 dfced55e24..d73d533cba 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 33a57ccc86..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", + "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 dd9f5bb8bf..6f3270dba1 100644 --- a/plugins/vault-backend/CHANGELOG.md +++ b/plugins/vault-backend/CHANGELOG.md @@ -1,5 +1,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 + ## 0.2.3 ### Patch Changes diff --git a/plugins/vault-backend/package.json b/plugins/vault-backend/package.json index 27a6718c10..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", + "version": "0.2.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/vault/CHANGELOG.md b/plugins/vault/CHANGELOG.md index 610fe46f12..ce0f6c41cf 100644 --- a/plugins/vault/CHANGELOG.md +++ b/plugins/vault/CHANGELOG.md @@ -1,5 +1,17 @@ # @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 diff --git a/plugins/vault/package.json b/plugins/vault/package.json index 182b091743..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", + "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 1461d15b9d..8c68404127 100644 --- a/plugins/xcmetrics/CHANGELOG.md +++ b/plugins/xcmetrics/CHANGELOG.md @@ -1,5 +1,18 @@ # @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 diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index c9082c9802..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", + "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 1567968127..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.5": - version: 1.3.5 - resolution: "@swc/core-android-arm-eabi@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-android-arm64@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-darwin-arm64@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-darwin-x64@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-freebsd-x64@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-x64-musl@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.5" +"@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.5 - resolution: "@swc/core@npm:1.3.5" +"@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.5 - "@swc/core-android-arm64": 1.3.5 - "@swc/core-darwin-arm64": 1.3.5 - "@swc/core-darwin-x64": 1.3.5 - "@swc/core-freebsd-x64": 1.3.5 - "@swc/core-linux-arm-gnueabihf": 1.3.5 - "@swc/core-linux-arm64-gnu": 1.3.5 - "@swc/core-linux-arm64-musl": 1.3.5 - "@swc/core-linux-x64-gnu": 1.3.5 - "@swc/core-linux-x64-musl": 1.3.5 - "@swc/core-win32-arm64-msvc": 1.3.5 - "@swc/core-win32-ia32-msvc": 1.3.5 - "@swc/core-win32-x64-msvc": 1.3.5 + "@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: f0bcd0aa1d1a0b7d48ee5c416f8a384e19e4c0eed1e0be940d8893bf3981d98bf00f27d03bebf9cdedd34bf316d68c2db68e7bb7d1940d7b6062541e6e4cc738 + 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 56c3bc8693..c42aac2eec 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" @@ -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,6 +3291,17 @@ __metadata: languageName: unknown linkType: soft +"@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.2 + "@backstage/errors": ^1.1.2 + cross-fetch: ^3.1.5 + checksum: 788c357e5a783ce46e4dcd805b302ffe2b407ad9bb04e49ee6b6aa8fb42adfd9cae8a57e2d18cd4c81ef22011990a5eb6518f3f44fb812485d7c99226db26e1d + languageName: node + linkType: hard + "@backstage/catalog-client@workspace:^, @backstage/catalog-client@workspace:packages/catalog-client": version: 0.0.0-use.local resolution: "@backstage/catalog-client@workspace:packages/catalog-client" @@ -3194,7 +3314,22 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@^1.1.0-next.2, @backstage/catalog-model@^1.1.1, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@npm:^1.1.2": + version: 1.1.2 + resolution: "@backstage/catalog-model@npm:1.1.2" + dependencies: + "@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: d53e5db9f1662b6c2bd48b3b1fed97751b5bf8805575feb12ed2b6f600048d676f954fe4cfa214536d09f984535f0524a24c6330e08c4d255064d0eb62678bc1 + languageName: node + linkType: hard + +"@backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -3250,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 @@ -3408,7 +3543,17 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@^1.0.1, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": +"@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: 1026f741ac019043f46965eeb93c796110b7b98add181562a975e3ef9bf27332ea7841cd174a358a55c1edced892fc8b644263caf14f6c6d244b550a1176e1ad + languageName: node + linkType: hard + +"@backstage/config@workspace:^, @backstage/config@workspace:packages/config": version: 0.0.0-use.local resolution: "@backstage/config@workspace:packages/config" dependencies: @@ -3454,7 +3599,58 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@^0.11.0, @backstage/core-components@^0.11.1, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": +"@backstage/core-components@npm:^0.11.2": + version: 0.11.2 + resolution: "@backstage/core-components@npm:0.11.2" + dependencies: + "@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 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.57 + "@react-hookz/web": ^15.0.0 + "@types/react-sparklines": ^1.7.0 + "@types/react-text-truncate": ^0.14.0 + ansi-regex: ^6.0.1 + classnames: ^2.2.6 + d3-selection: ^3.0.0 + d3-shape: ^3.0.0 + d3-zoom: ^3.0.0 + dagre: ^0.8.5 + history: ^5.0.0 + immer: ^9.0.1 + lodash: ^4.17.21 + pluralize: ^8.0.0 + prop-types: ^15.7.2 + qs: ^6.9.4 + rc-progress: 3.4.0 + react-helmet: 6.1.0 + react-hook-form: ^7.12.2 + react-markdown: ^8.0.0 + react-sparklines: ^1.7.0 + react-syntax-highlighter: ^15.4.5 + react-text-truncate: ^0.19.0 + react-use: ^17.3.2 + react-virtualized-auto-sizer: ^1.0.6 + react-window: ^1.8.6 + remark-gfm: ^3.0.1 + zen-observable: ^0.8.15 + zod: ^3.11.6 + peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 + 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 + react-router-dom: 6.0.0-beta.0 || ^6.3.0 + checksum: 510a0c2594a9aa820293d79fbaeee72bef9ac5e6161645d05e2c64b7176050386984befa07921391f9758bfbf07f96170662f7881e894fb0b0f2787378b19182 + languageName: node + linkType: hard + +"@backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": version: 0.0.0-use.local resolution: "@backstage/core-components@workspace:packages/core-components" dependencies: @@ -3526,7 +3722,25 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@^1.0.3, @backstage/core-plugin-api@^1.0.6, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": +"@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.3 + "@backstage/types": ^1.0.0 + "@backstage/version-bridge": ^1.0.1 + history: ^5.0.0 + prop-types: ^15.7.2 + zen-observable: ^0.8.15 + peerDependencies: + "@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: ce07e5ec1f4f21a5e879e3ab2eb271721cc0eadd685a7457098afca1d8106140883fbc7c3cf7b436c594614152a88fcba7466df0900d9fa8e958baab2f8b1a6a + languageName: node + linkType: hard + +"@backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" dependencies: @@ -3567,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 @@ -3613,6 +3826,17 @@ __metadata: languageName: unknown linkType: soft +"@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: 1e8b58b8059af8e83ebc1c8fea94227ec887a55e3adc32ab959fa86ac20bed745703a5e046b17505c05e7306c1835834756d27cfc0cbeea2021cb9631656ed22 + languageName: node + linkType: hard + "@backstage/errors@workspace:^, @backstage/errors@workspace:packages/errors": version: 0.0.0-use.local resolution: "@backstage/errors@workspace:packages/errors" @@ -3624,7 +3848,26 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@^1.1.4, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": +"@backstage/integration-react@npm:^1.1.5": + version: 1.1.5 + resolution: "@backstage/integration-react@npm:1.1.5" + dependencies: + "@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 + "@material-ui/lab": 4.0.0-alpha.57 + react-use: ^17.2.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 + checksum: 2f50416e0ca7635a59b5bf4bf89b4cf8d91a755e7ba2e4be75fb338011f98533213f984ac2def7ff65fccd51d7a94ba064c59b47d0d1d41c2a4b51f54ed68992 + languageName: node + linkType: hard + +"@backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" dependencies: @@ -3651,6 +3894,22 @@ __metadata: languageName: unknown linkType: soft +"@backstage/integration@npm:^1.3.2": + version: 1.3.2 + resolution: "@backstage/integration@npm:1.3.2" + dependencies: + "@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: 960f0ea564021082f8b6e10a59782f6ffb80550dbd8a1b62e52d8b03bf72345759f0d744ef7c41434d32ad3987d4dd89e18645f818be6c00cd53e541c0a041a1 + languageName: node + linkType: hard + "@backstage/integration@workspace:^, @backstage/integration@workspace:packages/integration": version: 0.0.0-use.local resolution: "@backstage/integration@workspace:packages/integration" @@ -4138,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" @@ -4278,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 @@ -4289,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 @@ -4313,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:^" @@ -4321,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 @@ -4357,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:^" @@ -4365,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 @@ -4400,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:^" @@ -4408,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 @@ -4452,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:^" @@ -4460,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 @@ -4497,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 @@ -4589,7 +4927,18 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@^1.0.4-next.0, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": +"@backstage/plugin-catalog-common@npm:^1.0.7": + version: 1.0.7 + resolution: "@backstage/plugin-catalog-common@npm:1.0.7" + dependencies: + "@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 + +"@backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: @@ -4717,7 +5066,41 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@^1.1.2-next.2, @backstage/plugin-catalog-react@^1.1.4, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": +"@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.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 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.57 + classnames: ^2.2.6 + jwt-decode: ^3.1.0 + lodash: ^4.17.21 + qs: ^6.9.4 + react-use: ^17.2.4 + yaml: ^2.0.0 + zen-observable: ^0.8.15 + peerDependencies: + "@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: c2875ac0af1e3e051b2faf018bd46b9a1843c2fd67fcfd1a09a7931ad7fbd6adf591200d184f40194702aa6f21757c7bb70636861d25ef2f1e670fca0cb0a7cf + languageName: node + linkType: hard + +"@backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-react@workspace:plugins/catalog-react" dependencies: @@ -4999,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 @@ -5111,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: @@ -5176,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:^" @@ -5348,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 @@ -5481,6 +5865,7 @@ __metadata: cross-fetch: ^3.1.5 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 @@ -5606,7 +5991,31 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-home@^0.4.25, @backstage/plugin-home@workspace:^, @backstage/plugin-home@workspace:plugins/home": +"@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.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 + "@material-ui/lab": 4.0.0-alpha.57 + lodash: ^4.17.21 + react-use: ^17.2.4 + peerDependencies: + "@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: bb20f2538a1e155bbb7c5587fe953ecaacd6f0384b951dcd862be7a2fa277ed80d793336953cc778b6a574ee898064a5aab436b3c61d3695944084c748449095 + languageName: node + linkType: hard + +"@backstage/plugin-home@workspace:^, @backstage/plugin-home@workspace:plugins/home": version: 0.0.0-use.local resolution: "@backstage/plugin-home@workspace:plugins/home" dependencies: @@ -6108,6 +6517,20 @@ __metadata: languageName: unknown linkType: soft +"@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.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: e71705494b55efacf234a46abe162f241ebecb231a6a4c76d7134bb629c1b7d1ffdead8f9725ea515a818ce376a99d2121dc7ef1c8d0badd955fdb1d4cd405cf + languageName: node + linkType: hard + "@backstage/plugin-permission-common@workspace:^, @backstage/plugin-permission-common@workspace:plugins/permission-common": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-common@workspace:plugins/permission-common" @@ -6145,6 +6568,24 @@ __metadata: languageName: unknown linkType: soft +"@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.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 + peerDependencies: + "@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: 7c8c619d1c0465ac6f48a5f841127f75fb3689c6b7948777f093b11a7a65ac1308cb30ab46658591e8463ccd8dac7bdbc098c4922b9fa858b307f2113ef2acef + languageName: node + linkType: hard + "@backstage/plugin-permission-react@workspace:^, @backstage/plugin-permission-react@workspace:plugins/permission-react": version: 0.0.0-use.local resolution: "@backstage/plugin-permission-react@workspace:plugins/permission-react" @@ -6446,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 @@ -6622,6 +7064,16 @@ __metadata: languageName: unknown linkType: soft +"@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.7.0 + "@backstage/types": ^1.0.0 + checksum: c14e91b5e180c345d47077e678a5b1b9a86620350171f5cafe799e24a815c07952bf817e2b8759019227c07ea93dcf0608ba321afa7ead68c4281e01f6b3b3de + languageName: node + linkType: hard + "@backstage/plugin-search-common@workspace:^, @backstage/plugin-search-common@workspace:plugins/search-common": version: 0.0.0-use.local resolution: "@backstage/plugin-search-common@workspace:plugins/search-common" @@ -6863,6 +7315,30 @@ __metadata: languageName: unknown linkType: soft +"@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.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 + "@testing-library/jest-dom": ^5.10.1 + cross-fetch: ^3.1.5 + lodash: ^4.17.21 + qs: ^6.9.4 + react-use: ^17.2.4 + peerDependencies: + "@types/react": ^16.13.1 || ^17.0.0 + react: ^16.13.1 || ^17.0.0 + checksum: e7900ed037f34746e4156106d4579f0474f272fa46b5320ece2e5a7fec967aacc8fc90194802369460648a15b4a78103f68745261b666668cde6957b255ba166 + languageName: node + linkType: hard + "@backstage/plugin-stack-overflow@workspace:^, @backstage/plugin-stack-overflow@workspace:plugins/stack-overflow": version: 0.0.0-use.local resolution: "@backstage/plugin-stack-overflow@workspace:plugins/stack-overflow" @@ -6926,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 @@ -7441,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 @@ -7489,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: @@ -7498,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 @@ -7543,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 @@ -7560,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 @@ -7626,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 @@ -7654,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 @@ -7689,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 @@ -7712,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 @@ -7758,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 @@ -7808,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 @@ -7817,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 @@ -7872,13 +8357,13 @@ __metadata: linkType: hard "@codemirror/view@npm:^6.0.0": - version: 6.3.0 - resolution: "@codemirror/view@npm:6.3.0" + 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: 8fe39a1c47799b9bca8b815d7fd5c33bcde06cc8b98c26c87729ccca6d5afbc005671c8b00d8678209bcfc079f6894f4e61f26618d8b8133a79befa0828c789f + checksum: 57ed7d9d51907f1ea549a2a158872fb7affa6cdff72e29e214f8437b7142ea2a2431ac3c780cbc0187dfff7cb5fd1055a45a887ea9b8c2a09d8430c60ebe9083 languageName: node linkType: hard @@ -8176,14 +8661,14 @@ __metadata: linkType: hard "@google-cloud/firestore@npm:^6.0.0": - version: 6.4.0 - resolution: "@google-cloud/firestore@npm:6.4.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: 7503e56f85c20aad4fa01b5de0b97e5108fe44c87db3423cb53bcdb083e053c6458c7a690820dc7db9ead384ce28da66142c1781d8cc406d4e7056374b29d23d + checksum: 7bd79d7444cefd8ffe8f768c28dbd1f0b32628ff5b611b4bdaa9b415428436f5368a8e258a002948d084381762b64501a574828c51c5cea7c054cc604f93b6d6 languageName: node linkType: hard @@ -8270,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 @@ -8303,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 @@ -8315,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 @@ -8334,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 @@ -8379,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 @@ -8425,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 @@ -10084,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 @@ -11407,22 +11908,22 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks-types@npm:6.5.0": - version: 6.5.0 - resolution: "@octokit/webhooks-types@npm:6.5.0" - checksum: 747e4d277061ddced1b215d9fb3c3fb279e41069d90e703b4f9df6feb723769ba899c107f62aaa2db4cc7ac988de807e4455ab3b6049d8b853164e64c723a680 +"@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.3.0 - resolution: "@octokit/webhooks@npm:10.3.0" + 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.5.0 + "@octokit/webhooks-types": 6.6.0 aggregate-error: ^3.1.0 - checksum: 23c61cc139f1db145f74deab637efcb47f213b45ce85f107b9b9a80d96c9a566e5f763dfc9f3d75594e31ca3408c1b5ced48e735bb367886948135f3bb490210 + checksum: 2601528d67d9de25fa8d6b9c7b88a75de7887862f0f36569ce15592a35a02281f7f35870fcc3ff2ca2b9933905c676be3ada747efb88a1084cf3c2cdab1518b6 languageName: node linkType: hard @@ -11488,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 @@ -11507,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 @@ -11768,13 +12269,13 @@ __metadata: linkType: hard "@roadiehq/backstage-plugin-buildkite@npm:^2.0.8": - version: 2.0.9 - resolution: "@roadiehq/backstage-plugin-buildkite@npm:2.0.9" + version: 2.1.0 + resolution: "@roadiehq/backstage-plugin-buildkite@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.12.1 "@material-ui/icons": ^4.11.2 @@ -11787,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 @@ -11815,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 @@ -11844,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 @@ -11870,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 @@ -12148,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 @@ -12296,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.5": - version: 1.3.5 - resolution: "@swc/core-android-arm-eabi@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-android-arm64@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-darwin-arm64@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-darwin-x64@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-freebsd-x64@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-linux-x64-musl@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.5" +"@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.5": - version: 1.3.5 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.5" +"@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.5 - resolution: "@swc/core@npm:1.3.5" +"@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.5 - "@swc/core-android-arm64": 1.3.5 - "@swc/core-darwin-arm64": 1.3.5 - "@swc/core-darwin-x64": 1.3.5 - "@swc/core-freebsd-x64": 1.3.5 - "@swc/core-linux-arm-gnueabihf": 1.3.5 - "@swc/core-linux-arm64-gnu": 1.3.5 - "@swc/core-linux-arm64-musl": 1.3.5 - "@swc/core-linux-x64-gnu": 1.3.5 - "@swc/core-linux-x64-musl": 1.3.5 - "@swc/core-win32-arm64-msvc": 1.3.5 - "@swc/core-win32-ia32-msvc": 1.3.5 - "@swc/core-win32-x64-msvc": 1.3.5 + "@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 @@ -12468,7 +12971,7 @@ __metadata: optional: true bin: swcx: run_swcx.js - checksum: f0bcd0aa1d1a0b7d48ee5c416f8a384e19e4c0eed1e0be940d8893bf3981d98bf00f27d03bebf9cdedd34bf316d68c2db68e7bb7d1940d7b6062541e6e4cc738 + checksum: 761918f1bca5d494eaaafd49720717e3b3071df5bc6ef8b298a778ba4e4d20bc5a78c939a0b1c98623f9fe23e535a16a359179b72390cd1f5cbc891ea53c22fa languageName: node linkType: hard @@ -12516,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 @@ -12538,7 +13041,7 @@ __metadata: optional: true react-native: optional: true - checksum: 07f6ec6c44c6826986167d772d78051414f15f08c874fe38187e1175a527ec71f77025d1466651802b8cde991618acece668fc17d7dc62af837ad7fb02882fa3 + checksum: e538b585e4c2b4a8ff84c17509f4ce226e18e3e862553b6a9ca90e29c200db4ec7ab4f400f19ef4846a76c51babe0db120c54779054805fbecdcd29b369caf0e languageName: node linkType: hard @@ -13582,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" @@ -13638,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 @@ -13826,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" @@ -15148,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" @@ -16559,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" @@ -18303,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" @@ -18617,7 +19112,7 @@ __metadata: languageName: node linkType: hard -"core-js@npm:^2.4.0, core-js@npm:^2.5.0, core-js@npm:^2.6.10": +"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 @@ -18798,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 @@ -19211,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 @@ -19258,7 +19753,7 @@ __metadata: yauzl: ^2.10.0 bin: cypress: bin/cypress - checksum: c052690049980e7721e6fca563b724fde839d87d83c1478dfe26ce7d230992717c2c4028e7157bfb39ec274473e51929f49e5aab6a23c2b25cde2a439b1c3cf9 + checksum: 668a32534a527dba79754abbf98af176b80c539a12ec00058932ba2a19c794c7888323e59e738c30f726ad740c5451c31d02548a0cb7c1b1c8ad01c55a984ca2 languageName: node linkType: hard @@ -19271,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" @@ -19341,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" @@ -19364,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" @@ -19382,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" @@ -19410,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" @@ -19444,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" @@ -19471,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" @@ -19489,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" @@ -21851,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:^" @@ -21956,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:^" @@ -24747,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 @@ -24949,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: @@ -24972,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" @@ -25030,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 @@ -25043,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 @@ -26510,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 @@ -26930,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 @@ -26983,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 @@ -27445,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 @@ -28137,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" @@ -28158,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 @@ -28596,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" @@ -29447,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: @@ -31392,17 +31828,17 @@ __metadata: linkType: hard "passport-saml@npm:^3.1.2": - version: 3.2.3 - resolution: "passport-saml@npm:3.2.3" + 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: ccddfa37557ef39219b447285d9b87ad08309b3cd2537aab5df344fd3910d9b506eb36e4f24f2eec35047465f00fbea53b320674de9ae68ea18cbbce17de79f4 + checksum: 8e885af4d44c2d862b2ea0d051ab2a36bc6f9a70e62f90daf7ce4eefd126ac2ab4d5fc070693eba05f5e1be248af23fa018611bbfa7fad31708371f387f5dd77 languageName: node linkType: hard @@ -32656,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: @@ -33498,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" @@ -33526,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" @@ -33585,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" @@ -33615,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" @@ -33668,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: @@ -33916,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: @@ -33925,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 @@ -33991,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 @@ -34025,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" @@ -34046,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" @@ -34473,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 @@ -37511,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" @@ -37712,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 @@ -38531,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