diff --git a/.changeset/brave-books-pretend.md b/.changeset/brave-books-pretend.md new file mode 100644 index 0000000000..ee80aedb18 --- /dev/null +++ b/.changeset/brave-books-pretend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-playlist': patch +--- + +change color of delete icon to secondary in playlist table diff --git a/.changeset/cold-apricots-poke.md b/.changeset/cold-apricots-poke.md new file mode 100644 index 0000000000..b050c8082b --- /dev/null +++ b/.changeset/cold-apricots-poke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Changed the processing loop task pipeline implementation from recursive to iterative diff --git a/.changeset/early-trains.draw.md b/.changeset/early-trains.draw.md new file mode 100644 index 0000000000..30aad1a08d --- /dev/null +++ b/.changeset/early-trains.draw.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-radar': patch +--- + +Fixed the width of the tech radar dialog so that the content fits diff --git a/.changeset/four-masks-fry.md b/.changeset/four-masks-fry.md new file mode 100644 index 0000000000..94bd929016 --- /dev/null +++ b/.changeset/four-masks-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Update OpenAPI schema to relax the encoding validation of all request parameters. diff --git a/.changeset/gorgeous-coins-attack.md b/.changeset/gorgeous-coins-attack.md new file mode 100644 index 0000000000..d500459225 --- /dev/null +++ b/.changeset/gorgeous-coins-attack.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': patch +--- + +Added delete alert popup when user delete the entity diff --git a/.changeset/healthy-spiders-arrive.md b/.changeset/healthy-spiders-arrive.md new file mode 100644 index 0000000000..e8d110ccaa --- /dev/null +++ b/.changeset/healthy-spiders-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Ensure direct relations are shown for User entities while keeping support for aggregating closest parent group ownership diff --git a/.changeset/hip-seahorses-exist.md b/.changeset/hip-seahorses-exist.md new file mode 100644 index 0000000000..e8e247cc0a --- /dev/null +++ b/.changeset/hip-seahorses-exist.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Align zod dependency to align with other packages. diff --git a/.changeset/many-tips-attend.md b/.changeset/many-tips-attend.md new file mode 100644 index 0000000000..34b5898803 --- /dev/null +++ b/.changeset/many-tips-attend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': patch +--- + +Optionally initializes the search context with default settings for search queries only when the config is defined, rather than always overriding it. diff --git a/.changeset/nasty-cherries-exercise.md b/.changeset/nasty-cherries-exercise.md new file mode 100644 index 0000000000..bb4386e978 --- /dev/null +++ b/.changeset/nasty-cherries-exercise.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +fix "undefined" kind for custom resources diff --git a/.changeset/nasty-snails-complain.md b/.changeset/nasty-snails-complain.md new file mode 100644 index 0000000000..d7d2cac319 --- /dev/null +++ b/.changeset/nasty-snails-complain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes': patch +--- + +fix logs dialog min height diff --git a/.changeset/polite-forks-grin.md b/.changeset/polite-forks-grin.md new file mode 100644 index 0000000000..8f6570ccbc --- /dev/null +++ b/.changeset/polite-forks-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fixed validation of the `fullTextFilterFields` query parameter. diff --git a/.changeset/real-chairs-arrive.md b/.changeset/real-chairs-arrive.md new file mode 100644 index 0000000000..09551b44f9 --- /dev/null +++ b/.changeset/real-chairs-arrive.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Replace `passport-saml` with `@node-saml/passport-saml` (since 4.0.0). diff --git a/.changeset/repo-tools-boo-descriptions-two-electric-boogaloo.md b/.changeset/repo-tools-boo-descriptions-two-electric-boogaloo.md new file mode 100644 index 0000000000..8cb6d49153 --- /dev/null +++ b/.changeset/repo-tools-boo-descriptions-two-electric-boogaloo.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Fixed a bug with the `generate-catalog-info` command that could cause `metadata.description` values to be overwritten by `package.json` description values only because unrelated attributes were being changed. diff --git a/.changeset/rude-dolphins-reply.md b/.changeset/rude-dolphins-reply.md new file mode 100644 index 0000000000..89545cde1f --- /dev/null +++ b/.changeset/rude-dolphins-reply.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs-module-addons-contrib': minor +--- + +Enable zoom icon for techdocs images inside lightbox diff --git a/.changeset/silly-emus-remain.md b/.changeset/silly-emus-remain.md new file mode 100644 index 0000000000..4fe605080a --- /dev/null +++ b/.changeset/silly-emus-remain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Add examples for `github:actions:dispatch` scaffolder actions. diff --git a/.changeset/tall-peas-visit.md b/.changeset/tall-peas-visit.md new file mode 100644 index 0000000000..3e3ca76e46 --- /dev/null +++ b/.changeset/tall-peas-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Fix issue with `catalogFileName` not being a required property for `/analyze-location` diff --git a/.changeset/twelve-elephants-pay.md b/.changeset/twelve-elephants-pay.md new file mode 100644 index 0000000000..08fb983b6f --- /dev/null +++ b/.changeset/twelve-elephants-pay.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs-backend': minor +'@backstage/plugin-techdocs-node': minor +'@techdocs/cli': minor +--- + +Add optional config and cli option for techdocs to specify default mkdocs plugins. diff --git a/.changeset/witty-ants-cover.md b/.changeset/witty-ants-cover.md new file mode 100644 index 0000000000..7f499a88ae --- /dev/null +++ b/.changeset/witty-ants-cover.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-home': patch +--- + +Allow specifying static widgets to custom home page diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f58291912d..922f7588a5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -58,8 +58,8 @@ yarn.lock @backstage/maintainers @backst /plugins/home-* @backstage/discoverability-maintainers /plugins/kafka @backstage/maintainers @backstage/reviewers @andrewthauer /plugins/kafka-backend @backstage/maintainers @backstage/reviewers @andrewthauer -/plugins/kubernetes @backstage/maintainers @backstage/reviewers @backstage/kubernetes-maintainers -/plugins/kubernetes-* @backstage/maintainers @backstage/reviewers @backstage/kubernetes-maintainers +/plugins/kubernetes @backstage/kubernetes-maintainers +/plugins/kubernetes-* @backstage/kubernetes-maintainers /plugins/linguist @backstage/maintainers @backstage/reviewers @awanlin /plugins/linguist-backend @backstage/maintainers @backstage/reviewers @awanlin /plugins/linguist-common @backstage/maintainers @backstage/reviewers @awanlin diff --git a/.github/workflows/deploy_docker-image.yml b/.github/workflows/deploy_docker-image.yml index 6fdfd5d5a4..231b81598e 100644 --- a/.github/workflows/deploy_docker-image.yml +++ b/.github/workflows/deploy_docker-image.yml @@ -10,7 +10,7 @@ jobs: strategy: fail-fast: false matrix: - node-version: [16.x] + node-version: [18.x] steps: - name: checkout diff --git a/OWNERS.md b/OWNERS.md index 85f7fe545a..6c26078f8c 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -94,11 +94,13 @@ Scope: The TechDocs plugin and related tooling | ------------------------ | ------------ | ------- | ------------------------------------------------- | ------------------- | | Alex Lorenzi | Spotify | ProTean | [AlexLorenzi](https://github.com/AlexLorenzi) | `alexlorenzi` | | Bond Yan | Spotify | ProTean | [byan1197](https://github.com/byan1197) | `bondify` | +| Daniel Lochrie | Spotify | ProTean | [dlochrie](https://github.com/dlochrie) | - | | Jackson Chen | Spotify | ProTean | [PeaWarrior](https://github.com/PeaWarrior) | `jacksonc#3322` | | John Philip | Spotify | ProTean | [johnphilip283](https://github.com/johnphilip283) | `john_philip#2399` | | Morgan Bentell | Spotify | ProTean | [agentbellnorm](http://github.com/agentbellnorm) | `morganbentell` | | Raghunandan Balachandran | Spotify | BUX | [soapraj](http://github.com/soapraj) | `raghunandanb#1114` | | Sydney Achinger | Spotify | ProTean | [squid-ney](https://github.com/squid-ney) | - | +| Vojta Burian | Spotify | ProTean | [sankaritan](https://github.com/sankaritan) | `vojtabu` | ## Incubating Project Areas diff --git a/contrib/.devcontainer/devcontainer.json b/contrib/.devcontainer/devcontainer.json index b312102784..14e7d7f0ae 100644 --- a/contrib/.devcontainer/devcontainer.json +++ b/contrib/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "features": { "ghcr.io/devcontainers/features/common-utils:1": {}, "ghcr.io/devcontainers/features/docker-from-docker:1": {}, - "ghcr.io/devcontainers-contrib/features/mkdocs:1": {} + "ghcr.io/devcontainers-contrib/features/mkdocs:2": {} }, // Use 'forwardPorts' to make a list of ports inside the container available locally. diff --git a/contrib/.devcontainer/postCreate.sh b/contrib/.devcontainer/postCreate.sh index f0631a4af2..88f403de5e 100755 --- a/contrib/.devcontainer/postCreate.sh +++ b/contrib/.devcontainer/postCreate.sh @@ -1,3 +1,6 @@ #!/bin/bash yarn install -pip install mkdocs-techdocs-core \ No newline at end of file +export VIRTUAL_ENV=$HOME/venv +python3 -m venv $VIRTUAL_ENV +export PATH="$VIRTUAL_ENV/bin:$PATH" +python3 -m pip install mkdocs-techdocs-core \ No newline at end of file diff --git a/docs/backend-system/architecture/03-services.md b/docs/backend-system/architecture/03-services.md index 8a86d90efb..e67d467453 100644 --- a/docs/backend-system/architecture/03-services.md +++ b/docs/backend-system/architecture/03-services.md @@ -40,7 +40,7 @@ export const fooServiceRef = createServiceRef({ The `fooServiceRef` that we create above should be exported, and can then be used to declare a dependency on the `FooService` interface and receive an implementation of it at runtime. -When creating a service reference you need to give it an ID. This ID needs to be globally unique, and should generally be of the format `'.'`. For more naming patters surrounding services, see the [naming patterns](./07-naming-patterns.md#services) page. +When creating a service reference you need to give it an ID. This ID needs to be globally unique, and should generally be of the format `'.'`. For more naming patterns surrounding services, see the [naming patterns](./07-naming-patterns.md#services) page. A note on naming: the frontend and backend systems intentionally use the separate names "APIs" and "Services" for concepts that are quite similar. This is to avoid confusion between the two, both in documentation and discussion, but also in code. While the two systems are quite similar, they are not identical, and they can't be used interchangeably. diff --git a/docs/features/software-catalog/configuration.md b/docs/features/software-catalog/configuration.md index 5abd266099..2f5edc3696 100644 --- a/docs/features/software-catalog/configuration.md +++ b/docs/features/software-catalog/configuration.md @@ -123,3 +123,14 @@ source that should be mirrored into Backstage. To make Backstage a mirror of this remote source, users cannot also register new entities with e.g. the [catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) plugin. + +## Clean up orphaned entities + +In short entities can become orphaned through multiple means, such as when a catalog-info YAML file is moved from one place to another in the version control system without updating the registration in the catalog. For safety reasons the default behavior is to just tag the orphaned entities, and keep them around. You can read more about orphaned entities [here](life-of-an-entity.md#orphaning). + +However, if you do with to automatically remove the orphaned entities, you can use the following configuration, and everything with an orphaned entity tag will be eventually deleted. + +``` +catalog: + orphanStrategy: delete +``` diff --git a/docs/features/software-catalog/life-of-an-entity.md b/docs/features/software-catalog/life-of-an-entity.md index 618923289e..583be7df20 100644 --- a/docs/features/software-catalog/life-of-an-entity.md +++ b/docs/features/software-catalog/life-of-an-entity.md @@ -222,7 +222,8 @@ either, it becomes _orphaned_. The end result is as follows: - The stitching process injects a `backstage.io/orphan: 'true'` annotation on the child entity. - The child entity is _not_ removed from the catalog, but stays around until - explicitly deleted via the catalog API, or "reclaimed" by the original parent + explicitly deleted via the catalog API, implicitly if `orphanStrategy: delete` + configuration is set, or until it is "reclaimed" by the original parent or another parent starting to reference it. - The catalog page in Backstage for the child entity detects the new annotation and informs users about the orphan status. @@ -259,9 +260,13 @@ entities without explicit owner consent. The catalog therefore takes the stance that entities that often were added by direct user action should also be deleted only by direct user action. -It is possible to use the catalog API to build automated "reaper" systems that -finally delete entities that are orphaned. This is however not something that's -provided out of the box. +However, if you want to delete orphaned entities automatically anyway, you can +enable the automated clean up with the following app-config option. + +``` +catalog: + orphanStrategy: delete +``` ## Implicit Deletion diff --git a/docs/features/techdocs/addons.md b/docs/features/techdocs/addons.md index bd709ee15f..a41995c260 100644 --- a/docs/features/techdocs/addons.md +++ b/docs/features/techdocs/addons.md @@ -122,12 +122,12 @@ page header, TechDocs Addons whose location is `Header` will not be rendered. Addons can, in principle, be provided by any plugin! To make it easier to discover available Addons, we've compiled a list of them here: -| Addon | Package/Plugin | Description | -| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.expandablenavigation) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. | -| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. | -| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. | -| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.lightbox) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. | +| Addon | Package/Plugin | Description | +| ---------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.expandablenavigation) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to expand or collapse the entire TechDocs main navigation, and keeps the user's preferred state between documentation sites. | +| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.reportissue) | `@backstage/plugin-techdocs-module-addons-contrib` | Allows TechDocs users to select a portion of text on a TechDocs page and open an issue against the repository that contains the documentation, populating the issue description with the selected text according to a configurable template. | +| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.textsize) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to customize text size on documentation pages, they can select how much they want to increase or decrease the font size via slider or buttons. The default value for font size is 100% and this setting is kept in the browser's local storage whenever it is changed. | +| [``](https://backstage.io/docs/reference/plugin-techdocs-module-addons-contrib.lightbox) | `@backstage/plugin-techdocs-module-addons-contrib` | This TechDocs addon allows users to open images in a light-box on documentation pages, they can navigate between images if there are several on one page. The image size of the light-box image is the same as the image size on the document page. When clicking on the zoom icon it zooms the image to fit in the screen (similar to `background-size: contain`). | Got an Addon to contribute? Feel free to add a row above! diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md index 76589de5a8..87eb67d04a 100644 --- a/docs/features/techdocs/cli.md +++ b/docs/features/techdocs/cli.md @@ -141,6 +141,7 @@ Options: if not found. --etag A unique identifier for the prepared tree e.g. commit SHA. If provided it will be stored in techdocs_metadata.json. + --defaultPlugin Plugins which should be added automatically to the mkdocs.yaml file. (default: []) --omitTechdocsCoreMkdocsPlugin An option to disable automatic addition of techdocs-core plugin to the mkdocs.yaml files. Defaults to false, which means that the techdocs-core plugin is always added to the mkdocs file. --legacyCopyReadmeMdToIndexMd Attempt to ensure an index.md exists falling back to using /README.md or README.md diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md index 49ee9d72c2..f567bfbac9 100644 --- a/docs/features/techdocs/configuration.md +++ b/docs/features/techdocs/configuration.md @@ -49,6 +49,15 @@ techdocs: # will be broken in these scenarios. legacyCopyReadmeMdToIndexMd: false + # (Optional) Configures the default plugins which should be added + # automatically to every mkdocs.yaml file. This simplifies the usage as + # e.g. styling plugins can be added once for all. + # Make sure that the defined plugins are installed locally / in the Docker + # image. + # By default, only the techdocs-core plugin will be added (except if + # omitTechdocsCorePlugin: true). + defaultPlugins: ['techdocs-core'] + # techdocs.builder can be either 'local' or 'external'. # Using the default build strategy, if builder is set to 'local' and you open a TechDocs page, # techdocs-backend will try to generate the docs, publish to storage and show the generated docs afterwards. diff --git a/docs/publishing.md b/docs/publishing.md index b2224e76b7..be94406a40 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -13,6 +13,34 @@ npm. Releases are handled by changesets and trigger whenever the "Version Packages" PR is merged. This is typically done every Tuesday around noon CET. +## Next Line Release Process + +- PR Checks: Ensure there are no outstanding PRs pending to be merged for this version. If there are any, reach out to maintainers and relevant owners of the affected code reminding them of the deadline for the release. +- [optional] Lock main branch + - Lock the main branch to prevent any new merges. + - Note: Admin rights are required to lock the branch. If you lack the necessary permissions, contact a core maintainer to perform this action on your behalf. +- Check [`Version Packages (next)` Pull Request](https://github.com/backstage/backstage/pulls?q=is%3Aopen+is%3Apr+in%3Atitle+%22Version+Packages+%28next%29%22) + - Verify the version we are shipping is correct, by looking at the version packages PR title. It should be "Version Packages (next)" + - Check [`.changeset/pre.json`](https://github.com/backstage/backstage/blob/master/.changeset/pre.json) if the `mode` is set to `pre`. If you encounter `mode: "exit"` or if it's not defined, it indicates a mainline release. +- Verify that there are no active/unfinished `sync_version-packages` actions running (https://github.com/backstage/backstage/actions/workflows/sync_version-packages.yml) + - Locking the main branch will prevent new ones to be created, but be sure to check for running actions again after unlocking, since it may cause pending auto-merge PRs to be merged. +- Check [`Version Packages (next)` Pull Request](https://github.com/backstage/backstage/pulls?q=is%3Aopen+is%3Apr+in%3Atitle+%22Version+Packages+%28next%29%22) for sufficient approval to be merged + - Check generated `changelog` in the changed files of the pull request under `docs/releases` to see if there are any unexpected major bumps e.g. by searching the file + - Review & approve changes + - Reach out to core maintainer to merge the pull request + - Heads-up: The microsite building step can be skipped as long as the `prettier` task passes & everything else looks green + +Merging the `Version Packages (next)` Pull Request will trigger the deployment workflows. Follow along the [deployment workflow](https://github.com/backstage/backstage/actions/workflows/deploy_packages.yml). If you notice flakiness (e.g. if the build is flaky or if the release step runs into an error with releasing to npm) just restart the workflow. + +Congratulations on the release! There should be now a post in the [`#announcements` channel](https://discord.com/channels/687207715902193673/705123584468582400) in Discord linking to the release tag - check if links & tag look as expected. Finally unlock the main branch again. Merging PRs in master directly after release should be done with caution as it potential complicates fixing issues introduced in the release. + +## Switching Release Modes + +- To enter pre-release mode: `yarn changeset pre enter next` & create PR + merge changes +- To exit pre-release mode: `yarn changeset pre exit` & create PR + merge changes + - Has to be done before the mainline release +- It's not time critical; Affects the next release happening + ## Emergency Release Process **This emergency release process is intended only for the Backstage diff --git a/microsite/data/plugins/artifactory.yaml b/microsite/data/plugins/artifactory.yaml new file mode 100644 index 0000000000..119e09eb8e --- /dev/null +++ b/microsite/data/plugins/artifactory.yaml @@ -0,0 +1,12 @@ +--- +title: Artifacts Plugin for JFrog Artifactory +author: StageCentral +authorUrl: https://stagecentral.io +category: Artifacts +description: | + Connect Backstage entities to software artifacts stored in JFrog Artifactory. + Display information about artifacts, such as their version, size, and download links. +documentation: https://github.com/StageCentral/backstage-artifactory-plugin/tree/main#readme +iconUrl: https://raw.githubusercontent.com/StageCentral/backstage-artifactory-plugin/main/logo/logo.png +npmPackageName: '@stagecentral/plugin-artifactory' +addedDate: '2023-08-23' diff --git a/microsite/data/plugins/flux.yaml b/microsite/data/plugins/flux.yaml new file mode 100644 index 0000000000..91156f7ed3 --- /dev/null +++ b/microsite/data/plugins/flux.yaml @@ -0,0 +1,10 @@ +--- +title: Flux Plugins +author: Weaveworks +authorUrl: https://weave.works +category: GitOps +description: View the status of fluxcd.io resources +documentation: https://github.com/weaveworks/weaveworks-backstage/tree/main/plugins/backstage-plugin-flux#readme +iconUrl: https://raw.githubusercontent.com/fluxcd/website/main/assets/icons/logo.svg +npmPackageName: '@weaveworksoss/backstage-plugin-flux' +addedDate: '2023-08-23' diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 34bc7e6e12..b48caf6669 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -5028,14 +5028,14 @@ __metadata: linkType: hard "docusaurus-plugin-sass@npm:^0.2.3": - version: 0.2.4 - resolution: "docusaurus-plugin-sass@npm:0.2.4" + version: 0.2.5 + resolution: "docusaurus-plugin-sass@npm:0.2.5" dependencies: sass-loader: ^10.1.1 peerDependencies: - "@docusaurus/core": ^2.0.0-beta + "@docusaurus/core": ^2.0.0-beta || ^3.0.0-alpha sass: ^1.30.0 - checksum: 10e0c9f519dd7ef4666b1fb82d120738feab03ea451c18bab7f785627887f65b8cb72457065a9b2a2664af1490c20b38df9adf525c1b58ce3e324d2b50412734 + checksum: db17a8c87a344330717962fe195c94089e32281dc7f79b66bf99587254ebad71353b8588c5b74feb1c2841c6183e59b20259f5fb1a5c74026903557cbb113a68 languageName: node linkType: hard diff --git a/packages/app-defaults/catalog-info.yaml b/packages/app-defaults/catalog-info.yaml new file mode 100644 index 0000000000..e8450be1ec --- /dev/null +++ b/packages/app-defaults/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-app-defaults + title: '@backstage/app-defaults' + description: Provides the default wiring of a Backstage App +spec: + lifecycle: production + type: backstage-web-library + owner: maintainers diff --git a/packages/app-next/catalog-info.yaml b/packages/app-next/catalog-info.yaml new file mode 100644 index 0000000000..85e3f0d70b --- /dev/null +++ b/packages/app-next/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example-app-next + title: example-app-next +spec: + lifecycle: experimental + type: backstage-frontend + owner: maintainers diff --git a/packages/app-next/src/examples/graphiqlPlugin.tsx b/packages/app-next/src/examples/graphiqlPlugin.tsx index 87f2bb242c..c40b78fc97 100644 --- a/packages/app-next/src/examples/graphiqlPlugin.tsx +++ b/packages/app-next/src/examples/graphiqlPlugin.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ -import React from 'react'; import { createExtension, createPlugin, coreExtensionData, + createSchemaFromZod, } from '@backstage/frontend-plugin-api'; import { Router as GraphiQLPage } from '@backstage/plugin-graphiql'; @@ -27,12 +27,12 @@ export const GraphiqlPageExtension = createExtension({ component: coreExtensionData.reactComponent, path: coreExtensionData.routePath, }, + configSchema: createSchemaFromZod(z => + z.object({ path: z.string().default('/graphiql') }), + ), factory({ bind, config }) { - bind.component(() => { - return ; - }); - // TODO: In need of schemas and type safety - bind.path((config as { path: string }).path); + bind.component(GraphiQLPage); + bind.path(config.path); }, }); @@ -43,7 +43,6 @@ export const graphiqlPlugin = createPlugin({ id: 'graphiql.page', at: 'core.router/routes', extension: GraphiqlPageExtension, - config: { path: '/graphiql' }, }, ], }); diff --git a/packages/app/catalog-info.yaml b/packages/app/catalog-info.yaml new file mode 100644 index 0000000000..e0808307cc --- /dev/null +++ b/packages/app/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example-app + title: example-app +spec: + lifecycle: experimental + type: backstage-frontend + owner: maintainers diff --git a/packages/app/src/components/home/HomePage.tsx b/packages/app/src/components/home/HomePage.tsx index aa3739728e..a8ddc0e0e2 100644 --- a/packages/app/src/components/home/HomePage.tsx +++ b/packages/app/src/components/home/HomePage.tsx @@ -15,14 +15,14 @@ */ import { - HomePageRandomJoke, - WelcomeTitle, - HeaderWorldClock, ClockConfig, - HomePageStarredEntities, CustomHomepageGrid, - HomePageToolkit, + HeaderWorldClock, HomePageCompanyLogo, + HomePageRandomJoke, + HomePageStarredEntities, + HomePageToolkit, + WelcomeTitle, } from '@backstage/plugin-home'; import { Content, Header, Page } from '@backstage/core-components'; import { HomePageSearchBar } from '@backstage/plugin-search'; @@ -64,6 +64,9 @@ const defaultConfig = [ y: 0, width: 12, height: 1, + movable: false, + resizable: false, + deletable: false, }, { component: 'WelcomeTitle', @@ -77,7 +80,7 @@ const defaultConfig = [ x: 0, y: 2, width: 12, - height: 1, + height: 2, }, ]; diff --git a/packages/backend-app-api/catalog-info.yaml b/packages/backend-app-api/catalog-info.yaml new file mode 100644 index 0000000000..051e7ab5c2 --- /dev/null +++ b/packages/backend-app-api/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-app-api + title: '@backstage/backend-app-api' + description: Core API used by Backstage backend apps +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend-common/catalog-info.yaml b/packages/backend-common/catalog-info.yaml new file mode 100644 index 0000000000..ff38a4df75 --- /dev/null +++ b/packages/backend-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-common + title: '@backstage/backend-common' + description: Common functionality library for Backstage backends +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend-defaults/catalog-info.yaml b/packages/backend-defaults/catalog-info.yaml new file mode 100644 index 0000000000..d3d00cc3d2 --- /dev/null +++ b/packages/backend-defaults/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-defaults + title: '@backstage/backend-defaults' + description: Backend defaults used by Backstage backend apps +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend-dev-utils/catalog-info.yaml b/packages/backend-dev-utils/catalog-info.yaml new file mode 100644 index 0000000000..ecbcb4e0bb --- /dev/null +++ b/packages/backend-dev-utils/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-dev-utils + title: '@backstage/backend-dev-utils' +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend-next/catalog-info.yaml b/packages/backend-next/catalog-info.yaml new file mode 100644 index 0000000000..d297365e4b --- /dev/null +++ b/packages/backend-next/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example-backend-next + title: example-backend-next +spec: + lifecycle: experimental + type: backstage-backend + owner: maintainers diff --git a/packages/backend-openapi-utils/catalog-info.yaml b/packages/backend-openapi-utils/catalog-info.yaml new file mode 100644 index 0000000000..ab0a4be883 --- /dev/null +++ b/packages/backend-openapi-utils/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-openapi-utils + title: '@backstage/backend-openapi-utils' + description: OpenAPI typescript support. +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend-plugin-api/catalog-info.yaml b/packages/backend-plugin-api/catalog-info.yaml new file mode 100644 index 0000000000..35ca9850fa --- /dev/null +++ b/packages/backend-plugin-api/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-plugin-api + title: '@backstage/backend-plugin-api' + description: Core API used by Backstage backend plugins +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend-tasks/catalog-info.yaml b/packages/backend-tasks/catalog-info.yaml new file mode 100644 index 0000000000..f9b81066e6 --- /dev/null +++ b/packages/backend-tasks/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-tasks + title: '@backstage/backend-tasks' + description: Common distributed task management library for Backstage backends +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend-test-utils/catalog-info.yaml b/packages/backend-test-utils/catalog-info.yaml new file mode 100644 index 0000000000..396f190ef4 --- /dev/null +++ b/packages/backend-test-utils/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-backend-test-utils + title: '@backstage/backend-test-utils' + description: Test helpers library for Backstage backends +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/backend/catalog-info.yaml b/packages/backend/catalog-info.yaml new file mode 100644 index 0000000000..0e95516a41 --- /dev/null +++ b/packages/backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example-backend + title: example-backend +spec: + lifecycle: experimental + type: backstage-backend + owner: maintainers diff --git a/packages/catalog-client/catalog-info.yaml b/packages/catalog-client/catalog-info.yaml new file mode 100644 index 0000000000..91a4cb5504 --- /dev/null +++ b/packages/catalog-client/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-catalog-client + title: '@backstage/catalog-client' + description: An isomorphic client for the catalog backend +spec: + lifecycle: production + type: backstage-common-library + owner: maintainers diff --git a/packages/catalog-model/catalog-info.yaml b/packages/catalog-model/catalog-info.yaml new file mode 100644 index 0000000000..7b2bce99b9 --- /dev/null +++ b/packages/catalog-model/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-catalog-model + title: '@backstage/catalog-model' + description: Types and validators that help describe the model of a Backstage Catalog +spec: + lifecycle: production + type: backstage-common-library + owner: maintainers diff --git a/packages/cli-common/catalog-info.yaml b/packages/cli-common/catalog-info.yaml new file mode 100644 index 0000000000..bc1f589fc1 --- /dev/null +++ b/packages/cli-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-cli-common + title: '@backstage/cli-common' + description: Common functionality used by cli, backend, and create-app +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/cli-node/catalog-info.yaml b/packages/cli-node/catalog-info.yaml new file mode 100644 index 0000000000..3497007bfa --- /dev/null +++ b/packages/cli-node/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-cli-node + title: '@backstage/cli-node' + description: Node.js library for Backstage CLIs +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/cli/catalog-info.yaml b/packages/cli/catalog-info.yaml new file mode 100644 index 0000000000..212ba472d4 --- /dev/null +++ b/packages/cli/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-cli + title: '@backstage/cli' + description: CLI for developing Backstage plugins and apps +spec: + lifecycle: experimental + type: backstage-cli + owner: maintainers diff --git a/packages/codemods/catalog-info.yaml b/packages/codemods/catalog-info.yaml new file mode 100644 index 0000000000..8e5739bd1a --- /dev/null +++ b/packages/codemods/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-codemods + title: '@backstage/codemods' + description: A collection of codemods for Backstage projects +spec: + lifecycle: experimental + type: backstage-cli + owner: maintainers diff --git a/packages/config-loader/catalog-info.yaml b/packages/config-loader/catalog-info.yaml new file mode 100644 index 0000000000..4dfe9c1e59 --- /dev/null +++ b/packages/config-loader/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-config-loader + title: '@backstage/config-loader' + description: Config loading functionality used by Backstage backend, and CLI +spec: + lifecycle: production + type: backstage-node-library + owner: maintainers diff --git a/packages/config/catalog-info.yaml b/packages/config/catalog-info.yaml new file mode 100644 index 0000000000..c077d9d47b --- /dev/null +++ b/packages/config/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-config + title: '@backstage/config' + description: Config API used by Backstage core, backend, and CLI +spec: + lifecycle: production + type: backstage-common-library + owner: maintainers diff --git a/packages/core-app-api/catalog-info.yaml b/packages/core-app-api/catalog-info.yaml new file mode 100644 index 0000000000..628c5a36d6 --- /dev/null +++ b/packages/core-app-api/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-core-app-api + title: '@backstage/core-app-api' + description: Core app API used by Backstage apps +spec: + lifecycle: production + type: backstage-web-library + owner: maintainers diff --git a/packages/core-components/catalog-info.yaml b/packages/core-components/catalog-info.yaml new file mode 100644 index 0000000000..ba189b8c39 --- /dev/null +++ b/packages/core-components/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-core-components + title: '@backstage/core-components' + description: Core components used by Backstage plugins and apps +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/packages/core-plugin-api/catalog-info.yaml b/packages/core-plugin-api/catalog-info.yaml new file mode 100644 index 0000000000..2f0fa2d4eb --- /dev/null +++ b/packages/core-plugin-api/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-core-plugin-api + title: '@backstage/core-plugin-api' + description: Core API used by Backstage plugins +spec: + lifecycle: production + type: backstage-web-library + owner: maintainers diff --git a/packages/create-app/catalog-info.yaml b/packages/create-app/catalog-info.yaml new file mode 100644 index 0000000000..7da321f091 --- /dev/null +++ b/packages/create-app/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-create-app + title: '@backstage/create-app' + description: A CLI that helps you create your own Backstage app +spec: + lifecycle: experimental + type: backstage-cli + owner: maintainers diff --git a/packages/dev-utils/catalog-info.yaml b/packages/dev-utils/catalog-info.yaml new file mode 100644 index 0000000000..c67eecec83 --- /dev/null +++ b/packages/dev-utils/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-dev-utils + title: '@backstage/dev-utils' + description: Utilities for developing Backstage plugins. +spec: + lifecycle: production + type: backstage-web-library + owner: maintainers diff --git a/packages/e2e-test/catalog-info.yaml b/packages/e2e-test/catalog-info.yaml new file mode 100644 index 0000000000..55bce03e8b --- /dev/null +++ b/packages/e2e-test/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: e2e-test + title: e2e-test + description: E2E test for verifying Backstage packages +spec: + lifecycle: experimental + type: backstage-cli + owner: maintainers diff --git a/packages/errors/catalog-info.yaml b/packages/errors/catalog-info.yaml new file mode 100644 index 0000000000..ee97045b20 --- /dev/null +++ b/packages/errors/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-errors + title: '@backstage/errors' + description: Common utilities for error handling within Backstage +spec: + lifecycle: production + type: backstage-common-library + owner: maintainers diff --git a/packages/frontend-app-api/README.md b/packages/frontend-app-api/README.md index 5cc5073f61..8b2fe5dc23 100644 --- a/packages/frontend-app-api/README.md +++ b/packages/frontend-app-api/README.md @@ -1,12 +1,9 @@ # @backstage/frontend-app-api -_This package was created through the Backstage CLI_. +**This package is EXPERIMENTAL, we recommend against using it for production deployments** -## Installation +This package provides the core API used by Backstage frontend apps. It implements the design outlined in [RFC: Frontend System Evolution](https://github.com/backstage/backstage/issues/18372). -Install the package via Yarn: +## Documentation -```sh -cd # if within a monorepo -yarn add @backstage/frontend-app-api -``` +- [Backstage Documentation](https://backstage.io/docs) diff --git a/packages/frontend-app-api/catalog-info.yaml b/packages/frontend-app-api/catalog-info.yaml new file mode 100644 index 0000000000..332650f083 --- /dev/null +++ b/packages/frontend-app-api/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-frontend-app-api + title: '@backstage/frontend-app-api' +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index c085c6dba6..65c8a65e71 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -35,6 +35,7 @@ "@backstage/config": "workspace:^", "@backstage/frontend-plugin-api": "workspace:^", "@backstage/plugin-graphiql": "workspace:^", + "@backstage/types": "workspace:^", "lodash": "^4.17.21" }, "peerDependencies": { diff --git a/packages/frontend-app-api/src/createApp.tsx b/packages/frontend-app-api/src/createApp.tsx index 46cd0a7aed..031ad031bd 100644 --- a/packages/frontend-app-api/src/createApp.tsx +++ b/packages/frontend-app-api/src/createApp.tsx @@ -15,9 +15,9 @@ */ import React from 'react'; -import { Config, ConfigReader } from '@backstage/config'; +import { ConfigReader } from '@backstage/config'; import { - ExtensionInstanceConfig, + ExtensionInstanceParameters, BackstagePlugin, coreExtensionData, } from '@backstage/frontend-plugin-api'; @@ -26,65 +26,10 @@ import { createExtensionInstance, ExtensionInstance, } from './createExtensionInstance'; - -// Since we'll never merge arrays in config the config reader context -// isn't too much of a help. Fall back to manual config reading logic -// as the Config interface makes it quite hard for us otherwise. -function readAppExtensionConfigs( - rootConfig: Config, -): Partial[] { - const arr = rootConfig.getOptional('app.extensions'); - if (!Array.isArray(arr)) { - if (arr === undefined) { - return []; - } - // This will throw, and show which part of config had the wrong type - rootConfig.getConfigArray('app.extensions'); - return []; - } - - return arr.map((value, index) => { - function errorMsg(msg: string, key?: string, prop?: string) { - return `Invalid extension configuration at app.extensions[${index}]${ - key ? `[${key}]` : '' - }${prop ? `.${prop}` : ''}, ${msg}`; - } - - if (typeof value === 'string') { - return { id: value }; - } else if ( - typeof value !== 'object' || - value === null || - Array.isArray(value) - ) { - throw new Error(errorMsg('must be a string or an object')); - } - - const keys = Object.keys(value); - if (keys.length !== 1) { - const joinedKeys = `"${keys.join('", "')}"`; - throw new Error(errorMsg(`must have exactly one key, got ${joinedKeys}`)); - } - - const key = keys[0]; - const obj = value[key]; - if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) { - throw new Error(errorMsg('must be an object', key)); - } - const at = obj.at; - if (at !== undefined && typeof at !== 'string') { - throw new Error(errorMsg('must be a string', key, 'at')); - } - const extension = obj.extension; - if (extension !== undefined && typeof extension !== 'string') { - throw new Error(errorMsg('must be a string', key, 'extension')); - } - if (extension) { - throw new Error('TODO: implement extension resolution'); - } - return { id: key, at, config: obj.config /* validate later */ }; - }); -} +import { + mergeExtensionParameters, + readAppExtensionParameters, +} from './wiring/parameters'; /** @public */ export function createApp(options: { plugins: BackstagePlugin[] }): { @@ -92,10 +37,7 @@ export function createApp(options: { plugins: BackstagePlugin[] }): { } { const appConfig = ConfigReader.fromConfigs(process.env.APP_CONFIG as any); - // pull in default extension instance from discovered packages - // apply config to adjust default extension instances and add more - const extensionInstanceConfigs = [ - ...options.plugins.flatMap(plugin => plugin.defaultExtensionInstances), + const builtinExtensionInstanceParams = [ { id: 'core.router', at: 'root/default', @@ -104,40 +46,26 @@ export function createApp(options: { plugins: BackstagePlugin[] }): { }, ]; - const appExtensionConfigs = readAppExtensionConfigs(appConfig); - for (const appExtensionConfig of appExtensionConfigs) { - const existingConfig = extensionInstanceConfigs.find( - e => e.id === appExtensionConfig.id, - ); - if (existingConfig) { - if (appExtensionConfig.at) { - existingConfig.at = appExtensionConfig.at; - } - if (appExtensionConfig.extension) { - // TODO: do we want to reset config here? it might be completely - // unrelated to the previous one - existingConfig.extension = appExtensionConfig.extension; - } - if (appExtensionConfig.config) { - // TODO: merge config? - existingConfig.config = appExtensionConfig.config; - } - } else if (appExtensionConfig.id) { - const { id, at, extension, config } = appExtensionConfig; - if (!at || !extension) { - throw new Error(`Extension ${appExtensionConfig.id} is incomplete`); - } - extensionInstanceConfigs.push({ id, at, extension, config }); - } - } + // pull in default extension instance from discovered packages + // apply config to adjust default extension instances and add more + const extensionInstanceParams = mergeExtensionParameters( + [ + ...options.plugins.flatMap(plugin => plugin.defaultExtensionInstances), + ...builtinExtensionInstanceParams, + ], + readAppExtensionParameters(appConfig), + ); + + // TODO: validate the config of all extension instances + // We do it at this point to ensure that merging (if any) of config has already happened // Create attachment map so that we can look attachments up during instance creation const attachmentMap = new Map< string, - Map + Map >(); - for (const instanceConfig of extensionInstanceConfigs) { - const [extensionId, pointId = 'default'] = instanceConfig.at.split('/'); + for (const instanceParams of extensionInstanceParams) { + const [extensionId, pointId = 'default'] = instanceParams.at.split('/'); let pointMap = attachmentMap.get(extensionId); if (!pointMap) { @@ -151,21 +79,21 @@ export function createApp(options: { plugins: BackstagePlugin[] }): { pointMap.set(pointId, instances); } - instances.push(instanceConfig); + instances.push(instanceParams); } const instances = new Map(); function createInstance( - instanceConfig: ExtensionInstanceConfig, + instanceParams: ExtensionInstanceParameters, ): ExtensionInstance { - const existingInstance = instances.get(instanceConfig.id); + const existingInstance = instances.get(instanceParams.id); if (existingInstance) { return existingInstance; } const attachments = Object.fromEntries( - Array.from(attachmentMap.get(instanceConfig.id)?.entries() ?? []).map( + Array.from(attachmentMap.get(instanceParams.id)?.entries() ?? []).map( ([inputName, attachmentConfigs]) => [ inputName, attachmentConfigs.map(createInstance), @@ -174,16 +102,16 @@ export function createApp(options: { plugins: BackstagePlugin[] }): { ); return createExtensionInstance({ - id: instanceConfig.id, - config: instanceConfig.config, - extension: instanceConfig.extension, + id: instanceParams.id, + config: instanceParams.config, + extension: instanceParams.extension, attachments, }); } const rootConfigs = attachmentMap.get('root')?.get('default') ?? []; - const rootInstances = rootConfigs.map(instanceConfig => - createInstance(instanceConfig), + const rootInstances = rootConfigs.map(instanceParams => + createInstance(instanceParams), ); return { diff --git a/packages/frontend-app-api/src/createExtensionInstance.ts b/packages/frontend-app-api/src/createExtensionInstance.ts index 0b62df74ae..de9a41f49f 100644 --- a/packages/frontend-app-api/src/createExtensionInstance.ts +++ b/packages/frontend-app-api/src/createExtensionInstance.ts @@ -27,26 +27,45 @@ export interface ExtensionInstance { /** @internal */ export function createExtensionInstance(options: { id: string; - extension: Extension; + extension: Extension; config: unknown; attachments: Record; }): ExtensionInstance { - const { extension, config, attachments } = options; + const { id, extension, config, attachments } = options; const extensionData = new Map(); - extension.factory({ - config, - bind: mapValues(extension.output, ref => { - return (value: unknown) => extensionData.set(ref.id, value); - }), - inputs: mapValues( - extension.inputs, - ({ extensionData: pointData }, inputName) => { - // TODO: validation - return (attachments[inputName] ?? []).map(attachment => - mapValues(pointData, ref => attachment.data.get(ref.id)), - ); - }, - ), - }); - return { id: options.id, data: extensionData, $$type: 'extension-instance' }; + + let parsedConfig: unknown; + try { + parsedConfig = extension.configSchema?.parse(config ?? {}); + } catch (e) { + throw new Error( + `Invalid configuration for extension instance '${id}', ${e}`, + ); + } + + try { + extension.factory({ + config: parsedConfig, + bind: mapValues(extension.output, ref => { + return (value: unknown) => extensionData.set(ref.id, value); + }), + inputs: mapValues( + extension.inputs, + ({ extensionData: pointData }, inputName) => { + // TODO: validation + return (attachments[inputName] ?? []).map(attachment => + mapValues(pointData, ref => attachment.data.get(ref.id)), + ); + }, + ), + }); + } catch (e) { + throw new Error(`Failed to instantiate extension instance '${id}', ${e}`); + } + + return { + id: options.id, + data: extensionData, + $$type: 'extension-instance', + }; } diff --git a/packages/frontend-app-api/src/wiring/parameters.test.ts b/packages/frontend-app-api/src/wiring/parameters.test.ts new file mode 100644 index 0000000000..b47109ed67 --- /dev/null +++ b/packages/frontend-app-api/src/wiring/parameters.test.ts @@ -0,0 +1,436 @@ +/* + * Copyright 2023 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 { Extension } from '@backstage/frontend-plugin-api'; +import { JsonValue } from '@backstage/types'; +import { + expandShorthandExtensionParameters, + mergeExtensionParameters, + readAppExtensionParameters, +} from './parameters'; + +function makeExt(id: string, status: 'disabled' | 'enabled' = 'enabled') { + return { + id, + at: 'foo/bar', + extension: {} as Extension, + disabled: status === 'disabled', + }; +} + +describe('mergeExtensionParameters', () => { + it('should filter out disabled extension instances', () => { + expect(mergeExtensionParameters([makeExt('a', 'disabled')], [])).toEqual( + [], + ); + }); + + it('should pass through extension instances', () => { + expect(mergeExtensionParameters([makeExt('a'), makeExt('b')], [])).toEqual([ + makeExt('a'), + makeExt('b'), + ]); + }); + + it('should override extension instances', () => { + expect( + mergeExtensionParameters( + [makeExt('a'), makeExt('b')], + [ + { + id: 'b', + extension: { ext: 'other' } as unknown as Extension, + }, + ], + ), + ).toEqual([makeExt('a'), { ...makeExt('b'), extension: { ext: 'other' } }]); + }); + + it('should override attachment points', () => { + expect( + mergeExtensionParameters( + [makeExt('a'), makeExt('b')], + [ + { + id: 'b', + at: 'derp', + }, + ], + ), + ).toEqual([makeExt('a'), { ...makeExt('b'), at: 'derp' }]); + }); + + it('should fully override configuration', () => { + expect( + mergeExtensionParameters( + [ + { ...makeExt('a'), config: { foo: { bar: 1 } } }, + { ...makeExt('b'), config: { foo: { bar: 2 } } }, + ], + [ + { + id: 'b', + config: { foo: { qux: 3 } }, + }, + ], + ), + ).toEqual([ + { ...makeExt('a'), config: { foo: { bar: 1 } } }, + { ...makeExt('b'), config: { foo: { qux: 3 } } }, + ]); + }); + + it('should place enabled instances in the order that they were enabled', () => { + expect( + mergeExtensionParameters( + [makeExt('a', 'disabled'), makeExt('b', 'disabled')], + [ + { + id: 'b', + disabled: false, + }, + { + id: 'a', + disabled: false, + }, + ], + ), + ).toEqual([makeExt('b'), makeExt('a')]); + }); +}); + +describe('readAppExtensionParameters', () => { + it('should disable extension with shorthand notation', () => { + expect( + readAppExtensionParameters( + new ConfigReader({ app: { extensions: [{ 'core.router': false }] } }), + ), + ).toEqual([ + { + id: 'core.router', + disabled: true, + }, + ]); + expect( + readAppExtensionParameters( + new ConfigReader({ + app: { extensions: [{ 'core.router': { disabled: true } }] }, + }), + ), + ).toEqual([ + { + at: undefined, + config: undefined, + disabled: true, + id: 'core.router', + }, + ]); + }); + + it('should enable extension with shorthand notation', () => { + expect( + readAppExtensionParameters( + new ConfigReader({ app: { extensions: ['core.router'] } }), + ), + ).toEqual([ + { + id: 'core.router', + }, + ]); + expect( + readAppExtensionParameters( + new ConfigReader({ app: { extensions: [{ 'core.router': true }] } }), + ), + ).toEqual([ + { + id: 'core.router', + disabled: false, + }, + ]); + expect( + readAppExtensionParameters( + new ConfigReader({ + app: { extensions: [{ 'core.router': { disabled: false } }] }, + }), + ), + ).toEqual([ + { + id: 'core.router', + disabled: false, + }, + ]); + }); + + it('should support extension implementation shorthand', () => { + expect( + readAppExtensionParameters( + new ConfigReader({ + app: { + extensions: [{ 'core.router': 'example-package#CustomRouter' }], + }, + }), + ref => ({ ref } as unknown as Extension), + ), + ).toEqual([ + { + id: 'core.router', + extension: { ref: 'example-package#CustomRouter' }, + }, + ]); + }); + + it('should support attachment shorthand', () => { + expect( + readAppExtensionParameters( + new ConfigReader({ + app: { + extensions: [ + { + 'core.router/routes': { + extension: 'example-package#MyPage', + config: { foo: 'bar' }, + }, + }, + ], + }, + }), + ref => ({ ref } as unknown as Extension), + ), + ).toEqual([ + { + id: 'generated.1', + at: 'core.router/routes', + extension: { ref: 'example-package#MyPage' }, + config: { foo: 'bar' }, + }, + ]); + }); + + it('should support attachment with extension shorthand', () => { + expect( + readAppExtensionParameters( + new ConfigReader({ + app: { + extensions: [{ 'core.router/routes': 'example-package#MyPage' }], + }, + }), + ref => ({ ref } as unknown as Extension), + ), + ).toEqual([ + { + id: 'generated.1', + at: 'core.router/routes', + extension: { ref: 'example-package#MyPage' }, + }, + ]); + }); + + it('should reject attachment shorthand with explicit attachment', () => { + expect(() => + readAppExtensionParameters( + new ConfigReader({ + app: { + extensions: [ + { + 'core.router/routes': { + at: 'other/input', + }, + }, + ], + }, + }), + ), + ).toThrow( + `Invalid extension configuration at app.extensions[0][core.router/routes], must not redundantly specify 'at' when the extension input ID form of the key is used (with a slash); the 'at' is already implicitly 'core.router/routes'`, + ); + }); +}); + +describe('expandShorthandExtensionParameters', () => { + const resolveExtensionRef = jest.fn( + ref => ({ ref } as unknown as Extension), + ); + const generateExtensionId = jest.fn(() => 'generated.1'); + const run = (value: JsonValue) => { + return expandShorthandExtensionParameters( + value, + 1, + resolveExtensionRef, + generateExtensionId, + ); + }; + + it('rejects unknown keys', () => { + expect(() => run(null)).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1], must be a string or an object"`, + ); + expect(() => run(1)).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1], must be a string or an object"`, + ); + expect(() => run([])).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1], must be a string or an object"`, + ); + }); + + it('rejects the wrong number of keys', () => { + expect(() => run({})).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1], must have exactly one key, got none"`, + ); + expect(() => run({ a: {}, b: {} })).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1], must have exactly one key, got 'a', 'b'"`, + ); + }); + + it('rejects unknown values', () => { + expect(() => run({ a: null })).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][a], value must be a boolean, string, or object"`, + ); + expect(() => run({ a: 1 })).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][a], value must be a boolean, string, or object"`, + ); + expect(() => run({ a: [] })).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][a], value must be a boolean, string, or object"`, + ); + }); + + it('supports string key', () => { + expect(run('core.router')).toEqual({ + id: 'core.router', + }); + expect(() => run('')).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1], string shorthand cannot be the empty string"`, + ); + expect(() => run('core.router/routes')).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1], cannot target an extension instance input with the string shorthand (key cannot contain slashes; did you mean 'core.router'?)"`, + ); + }); + + it('supports boolean value', () => { + expect(run({ 'core.router': true })).toEqual({ + id: 'core.router', + disabled: false, + }); + expect(run({ 'core.router': false })).toEqual({ + id: 'core.router', + disabled: true, + }); + expect(() => + run({ 'core.router/routes': false }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][core.router/routes], cannot target an extension instance input (key cannot contain slashes; did you mean 'core.router'?)"`, + ); + }); + + it('supports string values', () => { + expect(run({ 'core.router': 'example-package#MyRouter' })).toEqual({ + id: 'core.router', + extension: { ref: 'example-package#MyRouter' }, + }); + expect(run({ 'core.router/routes': 'example-package#MyRouter' })).toEqual({ + id: 'generated.1', + at: 'core.router/routes', + extension: { ref: 'example-package#MyRouter' }, + }); + }); + + it('supports object id only in the key', () => { + expect(() => + run({ 'core.router/routes': { id: 'some.id' } }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][core.router/routes].id, must not specify 'id' when the extension input ID form of the key is used (with a slash); please replace the key 'core.router/routes' with the id instead, and put that key in the 'at' field"`, + ); + expect(() => + run({ + 'core.router/routes': { + id: 'example-package#MyRouter', + at: 'core.router/routes', + }, + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][core.router/routes], must not redundantly specify 'at' when the extension input ID form of the key is used (with a slash); the 'at' is already implicitly 'core.router/routes'"`, + ); + }); + + it('supports object at', () => { + expect(run({ 'core.router': { at: 'other.root/inputs' } })).toEqual({ + id: 'core.router', + at: 'other.root/inputs', + }); + expect(() => + run({ + 'core.router': { + id: 'other-id', + }, + }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][core.router], must not redundantly specify 'id' when the extension instance ID form of the key is used (without a slash); the 'id' is already implicitly 'core.router'"`, + ); + }); + + it('supports object disabled', () => { + expect(run({ 'core.router': { disabled: true } })).toEqual({ + id: 'core.router', + disabled: true, + }); + expect(run({ 'core.router': { disabled: false } })).toEqual({ + id: 'core.router', + disabled: false, + }); + expect(() => + run({ 'core.router': { disabled: 0 } }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][core.router].disabled, must be a boolean"`, + ); + }); + + it('supports object extension', () => { + expect( + run({ 'core.router/routes': { extension: 'example-package#MyRouter' } }), + ).toEqual({ + id: 'generated.1', + at: 'core.router/routes', + extension: { ref: 'example-package#MyRouter' }, + }); + expect(() => + run({ 'core.router/routes': { extension: 0 } }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][core.router/routes].extension, must be a string"`, + ); + }); + + it('supports object config', () => { + expect( + run({ 'core.router': { config: { disableRedirects: true } } }), + ).toEqual({ + id: 'core.router', + config: { disableRedirects: true }, + }); + expect(() => + run({ 'core.router': { config: 0 } }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][core.router].config, must be an object"`, + ); + }); + + it('rejects unknown object keys', () => { + expect(() => + run({ 'core.router': { foo: { settings: true } } }), + ).toThrowErrorMatchingInlineSnapshot( + `"Invalid extension configuration at app.extensions[1][core.router].foo, unknown parameter; expected one of 'at', 'disabled', 'extension', 'config'"`, + ); + }); +}); diff --git a/packages/frontend-app-api/src/wiring/parameters.ts b/packages/frontend-app-api/src/wiring/parameters.ts new file mode 100644 index 0000000000..ba04d6c9ee --- /dev/null +++ b/packages/frontend-app-api/src/wiring/parameters.ts @@ -0,0 +1,299 @@ +/* + * Copyright 2023 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 { + Extension, + ExtensionInstanceParameters, +} from '@backstage/frontend-plugin-api'; +import { JsonValue } from '@backstage/types'; +import omitBy from 'lodash/omitBy'; + +const knownExtensionInstanceParameters = [ + 'at', + 'disabled', + 'extension', + 'config', +]; + +// Since we'll never merge arrays in config the config reader context +// isn't too much of a help. Fall back to manual config reading logic +// as the Config interface makes it quite hard for us otherwise. +/** @internal */ +export function readAppExtensionParameters( + rootConfig: Config, + resolveExtensionRef: (ref: string) => Extension = () => { + throw new Error('Not Implemented'); + }, +): Partial[] { + const arr = rootConfig.getOptional('app.extensions'); + if (!Array.isArray(arr)) { + if (arr === undefined) { + return []; + } + // This will throw, and show which part of config had the wrong type + rootConfig.getConfigArray('app.extensions'); + return []; + } + + const generateExtensionId = (() => { + let index = 1; + return () => `generated.${index++}`; + })(); + + return arr.map((arrayEntry, arrayIndex) => + expandShorthandExtensionParameters( + arrayEntry, + arrayIndex, + resolveExtensionRef, + generateExtensionId, + ), + ); +} + +/** @internal */ +export function expandShorthandExtensionParameters( + arrayEntry: JsonValue, + arrayIndex: number, + resolveExtensionRef: (ref: string) => Extension, + generateExtensionId: () => string, +): Partial { + function errorMsg(msg: string, key?: string, prop?: string) { + return `Invalid extension configuration at app.extensions[${arrayIndex}]${ + key ? `[${key}]` : '' + }${prop ? `.${prop}` : ''}, ${msg}`; + } + + // Example YAML: + // - entity.card.about + if (typeof arrayEntry === 'string') { + if (arrayEntry.includes('/')) { + const suggestion = arrayEntry.split('/')[0]; + throw new Error( + errorMsg( + `cannot target an extension instance input with the string shorthand (key cannot contain slashes; did you mean '${suggestion}'?)`, + ), + ); + } + if (!arrayEntry) { + throw new Error(errorMsg('string shorthand cannot be the empty string')); + } + return { + id: arrayEntry, + }; + } + + // All remaining cases are single-key objects + if ( + typeof arrayEntry !== 'object' || + arrayEntry === null || + Array.isArray(arrayEntry) + ) { + throw new Error(errorMsg('must be a string or an object')); + } + const keys = Object.keys(arrayEntry); + if (keys.length !== 1) { + const joinedKeys = keys.length ? `'${keys.join("', '")}'` : 'none'; + throw new Error(errorMsg(`must have exactly one key, got ${joinedKeys}`)); + } + + const key = keys[0]; + const value = arrayEntry[key]; + + // Example YAML: + // - catalog.page.cicd: false + if (typeof value === 'boolean') { + if (key.includes('/')) { + const suggestion = key.split('/')[0]; + throw new Error( + errorMsg( + `cannot target an extension instance input (key cannot contain slashes; did you mean '${suggestion}'?)`, + key, + ), + ); + } + + return { + id: key, + disabled: !value, + }; + } + + // Example YAML: + // - core.router/routes: '@backstage/plugin-some-plugin#MyPage' + // - some-plugin.page: '@internal/frontend-customizations#MyModifiedPage' + if (typeof value === 'string') { + if (key.includes('/')) { + return { + id: generateExtensionId(), + at: key, + extension: resolveExtensionRef(value), + }; + } + + return { + id: key, + extension: resolveExtensionRef(value), + }; + } + + // The remaining case is the generic object. Example YAML: + // - core.router/routes: + // extension: '@backstage/core-app-api#Redirect' + // config: + // path: / + // to: /catalog + // - tech-radar.page: + // at: core.router/routes + // extension: '@backstage/plugin-tech-radar#TechRadarPage' + // config: + // path: /tech-radar + // width: 1500 + // height: 800 + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw new Error( + errorMsg('value must be a boolean, string, or object', key), + ); + } + + let id = value.id; + let at = value.at; + const disabled = value.disabled; + const extensionRef = value.extension; + const config = value.config; + + if (key.includes('/')) { + if (at !== undefined) { + throw new Error( + errorMsg( + `must not redundantly specify 'at' when the extension input ID form of the key is used (with a slash); the 'at' is already implicitly '${key}'`, + key, + ), + ); + } + if (id !== undefined) { + throw new Error( + errorMsg( + `must not specify 'id' when the extension input ID form of the key is used (with a slash); please replace the key '${key}' with the id instead, and put that key in the 'at' field`, + key, + 'id', + ), + ); + } + id = generateExtensionId(); + at = key; + } else { + if (id !== undefined) { + throw new Error( + errorMsg( + `must not redundantly specify 'id' when the extension instance ID form of the key is used (without a slash); the 'id' is already implicitly '${key}'`, + key, + ), + ); + } + id = key; + } + + if (id !== undefined && typeof id !== 'string') { + throw new Error(errorMsg('must be a string', key, 'id')); + } else if (at !== undefined && typeof at !== 'string') { + throw new Error(errorMsg('must be a string', key, 'at')); + } else if (disabled !== undefined && typeof disabled !== 'boolean') { + throw new Error(errorMsg('must be a boolean', key, 'disabled')); + } else if (extensionRef !== undefined && typeof extensionRef !== 'string') { + throw new Error(errorMsg('must be a string', key, 'extension')); + } else if ( + config !== undefined && + (typeof config !== 'object' || config === null || Array.isArray(config)) + ) { + throw new Error(errorMsg('must be an object', key, 'config')); + } + + const unknownKeys = Object.keys(value).filter( + k => !knownExtensionInstanceParameters.includes(k), + ); + if (unknownKeys.length > 0) { + throw new Error( + errorMsg( + `unknown parameter; expected one of '${knownExtensionInstanceParameters.join( + "', '", + )}'`, + key, + unknownKeys.join(', '), + ), + ); + } + + const extension: Extension | undefined = extensionRef + ? resolveExtensionRef(extensionRef) + : undefined; + + return omitBy( + { + id, + at, + disabled, + extension, + config, + }, + v => v === undefined, + ); +} + +/** @internal */ +export function mergeExtensionParameters( + base: ExtensionInstanceParameters[], + overrides: Array>, +): ExtensionInstanceParameters[] { + const extensionInstanceParams = base.slice(); + + for (const overrideParam of overrides) { + const existingParamIndex = extensionInstanceParams.findIndex( + e => e.id === overrideParam.id, + ); + if (existingParamIndex !== -1) { + const existingParam = extensionInstanceParams[existingParamIndex]; + if (overrideParam.at) { + existingParam.at = overrideParam.at; + } + if (overrideParam.extension) { + // TODO: do we want to reset config here? it might be completely + // unrelated to the previous one + existingParam.extension = overrideParam.extension; + } + if (overrideParam.config) { + // TODO: merge config? + existingParam.config = overrideParam.config; + } + if (Boolean(existingParam.disabled) !== Boolean(overrideParam.disabled)) { + existingParam.disabled = Boolean(overrideParam.disabled); + if (!existingParam.disabled) { + // bump + extensionInstanceParams.splice(existingParamIndex, 1); + extensionInstanceParams.push(existingParam); + } + } + } else if (overrideParam.id) { + const { id, at, extension, config } = overrideParam; + if (!at || !extension) { + throw new Error(`Extension ${overrideParam.id} is incomplete`); + } + extensionInstanceParams.push({ id, at, extension, config }); + } + } + + return extensionInstanceParams.filter(param => !param.disabled); +} diff --git a/packages/frontend-plugin-api/README.md b/packages/frontend-plugin-api/README.md index 43cb4082dc..1fd3da79d8 100644 --- a/packages/frontend-plugin-api/README.md +++ b/packages/frontend-plugin-api/README.md @@ -1,12 +1,9 @@ # @backstage/frontend-plugin-api -_This package was created through the Backstage CLI_. +**This package is EXPERIMENTAL, we recommend against using it for production deployments** -## Installation +This package provides the core API used by Backstage frontend plugins. It implements the design outlined in [RFC: Frontend System Evolution](https://github.com/backstage/backstage/issues/18372). -Install the package via Yarn: +## Documentation -```sh -cd # if within a monorepo -yarn add @backstage/frontend-plugin-api -``` +- [Backstage Documentation](https://backstage.io/docs) diff --git a/packages/frontend-plugin-api/api-report.md b/packages/frontend-plugin-api/api-report.md index 4337b0b8c2..a86597d7bd 100644 --- a/packages/frontend-plugin-api/api-report.md +++ b/packages/frontend-plugin-api/api-report.md @@ -4,6 +4,10 @@ ```ts import { ComponentType } from 'react'; +import { JsonObject } from '@backstage/types'; +import { z } from 'zod'; +import { ZodSchema } from 'zod'; +import { ZodTypeDef } from 'zod'; // @public (undocumented) export type AnyExtensionDataMap = Record>; @@ -13,7 +17,7 @@ export interface BackstagePlugin { // (undocumented) $$type: 'backstage-plugin'; // (undocumented) - defaultExtensionInstances: ExtensionInstanceConfig[]; + defaultExtensionInstances: ExtensionInstanceParameters[]; // (undocumented) id: string; } @@ -21,7 +25,7 @@ export interface BackstagePlugin { // @public (undocumented) export interface BackstagePluginOptions { // (undocumented) - defaultExtensionInstances?: ExtensionInstanceConfig[]; + defaultExtensionInstances?: ExtensionInstanceParameters[]; // (undocumented) id: string; } @@ -41,7 +45,8 @@ export function createExtension< extensionData: AnyExtensionDataMap; } >, ->(options: CreateExtensionOptions): Extension; + TConfig = never, +>(options: CreateExtensionOptions): Extension; // @public (undocumented) export interface CreateExtensionOptions< @@ -52,11 +57,14 @@ export interface CreateExtensionOptions< extensionData: AnyExtensionDataMap; } >, + TConfig, > { + // (undocumented) + configSchema?: PortableSchema; // (undocumented) factory(options: { bind: ExtensionDataBind; - config?: unknown; + config: TConfig; inputs: { [pointName in keyof TPoint]: ExtensionDataValue< TPoint[pointName]['extensionData'] @@ -73,13 +81,20 @@ export interface CreateExtensionOptions< export function createPlugin(options: BackstagePluginOptions): BackstagePlugin; // @public (undocumented) -export interface Extension { +export function createSchemaFromZod( + schemaCreator: (zImpl: typeof z) => ZodSchema, +): PortableSchema; + +// @public (undocumented) +export interface Extension { // (undocumented) $$type: 'extension'; // (undocumented) + configSchema?: PortableSchema; + // (undocumented) factory(options: { bind: ExtensionDataBind; - config?: unknown; + config: TConfig; inputs: Record>>; }): void; // (undocumented) @@ -111,14 +126,22 @@ export type ExtensionDataValue = { }; // @public (undocumented) -export interface ExtensionInstanceConfig { +export interface ExtensionInstanceParameters { // (undocumented) at: string; // (undocumented) - config: unknown; + config?: unknown; // (undocumented) - extension: Extension; + disabled?: boolean; + // (undocumented) + extension: Extension; // (undocumented) id: string; } + +// @public (undocumented) +export type PortableSchema = { + parse: (input: unknown) => TOutput; + schema: JsonObject; +}; ``` diff --git a/packages/frontend-plugin-api/catalog-info.yaml b/packages/frontend-plugin-api/catalog-info.yaml new file mode 100644 index 0000000000..4531f6eca3 --- /dev/null +++ b/packages/frontend-plugin-api/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-frontend-plugin-api + title: '@backstage/frontend-plugin-api' +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 731e66fed0..cf353e55a6 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -33,6 +33,9 @@ "react": "^16.13.1 || ^17.0.0" }, "dependencies": { - "lodash": "^4.17.21" + "@backstage/types": "workspace:^", + "lodash": "^4.17.21", + "zod": "^3.21.4", + "zod-to-json-schema": "^3.21.4" } } diff --git a/packages/frontend-plugin-api/src/createSchemaFromZod.test.ts b/packages/frontend-plugin-api/src/createSchemaFromZod.test.ts new file mode 100644 index 0000000000..90eba44249 --- /dev/null +++ b/packages/frontend-plugin-api/src/createSchemaFromZod.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright 2023 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 { createSchemaFromZod } from './createSchemaFromZod'; + +describe('createSchemaFromZod', () => { + it('should provide nice parse errors', () => { + const { parse } = createSchemaFromZod(z => + z.object({ + foo: z.union([z.string(), z.number()]), + derp: z.object({ bar: z.number() }), + }), + ); + + expect(() => parse({ derp: { bar: 'derp' } })).toThrow( + `Missing required value at 'foo'; Expected number, received string at 'derp.bar'`, + ); + expect(() => parse(undefined)).toThrow(`Missing required value`); + expect(() => parse('derp')).toThrow(`Expected object, received string`); + }); +}); diff --git a/packages/frontend-plugin-api/src/createSchemaFromZod.ts b/packages/frontend-plugin-api/src/createSchemaFromZod.ts new file mode 100644 index 0000000000..447cea8bbd --- /dev/null +++ b/packages/frontend-plugin-api/src/createSchemaFromZod.ts @@ -0,0 +1,59 @@ +/* + * Copyright 2023 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 { JsonObject } from '@backstage/types'; +import { z, ZodSchema, ZodTypeDef } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; + +/** @public */ +export type PortableSchema = { + parse: (input: unknown) => TOutput; + schema: JsonObject; +}; + +/** @public */ +export function createSchemaFromZod( + schemaCreator: (zImpl: typeof z) => ZodSchema, +): PortableSchema { + const schema = schemaCreator(z); + return { + // TODO: Types allow z.array etc here but it will break stuff + parse: input => { + const result = schema.safeParse(input); + if (result.success) { + return result.data; + } + + throw new Error(result.error.issues.map(formatIssue).join('; ')); + }, + // TODO: Verify why we are not compatible with the latest zodToJsonSchema. + schema: zodToJsonSchema(schema) as JsonObject, + }; +} + +function formatIssue(issue: z.ZodIssue): string { + if (issue.code === 'invalid_union') { + return formatIssue(issue.unionErrors[0].issues[0]); + } + let message = issue.message; + if (message === 'Required') { + message = `Missing required value`; + } + if (issue.path.length) { + message += ` at '${issue.path.join('.')}'`; + } + return message; +} diff --git a/packages/frontend-plugin-api/src/index.ts b/packages/frontend-plugin-api/src/index.ts index e36391131d..f70c77abcc 100644 --- a/packages/frontend-plugin-api/src/index.ts +++ b/packages/frontend-plugin-api/src/index.ts @@ -24,7 +24,7 @@ export { createExtension, coreExtensionData, createPlugin, - type ExtensionInstanceConfig, + type ExtensionInstanceParameters, type BackstagePlugin, type Extension, type AnyExtensionDataMap, @@ -34,3 +34,7 @@ export { type ExtensionDataRef, type ExtensionDataValue, } from './types'; +export { + createSchemaFromZod, + type PortableSchema, +} from './createSchemaFromZod'; diff --git a/packages/frontend-plugin-api/src/types.ts b/packages/frontend-plugin-api/src/types.ts index b2d131c62f..8822cf044d 100644 --- a/packages/frontend-plugin-api/src/types.ts +++ b/packages/frontend-plugin-api/src/types.ts @@ -15,6 +15,7 @@ */ import { ComponentType } from 'react'; +import { PortableSchema } from './createSchemaFromZod'; /** @public */ export type ExtensionDataRef = { @@ -51,12 +52,14 @@ export type ExtensionDataValue = { export interface CreateExtensionOptions< TData extends AnyExtensionDataMap, TPoint extends Record, + TConfig, > { inputs?: TPoint; output: TData; + configSchema?: PortableSchema; factory(options: { bind: ExtensionDataBind; - config?: unknown; + config: TConfig; inputs: { [pointName in keyof TPoint]: ExtensionDataValue< TPoint[pointName]['extensionData'] @@ -66,13 +69,15 @@ export interface CreateExtensionOptions< } /** @public */ -export interface Extension { +export interface Extension { $$type: 'extension'; + // TODO: will extensions have a default "at" as part of their contract, making it optional in the instance config? inputs: Record; output: AnyExtensionDataMap; + configSchema?: PortableSchema; factory(options: { bind: ExtensionDataBind; - config?: unknown; + config: TConfig; inputs: Record>>; }): void; } @@ -81,29 +86,31 @@ export interface Extension { export function createExtension< TData extends AnyExtensionDataMap, TPoint extends Record, ->(options: CreateExtensionOptions): Extension { + TConfig = never, +>(options: CreateExtensionOptions): Extension { return { ...options, $$type: 'extension', inputs: options.inputs ?? {} }; } /** @public */ -export interface ExtensionInstanceConfig { +export interface ExtensionInstanceParameters { id: string; at: string; - extension: Extension; - config: unknown; + extension: Extension; + config?: unknown; + disabled?: boolean; } /** @public */ export interface BackstagePluginOptions { id: string; - defaultExtensionInstances?: ExtensionInstanceConfig[]; + defaultExtensionInstances?: ExtensionInstanceParameters[]; } /** @public */ export interface BackstagePlugin { $$type: 'backstage-plugin'; id: string; - defaultExtensionInstances: ExtensionInstanceConfig[]; + defaultExtensionInstances: ExtensionInstanceParameters[]; } /** @public */ diff --git a/packages/integration-aws-node/catalog-info.yaml b/packages/integration-aws-node/catalog-info.yaml new file mode 100644 index 0000000000..4c8264d575 --- /dev/null +++ b/packages/integration-aws-node/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-integration-aws-node + title: '@backstage/integration-aws-node' + description: Helpers for fetching AWS account credentials +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/packages/integration-react/catalog-info.yaml b/packages/integration-react/catalog-info.yaml new file mode 100644 index 0000000000..8df5e52c95 --- /dev/null +++ b/packages/integration-react/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-integration-react + title: '@backstage/integration-react' + description: Frontend package for managing integrations towards external systems +spec: + lifecycle: production + type: backstage-web-library + owner: maintainers diff --git a/packages/integration/catalog-info.yaml b/packages/integration/catalog-info.yaml new file mode 100644 index 0000000000..7f602c42be --- /dev/null +++ b/packages/integration/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-integration + title: '@backstage/integration' + description: Helpers for managing integrations towards external systems +spec: + lifecycle: production + type: backstage-common-library + owner: maintainers diff --git a/packages/release-manifests/catalog-info.yaml b/packages/release-manifests/catalog-info.yaml new file mode 100644 index 0000000000..4564145a3d --- /dev/null +++ b/packages/release-manifests/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-release-manifests + title: '@backstage/release-manifests' + description: Helper library for receiving release manifests +spec: + lifecycle: experimental + type: backstage-common-library + owner: maintainers diff --git a/packages/repo-tools/catalog-info.yaml b/packages/repo-tools/catalog-info.yaml new file mode 100644 index 0000000000..66b5ecc64b --- /dev/null +++ b/packages/repo-tools/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-repo-tools + title: '@backstage/repo-tools' + description: 'CLI for Backstage repo tooling ' +spec: + lifecycle: experimental + type: backstage-cli + owner: maintainers diff --git a/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts b/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts index 38bc3ab4b2..b8a473c67b 100644 --- a/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts +++ b/packages/repo-tools/src/commands/generate-catalog-info/generate-catalog-info.ts @@ -253,7 +253,7 @@ function createOrMergeEntity( // Provide default name/title/description values. name: safeEntityName, title: packageJson.name, - ...(packageJson.description + ...(packageJson.description && !existingEntity.metadata?.description ? { description: packageJson.description } : undefined), }, diff --git a/packages/techdocs-cli/cli-report.md b/packages/techdocs-cli/cli-report.md index 556068de85..5f148daf24 100644 --- a/packages/techdocs-cli/cli-report.md +++ b/packages/techdocs-cli/cli-report.md @@ -37,6 +37,7 @@ Options: -v --verbose --omitTechdocsCoreMkdocsPlugin --legacyCopyReadmeMdToIndexMd + --defaultPlugin [defaultPlugins...] -h, --help ``` diff --git a/packages/techdocs-cli/src/commands/generate/generate.ts b/packages/techdocs-cli/src/commands/generate/generate.ts index 5d80beb38a..934eaf1efd 100644 --- a/packages/techdocs-cli/src/commands/generate/generate.ts +++ b/packages/techdocs-cli/src/commands/generate/generate.ts @@ -47,6 +47,7 @@ export default async function generate(opts: OptionValues) { const dockerImage = opts.dockerImage; const pullImage = opts.pull; const legacyCopyReadmeMdToIndexMd = opts.legacyCopyReadmeMdToIndexMd; + const defaultPlugins = opts.defaultPlugin; logger.info(`Using source dir ${sourceDir}`); logger.info(`Will output generated files in ${outputDir}`); @@ -68,6 +69,7 @@ export default async function generate(opts: OptionValues) { mkdocs: { legacyCopyReadmeMdToIndexMd, omitTechdocsCorePlugin, + defaultPlugins, }, }, }, diff --git a/packages/techdocs-cli/src/commands/index.ts b/packages/techdocs-cli/src/commands/index.ts index 98816a6b32..3321bb7f43 100644 --- a/packages/techdocs-cli/src/commands/index.ts +++ b/packages/techdocs-cli/src/commands/index.ts @@ -70,6 +70,11 @@ export function registerCommands(program: Command) { 'Attempt to ensure an index.md exists falling back to using /README.md or README.md in case a default /index.md is not provided.', false, ) + .option( + '--defaultPlugin [defaultPlugins...]', + 'Plugins which should be added automatically to the mkdocs.yaml file', + [], + ) .alias('build') .action(lazy(() => import('./generate/generate').then(m => m.default))); diff --git a/packages/test-utils/catalog-info.yaml b/packages/test-utils/catalog-info.yaml new file mode 100644 index 0000000000..4b894f08b5 --- /dev/null +++ b/packages/test-utils/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-test-utils + title: '@backstage/test-utils' + description: Utilities to test Backstage plugins and apps. +spec: + lifecycle: production + type: backstage-web-library + owner: maintainers diff --git a/packages/theme/catalog-info.yaml b/packages/theme/catalog-info.yaml new file mode 100644 index 0000000000..1fee85f58d --- /dev/null +++ b/packages/theme/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-theme + title: '@backstage/theme' + description: material-ui theme for use with Backstage. +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/packages/types/catalog-info.yaml b/packages/types/catalog-info.yaml new file mode 100644 index 0000000000..1bd30379cc --- /dev/null +++ b/packages/types/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-types + title: '@backstage/types' + description: Common TypeScript types used within Backstage +spec: + lifecycle: production + type: backstage-common-library + owner: maintainers diff --git a/packages/version-bridge/catalog-info.yaml b/packages/version-bridge/catalog-info.yaml new file mode 100644 index 0000000000..6f37a4b276 --- /dev/null +++ b/packages/version-bridge/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-version-bridge + title: '@backstage/version-bridge' + description: >- + Utilities used by @backstage packages to support multiple concurrent + versions +spec: + lifecycle: production + type: backstage-web-library + owner: maintainers diff --git a/plugins/adr-backend/catalog-info.yaml b/plugins/adr-backend/catalog-info.yaml new file mode 100644 index 0000000000..9bd35f2722 --- /dev/null +++ b/plugins/adr-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-adr-backend + title: '@backstage/plugin-adr-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: kuangp diff --git a/plugins/adr-common/catalog-info.yaml b/plugins/adr-common/catalog-info.yaml new file mode 100644 index 0000000000..defcbe9fdb --- /dev/null +++ b/plugins/adr-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-adr-common + title: '@backstage/plugin-adr-common' + description: Common functionalities for the adr plugin +spec: + lifecycle: experimental + type: backstage-common-library + owner: kuangp diff --git a/plugins/adr/catalog-info.yaml b/plugins/adr/catalog-info.yaml new file mode 100644 index 0000000000..61fb6116d6 --- /dev/null +++ b/plugins/adr/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-adr + title: '@backstage/plugin-adr' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: kuangp diff --git a/plugins/airbrake-backend/catalog-info.yaml b/plugins/airbrake-backend/catalog-info.yaml new file mode 100644 index 0000000000..90884f5d34 --- /dev/null +++ b/plugins/airbrake-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-airbrake-backend + title: '@backstage/plugin-airbrake-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/airbrake/catalog-info.yaml b/plugins/airbrake/catalog-info.yaml new file mode 100644 index 0000000000..d1eac0d04e --- /dev/null +++ b/plugins/airbrake/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-airbrake + title: '@backstage/plugin-airbrake' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/allure/catalog-info.yaml b/plugins/allure/catalog-info.yaml new file mode 100644 index 0000000000..4ea1238b30 --- /dev/null +++ b/plugins/allure/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-allure + title: '@backstage/plugin-allure' + description: A Backstage plugin that integrates with Allure +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/analytics-module-ga/catalog-info.yaml b/plugins/analytics-module-ga/catalog-info.yaml new file mode 100644 index 0000000000..24dddf1aa5 --- /dev/null +++ b/plugins/analytics-module-ga/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-analytics-module-ga + title: '@backstage/plugin-analytics-module-ga' +spec: + lifecycle: experimental + type: backstage-frontend-plugin-module + owner: maintainers diff --git a/plugins/analytics-module-ga4/catalog-info.yaml b/plugins/analytics-module-ga4/catalog-info.yaml new file mode 100644 index 0000000000..8d98ebe82c --- /dev/null +++ b/plugins/analytics-module-ga4/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-analytics-module-ga4 + title: '@backstage/plugin-analytics-module-ga4' +spec: + lifecycle: experimental + type: backstage-frontend-plugin-module + owner: maintainers diff --git a/plugins/analytics-module-newrelic-browser/catalog-info.yaml b/plugins/analytics-module-newrelic-browser/catalog-info.yaml new file mode 100644 index 0000000000..68f38c4848 --- /dev/null +++ b/plugins/analytics-module-newrelic-browser/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-analytics-module-newrelic-browser + title: '@backstage/plugin-analytics-module-newrelic-browser' +spec: + lifecycle: experimental + type: backstage-frontend-plugin-module + owner: maintainers diff --git a/plugins/apache-airflow/catalog-info.yaml b/plugins/apache-airflow/catalog-info.yaml new file mode 100644 index 0000000000..d564af4912 --- /dev/null +++ b/plugins/apache-airflow/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-apache-airflow + title: '@backstage/plugin-apache-airflow' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/api-docs-module-protoc-gen-doc/catalog-info.yaml b/plugins/api-docs-module-protoc-gen-doc/catalog-info.yaml new file mode 100644 index 0000000000..b69366878b --- /dev/null +++ b/plugins/api-docs-module-protoc-gen-doc/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-api-docs-module-protoc-gen-doc + title: '@backstage/plugin-api-docs-module-protoc-gen-doc' + description: >- + Additional functionalities for the api-docs plugin that renders the output + of the protoc-gen-doc +spec: + lifecycle: experimental + type: backstage-frontend-plugin-module + owner: maintainers diff --git a/plugins/api-docs/catalog-info.yaml b/plugins/api-docs/catalog-info.yaml new file mode 100644 index 0000000000..3c09133857 --- /dev/null +++ b/plugins/api-docs/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-api-docs + title: '@backstage/plugin-api-docs' + description: A Backstage plugin that helps represent API entities in the frontend +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: sda-se-reviewers diff --git a/plugins/apollo-explorer/catalog-info.yaml b/plugins/apollo-explorer/catalog-info.yaml new file mode 100644 index 0000000000..7e2380679c --- /dev/null +++ b/plugins/apollo-explorer/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-apollo-explorer + title: '@backstage/plugin-apollo-explorer' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/app-backend/catalog-info.yaml b/plugins/app-backend/catalog-info.yaml new file mode 100644 index 0000000000..27135f4f8d --- /dev/null +++ b/plugins/app-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-app-backend + title: '@backstage/plugin-app-backend' + description: A Backstage backend plugin that serves the Backstage frontend app +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/app-node/catalog-info.yaml b/plugins/app-node/catalog-info.yaml new file mode 100644 index 0000000000..6624153c6c --- /dev/null +++ b/plugins/app-node/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-app-node + title: '@backstage/plugin-app-node' + description: Node.js library for the app plugin +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml b/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml new file mode 100644 index 0000000000..ef604027df --- /dev/null +++ b/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-gcp-iap-provider + title: '@backstage/plugin-auth-backend-module-gcp-iap-provider' + description: A GCP IAP auth provider module for the Backstage auth backend +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/auth-backend-module-github-provider/catalog-info.yaml b/plugins/auth-backend-module-github-provider/catalog-info.yaml new file mode 100644 index 0000000000..8eda78fdaa --- /dev/null +++ b/plugins/auth-backend-module-github-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-github-provider + title: '@backstage/plugin-auth-backend-module-github-provider' + description: The github-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml b/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml new file mode 100644 index 0000000000..55086778e4 --- /dev/null +++ b/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-gitlab-provider + title: '@backstage/plugin-auth-backend-module-gitlab-provider' + description: The gitlab-provider backend module for the auth plugin. +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/auth-backend-module-google-provider/catalog-info.yaml b/plugins/auth-backend-module-google-provider/catalog-info.yaml new file mode 100644 index 0000000000..0897a12168 --- /dev/null +++ b/plugins/auth-backend-module-google-provider/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend-module-google-provider + title: '@backstage/plugin-auth-backend-module-google-provider' + description: A Google auth provider module for the Backstage auth backend +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/auth-backend/catalog-info.yaml b/plugins/auth-backend/catalog-info.yaml new file mode 100644 index 0000000000..38ac5f0753 --- /dev/null +++ b/plugins/auth-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-backend + title: '@backstage/plugin-auth-backend' + description: A Backstage backend plugin that handles authentication +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 5cedf974a4..a199716713 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -47,6 +47,7 @@ "@backstage/types": "workspace:^", "@davidzemon/passport-okta-oauth": "^0.0.5", "@google-cloud/firestore": "^6.0.0", + "@node-saml/passport-saml": "^4.0.4", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", "compression": "^1.7.4", @@ -77,7 +78,6 @@ "passport-microsoft": "^1.0.0", "passport-oauth2": "^1.6.1", "passport-onelogin-oauth": "^0.0.1", - "passport-saml": "^3.1.2", "uuid": "^8.0.0", "winston": "^3.2.1", "yn": "^4.0.0" diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index 3d35f46628..d3170c710a 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -15,16 +15,16 @@ */ import express from 'express'; -import { SamlConfig } from 'passport-saml/lib/passport-saml/types'; +import { SamlConfig } from '@node-saml/passport-saml'; import { - Strategy as SamlStrategy, Profile as SamlProfile, + Strategy as SamlStrategy, + VerifiedCallback, VerifyWithoutRequest, -} from 'passport-saml'; +} from '@node-saml/passport-saml'; import { executeFrameHandlerStrategy, executeRedirectStrategy, - PassportDoneCallback, } from '../../lib/passport'; import { AuthProviderRouteHandlers, @@ -62,17 +62,18 @@ export class SamlAuthProvider implements AuthProviderRouteHandlers { this.signInResolver = options.signInResolver; this.authHandler = options.authHandler; this.resolverContext = options.resolverContext; - this.strategy = new SamlStrategy({ ...options }, (( - fullProfile: SamlProfile, - done: PassportDoneCallback, + const verifier: VerifyWithoutRequest = ( + profile: SamlProfile | null, + done: VerifiedCallback, ) => { // TODO: There's plenty more validation and profile handling to do here, // this provider is currently only intended to validate the provider pattern // for non-oauth auth flows. // TODO: This flow doesn't issue an identity token that can be used to validate // the identity of the user in other backends, which we need in some form. - done(undefined, { fullProfile }); - }) as VerifyWithoutRequest); + done(null, { fullProfile: profile }); + }; + this.strategy = new SamlStrategy(options, verifier, verifier); } async start(req: express.Request, res: express.Response): Promise { diff --git a/plugins/auth-node/catalog-info.yaml b/plugins/auth-node/catalog-info.yaml new file mode 100644 index 0000000000..4b23642810 --- /dev/null +++ b/plugins/auth-node/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-auth-node + title: '@backstage/plugin-auth-node' +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/plugins/azure-devops-backend/catalog-info.yaml b/plugins/azure-devops-backend/catalog-info.yaml new file mode 100644 index 0000000000..4f45937842 --- /dev/null +++ b/plugins/azure-devops-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-azure-devops-backend + title: '@backstage/plugin-azure-devops-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: awanlin diff --git a/plugins/azure-devops-common/catalog-info.yaml b/plugins/azure-devops-common/catalog-info.yaml new file mode 100644 index 0000000000..939f6ec031 --- /dev/null +++ b/plugins/azure-devops-common/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-azure-devops-common + title: '@backstage/plugin-azure-devops-common' +spec: + lifecycle: experimental + type: backstage-common-library + owner: awanlin diff --git a/plugins/azure-devops/catalog-info.yaml b/plugins/azure-devops/catalog-info.yaml new file mode 100644 index 0000000000..7f2b9be648 --- /dev/null +++ b/plugins/azure-devops/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-azure-devops + title: '@backstage/plugin-azure-devops' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: awanlin diff --git a/plugins/azure-sites-backend/catalog-info.yaml b/plugins/azure-sites-backend/catalog-info.yaml new file mode 100644 index 0000000000..07ed594da8 --- /dev/null +++ b/plugins/azure-sites-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-azure-sites-backend + title: '@backstage/plugin-azure-sites-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/azure-sites-common/catalog-info.yaml b/plugins/azure-sites-common/catalog-info.yaml new file mode 100644 index 0000000000..74d7be8715 --- /dev/null +++ b/plugins/azure-sites-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-azure-sites-common + title: '@backstage/plugin-azure-sites-common' + description: Common functionalities for the azure plugin +spec: + lifecycle: experimental + type: backstage-common-library + owner: maintainers diff --git a/plugins/azure-sites/catalog-info.yaml b/plugins/azure-sites/catalog-info.yaml new file mode 100644 index 0000000000..0902759994 --- /dev/null +++ b/plugins/azure-sites/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-azure-sites + title: '@backstage/plugin-azure-sites' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/badges-backend/catalog-info.yaml b/plugins/badges-backend/catalog-info.yaml new file mode 100644 index 0000000000..7d4e1e85a1 --- /dev/null +++ b/plugins/badges-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-badges-backend + title: '@backstage/plugin-badges-backend' + description: A Backstage backend plugin that generates README badges for your entities +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/badges/catalog-info.yaml b/plugins/badges/catalog-info.yaml new file mode 100644 index 0000000000..1e96e514dc --- /dev/null +++ b/plugins/badges/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-badges + title: '@backstage/plugin-badges' + description: A Backstage plugin that generates README badges for your entities +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/bazaar-backend/catalog-info.yaml b/plugins/bazaar-backend/catalog-info.yaml new file mode 100644 index 0000000000..ca9a705b52 --- /dev/null +++ b/plugins/bazaar-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-bazaar-backend + title: '@backstage/plugin-bazaar-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/bazaar/catalog-info.yaml b/plugins/bazaar/catalog-info.yaml new file mode 100644 index 0000000000..4fb2562731 --- /dev/null +++ b/plugins/bazaar/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-bazaar + title: '@backstage/plugin-bazaar' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/bitbucket-cloud-common/catalog-info.yaml b/plugins/bitbucket-cloud-common/catalog-info.yaml new file mode 100644 index 0000000000..1a931c06cb --- /dev/null +++ b/plugins/bitbucket-cloud-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-bitbucket-cloud-common + title: '@backstage/plugin-bitbucket-cloud-common' + description: Common functionalities for bitbucket-cloud plugins +spec: + lifecycle: experimental + type: backstage-common-library + owner: pjungermann diff --git a/plugins/bitrise/catalog-info.yaml b/plugins/bitrise/catalog-info.yaml new file mode 100644 index 0000000000..19df0a6736 --- /dev/null +++ b/plugins/bitrise/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-bitrise + title: '@backstage/plugin-bitrise' + description: A Backstage plugin that integrates towards Bitrise +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: sda-se-reviewers diff --git a/plugins/catalog-backend-module-aws/catalog-info.yaml b/plugins/catalog-backend-module-aws/catalog-info.yaml new file mode 100644 index 0000000000..8bff4a9a37 --- /dev/null +++ b/plugins/catalog-backend-module-aws/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-catalog-backend-module-aws + title: '@backstage/plugin-catalog-backend-module-aws' + description: A Backstage catalog backend module that helps integrate towards AWS +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: pjungermann diff --git a/plugins/catalog-backend-module-bitbucket-cloud/catalog-info.yaml b/plugins/catalog-backend-module-bitbucket-cloud/catalog-info.yaml new file mode 100644 index 0000000000..99fc6661db --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-catalog-backend-module-bitbucket-cloud + title: '@backstage/plugin-catalog-backend-module-bitbucket-cloud' + description: >- + A Backstage catalog backend module that helps integrate towards Bitbucket + Cloud +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: pjungermann diff --git a/plugins/catalog-backend-module-msgraph/catalog-info.yaml b/plugins/catalog-backend-module-msgraph/catalog-info.yaml new file mode 100644 index 0000000000..acaf5e73b9 --- /dev/null +++ b/plugins/catalog-backend-module-msgraph/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-catalog-backend-module-msgraph + title: '@backstage/plugin-catalog-backend-module-msgraph' + description: >- + A Backstage catalog backend module that helps integrate towards Microsoft + Graph +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: pjungermann diff --git a/plugins/catalog-backend-module-puppetdb/catalog-info.yaml b/plugins/catalog-backend-module-puppetdb/catalog-info.yaml new file mode 100644 index 0000000000..11021ea68f --- /dev/null +++ b/plugins/catalog-backend-module-puppetdb/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-catalog-backend-module-puppetdb + title: '@backstage/plugin-catalog-backend-module-puppetdb' + description: A Backstage catalog backend module that helps integrate towards PuppetDB +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: catalog-maintainers diff --git a/plugins/catalog-backend/src/processing/TaskPipeline.test.ts b/plugins/catalog-backend/src/processing/TaskPipeline.test.ts index f814e8a608..43ec118bdb 100644 --- a/plugins/catalog-backend/src/processing/TaskPipeline.test.ts +++ b/plugins/catalog-backend/src/processing/TaskPipeline.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { startTaskPipeline } from './TaskPipeline'; +import { startTaskPipeline, createBarrier } from './TaskPipeline'; function createLimitedLoader(count: number, loadDelay?: number) { const items = new Array(count).fill(0).map((_, index) => index); @@ -119,3 +119,88 @@ describe('startTaskPipeline', () => { }).toThrow('must be lower'); }); }); + +describe('createBarrier', () => { + const tick = (millis: number) => + new Promise(resolve => setTimeout(resolve, millis)); + + it('abandons a wait after the timeout expires', async () => { + const abortController = new AbortController(); + const signal = abortController.signal; + const barrier = createBarrier({ waitTimeoutMillis: 100, signal }); + + const fn1 = jest.fn(); + barrier.wait().then(fn1); + + await tick(0); + expect(fn1).not.toHaveBeenCalled(); + + await tick(50); + expect(fn1).not.toHaveBeenCalled(); + + // start a new wait mid-way through the timeout + // should NOT resolve when the first one times out + const fn2 = jest.fn(); + barrier.wait().then(fn2); + + await tick(0); + expect(fn2).not.toHaveBeenCalled(); + + await tick(50); + expect(fn1).toHaveBeenCalledTimes(1); + expect(fn2).not.toHaveBeenCalled(); + + await tick(50); + expect(fn1).toHaveBeenCalledTimes(1); + expect(fn2).toHaveBeenCalledTimes(1); + }); + + it('abandons a wait after aborted', async () => { + const abortController = new AbortController(); + const signal = abortController.signal; + const barrier = createBarrier({ waitTimeoutMillis: 100, signal }); + + const fn1 = jest.fn(); + barrier.wait().then(fn1); + + // should resolve immediately, not after timeout + await tick(0); + expect(fn1).not.toHaveBeenCalled(); + abortController.abort(); + await tick(0); + expect(fn1).toHaveBeenCalledTimes(1); + + // subsequent waits should be immediate no matter what + const fn2 = jest.fn(); + barrier.wait().then(fn2); + await tick(0); + expect(fn2).toHaveBeenCalledTimes(1); + }); + + it('release immediately unblocks all waits', async () => { + const abortController = new AbortController(); + const signal = abortController.signal; + const barrier = createBarrier({ waitTimeoutMillis: 100, signal }); + + const fn1 = jest.fn(); + barrier.wait().then(fn1); + + await tick(50); + expect(fn1).not.toHaveBeenCalled(); + + // start a new wait mid-way through the timeout + // SHOULD resolve when releasing + const fn2 = jest.fn(); + barrier.wait().then(fn2); + + await tick(0); + expect(fn1).not.toHaveBeenCalled(); + expect(fn2).not.toHaveBeenCalled(); + + barrier.release(); + + await tick(0); + expect(fn1).toHaveBeenCalledTimes(1); + expect(fn2).toHaveBeenCalledTimes(1); + }); +}); diff --git a/plugins/catalog-backend/src/processing/TaskPipeline.ts b/plugins/catalog-backend/src/processing/TaskPipeline.ts index 4afe2408a8..44030be9e0 100644 --- a/plugins/catalog-backend/src/processing/TaskPipeline.ts +++ b/plugins/catalog-backend/src/processing/TaskPipeline.ts @@ -72,50 +72,112 @@ export function startTaskPipeline(options: Options) { throw new Error('lowWatermark must be lower than highWatermark'); } - let loading = false; - let stopped = false; - let inFlightCount = 0; + // State is in an object so that it can be stably referenced from within + // callbacks below + const state = { inFlightCount: 0 }; + const abortController = new AbortController(); + const abortSignal = abortController.signal; - async function maybeLoadMore() { - if (stopped || loading || inFlightCount > lowWatermark) { - return; - } + const barrier = createBarrier({ + waitTimeoutMillis: pollingIntervalMs, + signal: abortSignal, + }); - // Once we hit the low watermark we load in enough items to reach the high watermark - loading = true; - const loadCount = highWatermark - inFlightCount; - const loadedItems = await loadTasks(loadCount); - loading = false; - - // We might not reach the high watermark here, in case there weren't enough items to load - inFlightCount += loadedItems.length; - loadedItems.forEach(item => { - processTask(item).finally(() => { - if (stopped) { - return; + async function pipelineLoop() { + while (!abortSignal.aborted) { + if (state.inFlightCount <= lowWatermark) { + const loadCount = highWatermark - state.inFlightCount; + const loadedItems = await Promise.resolve() + .then(() => loadTasks(loadCount)) + .catch(() => { + // Silently swallow errors and go back to sleep to try again; we + // delegate to the loadTasks function itself to catch errors and log + // if it so desires + return []; + }); + if (loadedItems.length && !abortSignal.aborted) { + state.inFlightCount += loadedItems.length; + for (const item of loadedItems) { + Promise.resolve() + .then(() => processTask(item)) + .catch(() => { + // Silently swallow errors and go back to sleep to try again; we + // delegate to the processTask function itself to catch errors + // and log if it so desires + }) + .finally(() => { + state.inFlightCount -= 1; + barrier.release(); + }); + } } + } - // For each item we complete we check if it's time to load more - inFlightCount -= 1; - maybeLoadMore(); - }); - }); - - // We might have processed some tasks while we where loading, so check if we can load more - if (loadedItems.length > 1) { - maybeLoadMore(); + await barrier.wait(); } } - // This interval makes sure that we load in new items if the loop runs - // dry because of the lack of available tasks. As long as there are - // enough items to process this will be a noop. - const intervalId = setInterval(() => { - maybeLoadMore(); - }, pollingIntervalMs); + pipelineLoop().catch(error => { + // This should be impossible, but if it did happen, it would signal a + // programming error inside the loop (errors should definitely be caught + // inside of it). Let's rethrow with more information, and let it be caught + // by the process' uncaught exception handler, which will log the occurrence + // at a high level. + throw new Error(`Unexpected error in processing pipeline loop`, error); + }); return () => { - stopped = true; - clearInterval(intervalId); + abortController.abort(); + barrier.destroy(); + }; +} + +/** + * Creates a barrier with a timeout, that can be awaited or prematurely + * released either manually or by an abort signal. + */ +export function createBarrier(options: { + waitTimeoutMillis: number; + signal: AbortSignal; +}): { + wait: () => Promise; + release: () => void; + destroy: () => void; +} { + const { waitTimeoutMillis, signal } = options; + const resolvers = new Set<() => void>(); + + function wait() { + if (signal.aborted || !(waitTimeoutMillis > 0)) { + return Promise.resolve(); + } + + return new Promise(resolve => { + const timeoutHandle = setTimeout(done, waitTimeoutMillis); + + function done() { + resolvers.delete(done); + clearTimeout(timeoutHandle); + resolve(); + } + + resolvers.add(done); + }); + } + + function release() { + const resolversToCall = new Set(resolvers); + resolvers.clear(); + for (const resolver of resolversToCall) { + resolver(); + } + } + + signal.addEventListener('abort', release); + + return { + wait, + release, + destroy: () => signal.removeEventListener('abort', release), }; } diff --git a/plugins/catalog-backend/src/schema/openapi.generated.ts b/plugins/catalog-backend/src/schema/openapi.generated.ts index 3a56893b48..5c9b2773e9 100644 --- a/plugins/catalog-backend/src/schema/openapi.generated.ts +++ b/plugins/catalog-backend/src/schema/openapi.generated.ts @@ -45,6 +45,7 @@ export const spec = { name: 'kind', in: 'path', required: true, + allowReserved: true, schema: { type: 'string', }, @@ -53,6 +54,7 @@ export const spec = { name: 'namespace', in: 'path', required: true, + allowReserved: true, schema: { type: 'string', }, @@ -61,6 +63,7 @@ export const spec = { name: 'name', in: 'path', required: true, + allowReserved: true, schema: { type: 'string', }, @@ -69,6 +72,7 @@ export const spec = { name: 'uid', in: 'path', required: true, + allowReserved: true, schema: { type: 'string', }, @@ -78,6 +82,7 @@ export const spec = { in: 'query', description: 'Cursor to a set page of results.', required: false, + allowReserved: true, schema: { type: 'string', minLength: 1, @@ -88,6 +93,7 @@ export const spec = { in: 'query', description: 'Pointer to the previous page of results.', required: false, + allowReserved: true, schema: { type: 'string', minLength: 1, @@ -111,6 +117,7 @@ export const spec = { in: 'query', description: 'Filter for just the entities defined by this filter.', required: false, + allowReserved: true, schema: { type: 'array', items: { @@ -123,6 +130,7 @@ export const spec = { in: 'query', description: 'Number of records to skip in the query page.', required: false, + allowReserved: true, schema: { type: 'integer', minimum: 0, @@ -133,6 +141,7 @@ export const spec = { in: 'query', description: 'Number of records to return in the response.', required: false, + allowReserved: true, schema: { type: 'integer', minimum: 0, @@ -1037,6 +1046,7 @@ export const spec = { in: 'query', description: 'Text search term.', required: false, + allowReserved: true, schema: { type: 'string', }, @@ -1047,6 +1057,7 @@ export const spec = { description: 'A comma separated list of fields to sort returned results by.', required: false, + allowReserved: true, schema: { type: 'array', items: { @@ -1086,6 +1097,7 @@ export const spec = { in: 'query', name: 'facet', required: true, + allowReserved: true, schema: { type: 'array', items: { @@ -1141,6 +1153,7 @@ export const spec = { in: 'query', name: 'dryRun', required: false, + allowReserved: true, schema: { type: 'string', }, @@ -1226,6 +1239,7 @@ export const spec = { in: 'path', name: 'id', required: true, + allowReserved: true, schema: { type: 'string', }, @@ -1251,6 +1265,7 @@ export const spec = { in: 'path', name: 'id', required: true, + allowReserved: true, schema: { type: 'string', }, @@ -1295,7 +1310,7 @@ export const spec = { $ref: '#/components/schemas/LocationInput', }, }, - required: ['catalogFileName', 'location'], + required: ['location'], }, }, }, diff --git a/plugins/catalog-backend/src/schema/openapi.yaml b/plugins/catalog-backend/src/schema/openapi.yaml index e0b60b1a98..52321399c6 100644 --- a/plugins/catalog-backend/src/schema/openapi.yaml +++ b/plugins/catalog-backend/src/schema/openapi.yaml @@ -20,24 +20,28 @@ components: name: kind in: path required: true + allowReserved: true schema: type: string namespace: name: namespace in: path required: true + allowReserved: true schema: type: string name: name: name in: path required: true + allowReserved: true schema: type: string uid: name: uid in: path required: true + allowReserved: true schema: type: string cursor: @@ -45,6 +49,7 @@ components: in: query description: Cursor to a set page of results. required: false + allowReserved: true schema: type: string minLength: 1 @@ -53,6 +58,7 @@ components: in: query description: Pointer to the previous page of results. required: false + allowReserved: true schema: type: string minLength: 1 @@ -71,6 +77,7 @@ components: in: query description: Filter for just the entities defined by this filter. required: false + allowReserved: true schema: type: array items: @@ -80,6 +87,7 @@ components: in: query description: Number of records to skip in the query page. required: false + allowReserved: true schema: type: integer minimum: 0 @@ -88,6 +96,7 @@ components: in: query description: Number of records to return in the response. required: false + allowReserved: true schema: type: integer minimum: 0 @@ -788,12 +797,14 @@ paths: in: query description: Text search term. required: false + allowReserved: true schema: type: string - name: fullTextFilterFields in: query description: A comma separated list of fields to sort returned results by. required: false + allowReserved: true schema: type: array items: @@ -818,6 +829,7 @@ paths: - in: query name: facet required: true + allowReserved: true schema: type: array items: @@ -853,6 +865,7 @@ paths: - in: query name: dryRun required: false + allowReserved: true schema: type: string requestBody: @@ -908,6 +921,7 @@ paths: - in: path name: id required: true + allowReserved: true schema: type: string delete: @@ -923,6 +937,7 @@ paths: - in: path name: id required: true + allowReserved: true schema: type: string /analyze-location: @@ -952,7 +967,6 @@ paths: location: $ref: '#/components/schemas/LocationInput' required: - - catalogFileName - location /validate-entity: post: diff --git a/plugins/catalog-backend/src/service/request/parseQueryEntitiesParams.test.ts b/plugins/catalog-backend/src/service/request/parseQueryEntitiesParams.test.ts index dfcfd58c7e..e11656681a 100644 --- a/plugins/catalog-backend/src/service/request/parseQueryEntitiesParams.test.ts +++ b/plugins/catalog-backend/src/service/request/parseQueryEntitiesParams.test.ts @@ -28,11 +28,11 @@ describe('parseQueryEntitiesParams', () => { const validRequest = { authorizationToken: 'to_not_be_returned', fields: ['kind'], - limit: '3', + limit: 3, filter: ['a=1', 'b=2'], orderField: ['metadata.name,desc'], fullTextFilterTerm: 'query', - fullTextFilterFields: 'metadata.name,metadata.namespace', + fullTextFilterFields: ['metadata.name', 'metadata.namespace'], }; const parsedObj = parseQueryEntitiesParams( validRequest, @@ -65,9 +65,7 @@ describe('parseQueryEntitiesParams', () => { { filter: 3 }, { orderField: ['metadata.uid,diagonal'] }, { fields: [4] }, - { fullTextFilterTerm: [] }, - { fullTextFilterFields: 3 }, - ])('should throw if some parameter is not valid %p', params => { + ])('should throw if some parameter is not valid %p', (params: any) => { expect(() => parseQueryEntitiesParams(params)).toThrow(); }); }); @@ -83,7 +81,7 @@ describe('parseQueryEntitiesParams', () => { const validRequest = { authorizationToken: 'to_not_be_returned', fields: ['kind'], - limit: '3', + limit: 3, cursor: encodeCursor(cursor), }; const parsedObj = parseQueryEntitiesParams( @@ -103,10 +101,10 @@ describe('parseQueryEntitiesParams', () => { const validRequest = { authorizationToken: 'to_not_be_returned', fields: ['kind'], - limit: '3', + limit: 3, cursor: encodeCursor(cursor), filter: ['a=1', 'b=2'], - orderField: 'orderField,desc', + orderField: ['orderField,desc'], query: 'query', }; const parsedObj = parseQueryEntitiesParams( @@ -128,7 +126,7 @@ describe('parseQueryEntitiesParams', () => { it.each([{ cursor: [] }, { fields: [4] }])( 'should throw if some parameter is not valid %p', - params => { + (params: any) => { expect(() => parseQueryEntitiesParams(params)).toThrow(); }, ); diff --git a/plugins/catalog-backend/src/service/request/parseQueryEntitiesParams.ts b/plugins/catalog-backend/src/service/request/parseQueryEntitiesParams.ts index 9b55f193b9..b1d5e8dd1f 100644 --- a/plugins/catalog-backend/src/service/request/parseQueryEntitiesParams.ts +++ b/plugins/catalog-backend/src/service/request/parseQueryEntitiesParams.ts @@ -20,19 +20,19 @@ import { QueryEntitiesRequest, } from '../../catalog/types'; import { decodeCursor } from '../util'; -import { parseStringParam } from './common'; import { parseEntityFilterParams } from './parseEntityFilterParams'; import { parseEntityOrderFieldParams } from './parseEntityOrderFieldParams'; import { parseEntityTransformParams } from './parseEntityTransformParams'; -import { parseFullTextFilterFields } from './parseFullTextFilterFields'; +import { spec } from '../../schema/openapi.generated'; +import { internal } from '@backstage/backend-openapi-utils'; export function parseQueryEntitiesParams( - params: Record, + params: internal.QuerySchema, ): Omit { const fields = parseEntityTransformParams(params); - const cursor = parseStringParam(params.cursor, 'cursor'); - if (cursor) { - const decodedCursor = decodeCursor(cursor); + + if (params.cursor) { + const decodedCursor = decodeCursor(params.cursor); const response: Omit = { cursor: decodedCursor, fields, @@ -41,12 +41,6 @@ export function parseQueryEntitiesParams( } const filter = parseEntityFilterParams(params); - const fullTextFilterTerm = parseStringParam( - params.fullTextFilterTerm, - 'fullTextFilterTerm', - ); - const fullTextFilterFields = parseFullTextFilterFields(params); - const orderFields = parseEntityOrderFieldParams(params); const response: Omit = { @@ -54,8 +48,8 @@ export function parseQueryEntitiesParams( filter, orderFields, fullTextFilter: { - term: fullTextFilterTerm || '', - fields: fullTextFilterFields, + term: params.fullTextFilterTerm || '', + fields: params.fullTextFilterFields, }, }; diff --git a/plugins/catalog-graph/catalog-info.yaml b/plugins/catalog-graph/catalog-info.yaml new file mode 100644 index 0000000000..6dda99634b --- /dev/null +++ b/plugins/catalog-graph/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-catalog-graph + title: '@backstage/plugin-catalog-graph' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: sda-se-reviewers diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx index baa4b3732c..7f139854f6 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx @@ -48,6 +48,10 @@ describe('UnregisterEntityDialog', () => { }, }; + beforeEach(() => { + jest.spyOn(alertApi, 'post').mockImplementation(() => {}); + }); + const entity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Component', @@ -346,6 +350,11 @@ describe('UnregisterEntityDialog', () => { await waitFor(() => { expect(deleteEntity).toHaveBeenCalled(); expect(onConfirm).toHaveBeenCalled(); + expect(alertApi.post).toHaveBeenCalledWith({ + message: 'Removed entity n', + severity: 'success', + display: 'transient', + }); }); }); }); diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx index 6c4984129e..56689c71ce 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx @@ -85,7 +85,13 @@ const Contents = ({ setBusy(true); try { await state.deleteEntity(); + const entityName = entity.metadata.title ?? entity.metadata.name; onConfirm(); + alertApi.post({ + message: `Removed entity ${entityName}`, + severity: 'success', + display: 'transient', + }); } catch (err) { assertError(err); alertApi.post({ message: err.message }); @@ -94,7 +100,7 @@ const Contents = ({ } } }, - [alertApi, onConfirm, state], + [alertApi, onConfirm, state, entity], ); const DialogActionsPanel = () => ( diff --git a/plugins/cicd-statistics-module-gitlab/catalog-info.yaml b/plugins/cicd-statistics-module-gitlab/catalog-info.yaml new file mode 100644 index 0000000000..abfe402948 --- /dev/null +++ b/plugins/cicd-statistics-module-gitlab/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-cicd-statistics-module-gitlab + title: '@backstage/plugin-cicd-statistics-module-gitlab' + description: CI/CD Statistics plugin module; Gitlab CICD +spec: + lifecycle: experimental + type: backstage-frontend-plugin-module + owner: maintainers diff --git a/plugins/cicd-statistics/catalog-info.yaml b/plugins/cicd-statistics/catalog-info.yaml new file mode 100644 index 0000000000..83021cc033 --- /dev/null +++ b/plugins/cicd-statistics/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-cicd-statistics + title: '@backstage/plugin-cicd-statistics' + description: A frontend plugin visualizing CI/CD pipeline statistics (build time) +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/circleci/catalog-info.yaml b/plugins/circleci/catalog-info.yaml new file mode 100644 index 0000000000..1a02a270a9 --- /dev/null +++ b/plugins/circleci/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-circleci + title: '@backstage/plugin-circleci' + description: A Backstage plugin that integrates towards Circle CI +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: adamdmharvey diff --git a/plugins/cloudbuild/catalog-info.yaml b/plugins/cloudbuild/catalog-info.yaml new file mode 100644 index 0000000000..ffe625a5e3 --- /dev/null +++ b/plugins/cloudbuild/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-cloudbuild + title: '@backstage/plugin-cloudbuild' + description: A Backstage plugin that integrates towards Google Cloud Build +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/code-climate/catalog-info.yaml b/plugins/code-climate/catalog-info.yaml new file mode 100644 index 0000000000..8797ec0bfd --- /dev/null +++ b/plugins/code-climate/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-code-climate + title: '@backstage/plugin-code-climate' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/code-coverage-backend/catalog-info.yaml b/plugins/code-coverage-backend/catalog-info.yaml new file mode 100644 index 0000000000..3c0cdaebbf --- /dev/null +++ b/plugins/code-coverage-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-code-coverage-backend + title: '@backstage/plugin-code-coverage-backend' + description: A Backstage backend plugin that helps you keep track of your code coverage +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: alde diff --git a/plugins/code-coverage/catalog-info.yaml b/plugins/code-coverage/catalog-info.yaml new file mode 100644 index 0000000000..27887c89aa --- /dev/null +++ b/plugins/code-coverage/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-code-coverage + title: '@backstage/plugin-code-coverage' + description: A Backstage plugin that helps you keep track of your code coverage +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: alde diff --git a/plugins/codescene/catalog-info.yaml b/plugins/codescene/catalog-info.yaml new file mode 100644 index 0000000000..5904add837 --- /dev/null +++ b/plugins/codescene/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-codescene + title: '@backstage/plugin-codescene' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/config-schema/catalog-info.yaml b/plugins/config-schema/catalog-info.yaml new file mode 100644 index 0000000000..fbea2be520 --- /dev/null +++ b/plugins/config-schema/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-config-schema + title: '@backstage/plugin-config-schema' + description: A Backstage plugin that lets you browse the configuration schema of your app +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/cost-insights-common/catalog-info.yaml b/plugins/cost-insights-common/catalog-info.yaml new file mode 100644 index 0000000000..027726d1a9 --- /dev/null +++ b/plugins/cost-insights-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-cost-insights-common + title: '@backstage/plugin-cost-insights-common' + description: Common functionalities for the cost-insights plugin +spec: + lifecycle: experimental + type: backstage-common-library + owner: silver-lining diff --git a/plugins/cost-insights/catalog-info.yaml b/plugins/cost-insights/catalog-info.yaml new file mode 100644 index 0000000000..7cfeb607a8 --- /dev/null +++ b/plugins/cost-insights/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-cost-insights + title: '@backstage/plugin-cost-insights' + description: A Backstage plugin that helps you keep track of your cloud spend +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: silver-lining diff --git a/plugins/devtools-backend/catalog-info.yaml b/plugins/devtools-backend/catalog-info.yaml new file mode 100644 index 0000000000..29e91c4ad7 --- /dev/null +++ b/plugins/devtools-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-devtools-backend + title: '@backstage/plugin-devtools-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: awanlin diff --git a/plugins/devtools-common/catalog-info.yaml b/plugins/devtools-common/catalog-info.yaml new file mode 100644 index 0000000000..af2bcc4a1b --- /dev/null +++ b/plugins/devtools-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-devtools-common + title: '@backstage/plugin-devtools-common' + description: Common functionalities for the devtools plugin +spec: + lifecycle: experimental + type: backstage-common-library + owner: awanlin diff --git a/plugins/devtools/catalog-info.yaml b/plugins/devtools/catalog-info.yaml new file mode 100644 index 0000000000..59bcd82497 --- /dev/null +++ b/plugins/devtools/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-devtools + title: '@backstage/plugin-devtools' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: awanlin diff --git a/plugins/dynatrace/catalog-info.yaml b/plugins/dynatrace/catalog-info.yaml new file mode 100644 index 0000000000..3dc14c96ff --- /dev/null +++ b/plugins/dynatrace/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-dynatrace + title: '@backstage/plugin-dynatrace' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/entity-feedback-backend/catalog-info.yaml b/plugins/entity-feedback-backend/catalog-info.yaml new file mode 100644 index 0000000000..4973f67edc --- /dev/null +++ b/plugins/entity-feedback-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-entity-feedback-backend + title: '@backstage/plugin-entity-feedback-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: kuangp diff --git a/plugins/entity-feedback-common/catalog-info.yaml b/plugins/entity-feedback-common/catalog-info.yaml new file mode 100644 index 0000000000..01035f68fd --- /dev/null +++ b/plugins/entity-feedback-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-entity-feedback-common + title: '@backstage/plugin-entity-feedback-common' + description: Common functionalities for the entity-feedback plugin +spec: + lifecycle: experimental + type: backstage-common-library + owner: kuangp diff --git a/plugins/entity-feedback/catalog-info.yaml b/plugins/entity-feedback/catalog-info.yaml new file mode 100644 index 0000000000..57961ad954 --- /dev/null +++ b/plugins/entity-feedback/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-entity-feedback + title: '@backstage/plugin-entity-feedback' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: kuangp diff --git a/plugins/entity-validation/catalog-info.yaml b/plugins/entity-validation/catalog-info.yaml new file mode 100644 index 0000000000..d78b0296f3 --- /dev/null +++ b/plugins/entity-validation/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-entity-validation + title: '@backstage/plugin-entity-validation' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/events-backend-module-aws-sqs/catalog-info.yaml b/plugins/events-backend-module-aws-sqs/catalog-info.yaml new file mode 100644 index 0000000000..bf37805728 --- /dev/null +++ b/plugins/events-backend-module-aws-sqs/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-backend-module-aws-sqs + title: '@backstage/plugin-events-backend-module-aws-sqs' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: pjungermann diff --git a/plugins/events-backend-module-azure/catalog-info.yaml b/plugins/events-backend-module-azure/catalog-info.yaml new file mode 100644 index 0000000000..e5627e7383 --- /dev/null +++ b/plugins/events-backend-module-azure/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-backend-module-azure + title: '@backstage/plugin-events-backend-module-azure' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: pjungermann diff --git a/plugins/events-backend-module-bitbucket-cloud/catalog-info.yaml b/plugins/events-backend-module-bitbucket-cloud/catalog-info.yaml new file mode 100644 index 0000000000..1b73e0dab7 --- /dev/null +++ b/plugins/events-backend-module-bitbucket-cloud/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-backend-module-bitbucket-cloud + title: '@backstage/plugin-events-backend-module-bitbucket-cloud' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: pjungermann diff --git a/plugins/events-backend-module-gerrit/catalog-info.yaml b/plugins/events-backend-module-gerrit/catalog-info.yaml new file mode 100644 index 0000000000..482e8e2a74 --- /dev/null +++ b/plugins/events-backend-module-gerrit/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-backend-module-gerrit + title: '@backstage/plugin-events-backend-module-gerrit' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: pjungermann diff --git a/plugins/events-backend-module-github/catalog-info.yaml b/plugins/events-backend-module-github/catalog-info.yaml new file mode 100644 index 0000000000..4ca67b7048 --- /dev/null +++ b/plugins/events-backend-module-github/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-backend-module-github + title: '@backstage/plugin-events-backend-module-github' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: pjungermann diff --git a/plugins/events-backend-module-gitlab/catalog-info.yaml b/plugins/events-backend-module-gitlab/catalog-info.yaml new file mode 100644 index 0000000000..c8ab030d04 --- /dev/null +++ b/plugins/events-backend-module-gitlab/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-backend-module-gitlab + title: '@backstage/plugin-events-backend-module-gitlab' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: pjungermann diff --git a/plugins/events-backend-test-utils/catalog-info.yaml b/plugins/events-backend-test-utils/catalog-info.yaml new file mode 100644 index 0000000000..a899f973c5 --- /dev/null +++ b/plugins/events-backend-test-utils/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-backend-test-utils + title: '@backstage/plugin-events-backend-test-utils' + description: The plugin-events-backend-test-utils for @backstage/plugin-events-node +spec: + lifecycle: experimental + type: backstage-node-library + owner: pjungermann diff --git a/plugins/events-backend/catalog-info.yaml b/plugins/events-backend/catalog-info.yaml new file mode 100644 index 0000000000..7fd8d058cc --- /dev/null +++ b/plugins/events-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-backend + title: '@backstage/plugin-events-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: pjungermann diff --git a/plugins/events-node/catalog-info.yaml b/plugins/events-node/catalog-info.yaml new file mode 100644 index 0000000000..50a1dfe57b --- /dev/null +++ b/plugins/events-node/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-events-node + title: '@backstage/plugin-events-node' + description: The plugin-events-node module for @backstage/plugin-events-backend +spec: + lifecycle: experimental + type: backstage-node-library + owner: pjungermann diff --git a/plugins/example-todo-list-backend/catalog-info.yaml b/plugins/example-todo-list-backend/catalog-info.yaml new file mode 100644 index 0000000000..144837d5bb --- /dev/null +++ b/plugins/example-todo-list-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: internal-plugin-todo-list-backend + title: '@internal/plugin-todo-list-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/example-todo-list-common/catalog-info.yaml b/plugins/example-todo-list-common/catalog-info.yaml new file mode 100644 index 0000000000..dad155eda1 --- /dev/null +++ b/plugins/example-todo-list-common/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: internal-plugin-todo-list-common + title: '@internal/plugin-todo-list-common' +spec: + lifecycle: experimental + type: backstage-common-library + owner: maintainers diff --git a/plugins/example-todo-list/catalog-info.yaml b/plugins/example-todo-list/catalog-info.yaml new file mode 100644 index 0000000000..3774f01103 --- /dev/null +++ b/plugins/example-todo-list/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: internal-plugin-todo-list + title: '@internal/plugin-todo-list' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/explore-backend/catalog-info.yaml b/plugins/explore-backend/catalog-info.yaml new file mode 100644 index 0000000000..a74142843f --- /dev/null +++ b/plugins/explore-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-explore-backend + title: '@backstage/plugin-explore-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/explore-common/catalog-info.yaml b/plugins/explore-common/catalog-info.yaml new file mode 100644 index 0000000000..c4bf3e493f --- /dev/null +++ b/plugins/explore-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-explore-common + title: '@backstage/plugin-explore-common' + description: Common functionalities for the explore plugin +spec: + lifecycle: experimental + type: backstage-common-library + owner: maintainers diff --git a/plugins/explore-react/catalog-info.yaml b/plugins/explore-react/catalog-info.yaml new file mode 100644 index 0000000000..d65bbc7d8d --- /dev/null +++ b/plugins/explore-react/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-explore-react + title: '@backstage/plugin-explore-react' + description: >- + A frontend library for Backstage plugins that want to interact with the + explore plugin +spec: + lifecycle: experimental + type: backstage-web-library + owner: sda-se-reviewers diff --git a/plugins/explore/catalog-info.yaml b/plugins/explore/catalog-info.yaml new file mode 100644 index 0000000000..031dedfb3f --- /dev/null +++ b/plugins/explore/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-explore + title: '@backstage/plugin-explore' + description: >- + A Backstage plugin for building an exploration page of your software + ecosystem +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: sda-se-reviewers diff --git a/plugins/firehydrant/catalog-info.yaml b/plugins/firehydrant/catalog-info.yaml new file mode 100644 index 0000000000..89661726d3 --- /dev/null +++ b/plugins/firehydrant/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-firehydrant + title: '@backstage/plugin-firehydrant' + description: A Backstage plugin that integrates towards FireHydrant +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/fossa/catalog-info.yaml b/plugins/fossa/catalog-info.yaml new file mode 100644 index 0000000000..ac705ea660 --- /dev/null +++ b/plugins/fossa/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-fossa + title: '@backstage/plugin-fossa' + description: A Backstage plugin that integrates towards FOSSA +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: sda-se-reviewers diff --git a/plugins/gcalendar/catalog-info.yaml b/plugins/gcalendar/catalog-info.yaml new file mode 100644 index 0000000000..b0c371cf5a --- /dev/null +++ b/plugins/gcalendar/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-gcalendar + title: '@backstage/plugin-gcalendar' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/gcp-projects/catalog-info.yaml b/plugins/gcp-projects/catalog-info.yaml new file mode 100644 index 0000000000..53ea7f4f50 --- /dev/null +++ b/plugins/gcp-projects/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-gcp-projects + title: '@backstage/plugin-gcp-projects' + description: A Backstage plugin that helps you manage projects in GCP +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/git-release-manager/catalog-info.yaml b/plugins/git-release-manager/catalog-info.yaml new file mode 100644 index 0000000000..a7c21731ab --- /dev/null +++ b/plugins/git-release-manager/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-git-release-manager + title: '@backstage/plugin-git-release-manager' + description: A Backstage plugin that helps you manage releases in git +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: erikengervall diff --git a/plugins/github-actions/catalog-info.yaml b/plugins/github-actions/catalog-info.yaml new file mode 100644 index 0000000000..712b2f4be5 --- /dev/null +++ b/plugins/github-actions/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-github-actions + title: '@backstage/plugin-github-actions' + description: A Backstage plugin that integrates towards GitHub Actions +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/github-deployments/catalog-info.yaml b/plugins/github-deployments/catalog-info.yaml new file mode 100644 index 0000000000..43b901252f --- /dev/null +++ b/plugins/github-deployments/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-github-deployments + title: '@backstage/plugin-github-deployments' + description: A Backstage plugin that integrates towards GitHub Deployments +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/github-issues/catalog-info.yaml b/plugins/github-issues/catalog-info.yaml new file mode 100644 index 0000000000..252bfe2a67 --- /dev/null +++ b/plugins/github-issues/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-github-issues + title: '@backstage/plugin-github-issues' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/github-pull-requests-board/catalog-info.yaml b/plugins/github-pull-requests-board/catalog-info.yaml new file mode 100644 index 0000000000..c94722d538 --- /dev/null +++ b/plugins/github-pull-requests-board/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-github-pull-requests-board + title: '@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 +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/gitops-profiles/catalog-info.yaml b/plugins/gitops-profiles/catalog-info.yaml new file mode 100644 index 0000000000..9c8d04d5a5 --- /dev/null +++ b/plugins/gitops-profiles/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-gitops-profiles + title: '@backstage/plugin-gitops-profiles' + description: A Backstage plugin that helps you manage GitOps profiles +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/gocd/catalog-info.yaml b/plugins/gocd/catalog-info.yaml new file mode 100644 index 0000000000..ec85e55f61 --- /dev/null +++ b/plugins/gocd/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-gocd + title: '@backstage/plugin-gocd' + description: A Backstage plugin that integrates towards GoCD +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/graphiql/catalog-info.yaml b/plugins/graphiql/catalog-info.yaml new file mode 100644 index 0000000000..628d6d633e --- /dev/null +++ b/plugins/graphiql/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-graphiql + title: '@backstage/plugin-graphiql' + description: Backstage plugin for browsing GraphQL APIs +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/graphql-backend/catalog-info.yaml b/plugins/graphql-backend/catalog-info.yaml new file mode 100644 index 0000000000..4545c0def5 --- /dev/null +++ b/plugins/graphql-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-graphql-backend + title: '@backstage/plugin-graphql-backend' + description: An experimental Backstage backend plugin for GraphQL +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/graphql-voyager/catalog-info.yaml b/plugins/graphql-voyager/catalog-info.yaml new file mode 100644 index 0000000000..649454d9c4 --- /dev/null +++ b/plugins/graphql-voyager/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-graphql-voyager + title: '@backstage/plugin-graphql-voyager' + description: Backstage plugin for GraphQL Voyager +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/home/README.md b/plugins/home/README.md index 57c2a4d243..e147cdfbdf 100644 --- a/plugins/home/README.md +++ b/plugins/home/README.md @@ -233,6 +233,9 @@ const defaultConfig = [ y: 0, width: 12, height: 1, + movable: true, + resizable: false, + deletable: false, }, ]; diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index c5e458f78f..450bc054fa 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -148,6 +148,9 @@ export type LayoutConfiguration = { y: number; width: number; height: number; + movable?: boolean; + deletable?: boolean; + resizable?: boolean; }; // @public @deprecated (undocumented) diff --git a/plugins/home/package.json b/plugins/home/package.json index f732558a3b..60cc3f5f80 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -52,7 +52,7 @@ "react-grid-layout": "^1.3.4", "react-resizable": "^3.0.4", "react-use": "^17.2.4", - "zod": "~3.21.4" + "zod": "^3.21.4" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0", diff --git a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx index f5ee8f3ddc..88c3273ff3 100644 --- a/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx +++ b/plugins/home/src/components/CustomHomepage/CustomHomepageGrid.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { ReactNode, useCallback, useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; import { Layout, Layouts, Responsive, WidthProvider } from 'react-grid-layout'; import { ElementCollection, @@ -40,6 +40,7 @@ import { WidgetSettingsOverlay } from './WidgetSettingsOverlay'; import { AddWidgetDialog } from './AddWidgetDialog'; import { CustomHomepageButtons } from './CustomHomepageButtons'; import { + CustomHomepageGridProps, CustomHomepageGridStateV1, CustomHomepageGridStateV1Schema, GridWidget, @@ -158,6 +159,9 @@ const convertConfigToDefaultWidgets = ( isResizable: false, }, settings: {}, + movable: conf.movable, + deletable: conf.deletable, + resizable: conf.resizable, }; }); return compact(ret); @@ -190,82 +194,6 @@ const availableWidgetsFilter = (elements: ElementCollection) => { }); }; -/** - * Breakpoint options for - * - * @public - */ -export type Breakpoint = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; - -/** - * Props customizing the component. - * - * @public - */ -export type CustomHomepageGridProps = { - /** - * Children contain all widgets user can configure on their own homepage. - */ - children?: ReactNode; - /** - * Default layout for the homepage before users have modified it. - */ - config?: LayoutConfiguration[]; - /** - * Height of grid row in pixels. - * @defaultValue 60 - */ - rowHeight?: number; - /** - * Screen width in pixels for different breakpoints. - * @defaultValue theme breakpoints - */ - breakpoints?: Record; - /** - * Number of grid columns for different breakpoints. - * @defaultValue \{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 \} - */ - cols?: Record; - /** - * Grid container padding (x, y) in pixels for all or specific breakpoints. - * @defaultValue [0, 0] - * @example [10, 10] - * @example \{ lg: [10, 10] \} - */ - containerPadding?: [number, number] | Record; - /** - * Grid container margin (x, y) in pixels for all or specific breakpoints. - * @defaultValue [0, 0] - * @example [10, 10] - * @example \{ lg: [10, 10] \} - */ - containerMargin?: [number, number] | Record; - /** - * Maximum number of rows user can have in the grid. - * @defaultValue unlimited - */ - maxRows?: number; - /** - * Custom style for grid. - */ - style?: React.CSSProperties; - /** - * Compaction type of widgets in the grid. This controls where widgets are moved in case - * they are overlapping in the grid. - */ - compactType?: 'vertical' | 'horizontal' | null; - /** - * Controls if widgets can overlap in the grid. If true, grid can be placed one over the other. - * @defaultValue false - */ - allowOverlap?: boolean; - /** - * Controls if widgets can collide with each other. If true, grid items won't change position when being dragged over. - * @defaultValue false - */ - preventCollision?: boolean; -}; - /** * A component that allows customizing components in home grid layout. * @@ -318,6 +246,9 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { isDraggable: editMode, }, settings: {}, + movable: widget.movable, + deletable: widget.deletable, + resizable: widget.resizable, }, ]); setAddWidgetDialogOpen(false); @@ -348,9 +279,11 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { setEditMode(mode); setWidgets( widgets.map(w => { + const resizable = w.resizable === false ? false : mode; + const movable = w.movable === false ? false : mode; return { ...w, - layout: { ...w.layout, isDraggable: mode, isResizable: mode }, + layout: { ...w.layout, isDraggable: movable, isResizable: resizable }, }; }), ); @@ -370,7 +303,20 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { }; const handleRestoreDefaultConfig = () => { - setWidgets(defaultLayout); + setWidgets( + defaultLayout.map(w => { + const resizable = w.resizable === false ? false : editMode; + const movable = w.movable === false ? false : editMode; + return { + ...w, + layout: { + ...w.layout, + isDraggable: movable, + isResizable: resizable, + }, + }; + }), + ); }; return ( @@ -404,7 +350,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { style={props.style} allowOverlap={props.allowOverlap} preventCollision={props.preventCollision} - draggableCancel=".overlayGridItem,.widgetSettingsDialog" + draggableCancel=".overlayGridItem,.widgetSettingsDialog,.disabled" containerPadding={props.containerPadding} margin={props.containerMargin} breakpoints={ @@ -435,7 +381,9 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { return (
@@ -447,6 +395,7 @@ export const CustomHomepageGrid = (props: CustomHomepageGridProps) => { handleRemove={handleRemove} handleSettingsSave={handleSettingsSave} settings={w.settings} + deletable={w.deletable} /> )}
diff --git a/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx b/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx index 261dabb833..2c3a1d9ab9 100644 --- a/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx +++ b/plugins/home/src/components/CustomHomepage/WidgetSettingsOverlay.tsx @@ -58,10 +58,12 @@ interface WidgetSettingsOverlayProps { handleRemove: (id: string) => void; handleSettingsSave: (id: string, settings: Record) => void; settings?: Record; + deletable?: boolean; } export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { - const { id, widget, settings, handleRemove, handleSettingsSave } = props; + const { id, widget, settings, handleRemove, handleSettingsSave, deletable } = + props; const [settingsDialogOpen, setSettingsDialogOpen] = React.useState(false); const styles = useStyles(); @@ -110,13 +112,15 @@ export const WidgetSettingsOverlay = (props: WidgetSettingsOverlayProps) => { )} - - - handleRemove(id)}> - - - - + {deletable !== false && ( + + + handleRemove(id)}> + + + + + )} ); diff --git a/plugins/home/src/components/CustomHomepage/index.ts b/plugins/home/src/components/CustomHomepage/index.ts index eed597cca3..380fdb4b25 100644 --- a/plugins/home/src/components/CustomHomepage/index.ts +++ b/plugins/home/src/components/CustomHomepage/index.ts @@ -14,5 +14,8 @@ * limitations under the License. */ export { CustomHomepageGrid } from './CustomHomepageGrid'; -export type { CustomHomepageGridProps, Breakpoint } from './CustomHomepageGrid'; -export type { LayoutConfiguration } from './types'; +export type { + CustomHomepageGridProps, + Breakpoint, + LayoutConfiguration, +} from './types'; diff --git a/plugins/home/src/components/CustomHomepage/types.ts b/plugins/home/src/components/CustomHomepage/types.ts index 8e9c6f403e..79e94c5f03 100644 --- a/plugins/home/src/components/CustomHomepage/types.ts +++ b/plugins/home/src/components/CustomHomepage/types.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ReactElement } from 'react'; +import React, { ReactElement, ReactNode } from 'react'; import { Layout } from 'react-grid-layout'; import { z } from 'zod'; import { RJSFSchema, UiSchema } from '@rjsf/utils'; @@ -24,12 +24,91 @@ const RSJFTypeUiSchema: z.ZodType = z.any(); const ReactElementSchema: z.ZodType = z.any(); const LayoutSchema: z.ZodType = z.any(); +/** + * Breakpoint options for + * + * @public + */ +export type Breakpoint = 'xxs' | 'xs' | 'sm' | 'md' | 'lg' | 'xl'; + +/** + * Props customizing the component. + * + * @public + */ +export type CustomHomepageGridProps = { + /** + * Children contain all widgets user can configure on their own homepage. + */ + children?: ReactNode; + /** + * Default layout for the homepage before users have modified it. + */ + config?: LayoutConfiguration[]; + /** + * Height of grid row in pixels. + * @defaultValue 60 + */ + rowHeight?: number; + /** + * Screen width in pixels for different breakpoints. + * @defaultValue theme breakpoints + */ + breakpoints?: Record; + /** + * Number of grid columns for different breakpoints. + * @defaultValue \{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 \} + */ + cols?: Record; + /** + * Grid container padding (x, y) in pixels for all or specific breakpoints. + * @defaultValue [0, 0] + * @example [10, 10] + * @example \{ lg: [10, 10] \} + */ + containerPadding?: [number, number] | Record; + /** + * Grid container margin (x, y) in pixels for all or specific breakpoints. + * @defaultValue [0, 0] + * @example [10, 10] + * @example \{ lg: [10, 10] \} + */ + containerMargin?: [number, number] | Record; + /** + * Maximum number of rows user can have in the grid. + * @defaultValue unlimited + */ + maxRows?: number; + /** + * Custom style for grid. + */ + style?: React.CSSProperties; + /** + * Compaction type of widgets in the grid. This controls where widgets are moved in case + * they are overlapping in the grid. + */ + compactType?: 'vertical' | 'horizontal' | null; + /** + * Controls if widgets can overlap in the grid. If true, grid can be placed one over the other. + * @defaultValue false + */ + allowOverlap?: boolean; + /** + * Controls if widgets can collide with each other. If true, grid items won't change position when being dragged over. + * @defaultValue false + */ + preventCollision?: boolean; +}; + export const LayoutConfigurationSchema = z.object({ component: ReactElementSchema, x: z.number().nonnegative('x must be positive number'), y: z.number().nonnegative('y must be positive number'), width: z.number().positive('width must be positive number'), height: z.number().positive('height must be positive number'), + movable: z.boolean().optional(), + deletable: z.boolean().optional(), + resizable: z.boolean().optional(), }); /** @@ -43,6 +122,9 @@ export type LayoutConfiguration = { y: number; width: number; height: number; + movable?: boolean; + deletable?: boolean; + resizable?: boolean; }; export const WidgetSchema = z.object({ @@ -64,6 +146,9 @@ export const WidgetSchema = z.object({ .optional(), settingsSchema: RSJFTypeSchema.optional(), uiSchema: RSJFTypeUiSchema.optional(), + movable: z.boolean().optional(), + deletable: z.boolean().optional(), + resizable: z.boolean().optional(), }); export type Widget = z.infer; @@ -72,6 +157,9 @@ const GridWidgetSchema = z.object({ id: z.string(), layout: LayoutSchema, settings: z.record(z.string(), z.any()), + movable: z.boolean().optional(), + deletable: z.boolean().optional(), + resizable: z.boolean().optional(), }); export type GridWidget = z.infer; diff --git a/plugins/ilert/catalog-info.yaml b/plugins/ilert/catalog-info.yaml new file mode 100644 index 0000000000..a73c82cbfe --- /dev/null +++ b/plugins/ilert/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-ilert + title: '@backstage/plugin-ilert' + description: A Backstage plugin that integrates towards iLert +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/jenkins-backend/catalog-info.yaml b/plugins/jenkins-backend/catalog-info.yaml new file mode 100644 index 0000000000..d3345d60dd --- /dev/null +++ b/plugins/jenkins-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-jenkins-backend + title: '@backstage/plugin-jenkins-backend' + description: A Backstage backend plugin that integrates towards Jenkins +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/jenkins-common/catalog-info.yaml b/plugins/jenkins-common/catalog-info.yaml new file mode 100644 index 0000000000..1fbf7971f1 --- /dev/null +++ b/plugins/jenkins-common/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-jenkins-common + title: '@backstage/plugin-jenkins-common' +spec: + lifecycle: experimental + type: backstage-common-library + owner: maintainers diff --git a/plugins/jenkins/catalog-info.yaml b/plugins/jenkins/catalog-info.yaml new file mode 100644 index 0000000000..097034481d --- /dev/null +++ b/plugins/jenkins/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-jenkins + title: '@backstage/plugin-jenkins' + description: A Backstage plugin that integrates towards Jenkins +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/kafka-backend/catalog-info.yaml b/plugins/kafka-backend/catalog-info.yaml new file mode 100644 index 0000000000..13dc9fd963 --- /dev/null +++ b/plugins/kafka-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-kafka-backend + title: '@backstage/plugin-kafka-backend' + description: A Backstage backend plugin that integrates towards Kafka +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: andrewthauer diff --git a/plugins/kafka/catalog-info.yaml b/plugins/kafka/catalog-info.yaml new file mode 100644 index 0000000000..cf0e0a6d9d --- /dev/null +++ b/plugins/kafka/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-kafka + title: '@backstage/plugin-kafka' + description: A Backstage plugin that integrates towards Kafka +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: andrewthauer diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 314010f574..1c62384411 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -65,6 +65,7 @@ "@backstage/plugin-kubernetes-common": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-node": "workspace:^", + "@backstage/types": "workspace:^", "@google-cloud/container": "^4.0.0", "@jest-mock/express": "^2.0.1", "@kubernetes/client-node": "0.18.1", diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 9800780077..1a603a7a98 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -351,6 +351,7 @@ describe('KubernetesFetcher', () => { res( checkToken(req, ctx, 'token'), withLabels(req, ctx, { + kind: 'PodList', items: [{ metadata: { name: 'pod-name' } }], }), ), @@ -359,6 +360,7 @@ describe('KubernetesFetcher', () => { res( checkToken(req, ctx, 'token'), withLabels(req, ctx, { + kind: 'ServiceList', items: [{ metadata: { name: 'service-name' } }], }), ), @@ -369,6 +371,7 @@ describe('KubernetesFetcher', () => { res( checkToken(req, ctx, 'token'), withLabels(req, ctx, { + kind: 'ThingList', items: [{ metadata: { name: 'something-else' } }], }), ), @@ -424,6 +427,7 @@ describe('KubernetesFetcher', () => { type: 'customresources', resources: [ { + kind: 'Thing', metadata: { name: 'something-else', labels: { 'backstage.io/kubernetes-id': 'some-service' }, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index b2be199d50..25bade3e97 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -41,6 +41,7 @@ import { import fetch, { RequestInit, Response } from 'node-fetch'; import * as https from 'https'; import fs from 'fs-extra'; +import { JsonObject } from '@backstage/types'; export interface KubernetesClientBasedFetcherOptions { logger: Logger; @@ -104,9 +105,15 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { (r: Response): Promise => r.ok ? r.json().then( - ({ items }): FetchResponse => ({ + ({ kind, items }): FetchResponse => ({ type: objectType, - resources: items, + resources: + objectType === 'customresources' + ? items.map((item: JsonObject) => ({ + ...item, + kind: kind.replace(/(List)$/, ''), + })) + : items, }), ) : this.handleUnsuccessfulResponse(params.clusterDetails.name, r), diff --git a/plugins/kubernetes/src/components/Pods/PodLogs/PodLogs.tsx b/plugins/kubernetes/src/components/Pods/PodLogs/PodLogs.tsx index de9341508e..5c08de047c 100644 --- a/plugins/kubernetes/src/components/Pods/PodLogs/PodLogs.tsx +++ b/plugins/kubernetes/src/components/Pods/PodLogs/PodLogs.tsx @@ -64,7 +64,7 @@ export const PodLogs: React.FC = ({ )} {loading && } {!loading && diff --git a/plugins/lighthouse-backend/catalog-info.yaml b/plugins/lighthouse-backend/catalog-info.yaml new file mode 100644 index 0000000000..a44ce9fa44 --- /dev/null +++ b/plugins/lighthouse-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-lighthouse-backend + title: '@backstage/plugin-lighthouse-backend' + description: Backend functionalities for lighthouse +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/lighthouse-common/catalog-info.yaml b/plugins/lighthouse-common/catalog-info.yaml new file mode 100644 index 0000000000..f5e8ead471 --- /dev/null +++ b/plugins/lighthouse-common/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-lighthouse-common + title: '@backstage/plugin-lighthouse-common' + description: >- + Common functionalities for lighthouse, to be shared between lighthouse and + lighthouse-backend plugin +spec: + lifecycle: experimental + type: backstage-common-library + owner: maintainers diff --git a/plugins/lighthouse/catalog-info.yaml b/plugins/lighthouse/catalog-info.yaml new file mode 100644 index 0000000000..2f2f69a98d --- /dev/null +++ b/plugins/lighthouse/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-lighthouse + title: '@backstage/plugin-lighthouse' + description: A Backstage plugin that integrates towards Lighthouse +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/linguist-backend/catalog-info.yaml b/plugins/linguist-backend/catalog-info.yaml new file mode 100644 index 0000000000..de7545d39b --- /dev/null +++ b/plugins/linguist-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-linguist-backend + title: '@backstage/plugin-linguist-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: awanlin diff --git a/plugins/linguist-common/catalog-info.yaml b/plugins/linguist-common/catalog-info.yaml new file mode 100644 index 0000000000..266daa4a75 --- /dev/null +++ b/plugins/linguist-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-linguist-common + title: '@backstage/plugin-linguist-common' + description: Common functionalities for the linguist plugin +spec: + lifecycle: experimental + type: backstage-common-library + owner: awanlin diff --git a/plugins/linguist/catalog-info.yaml b/plugins/linguist/catalog-info.yaml new file mode 100644 index 0000000000..aca70b7570 --- /dev/null +++ b/plugins/linguist/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-linguist + title: '@backstage/plugin-linguist' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: awanlin diff --git a/plugins/microsoft-calendar/catalog-info.yaml b/plugins/microsoft-calendar/catalog-info.yaml new file mode 100644 index 0000000000..82c3c82b49 --- /dev/null +++ b/plugins/microsoft-calendar/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-microsoft-calendar + title: '@backstage/plugin-microsoft-calendar' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/newrelic-dashboard/catalog-info.yaml b/plugins/newrelic-dashboard/catalog-info.yaml new file mode 100644 index 0000000000..d944c9cb6b --- /dev/null +++ b/plugins/newrelic-dashboard/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-newrelic-dashboard + title: '@backstage/plugin-newrelic-dashboard' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/newrelic/catalog-info.yaml b/plugins/newrelic/catalog-info.yaml new file mode 100644 index 0000000000..bdf6aa5757 --- /dev/null +++ b/plugins/newrelic/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-newrelic + title: '@backstage/plugin-newrelic' + description: A Backstage plugin that integrates towards New Relic +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/nomad-backend/catalog-info.yaml b/plugins/nomad-backend/catalog-info.yaml new file mode 100644 index 0000000000..7364d1920e --- /dev/null +++ b/plugins/nomad-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-nomad-backend + title: '@backstage/plugin-nomad-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/nomad/catalog-info.yaml b/plugins/nomad/catalog-info.yaml new file mode 100644 index 0000000000..d704846059 --- /dev/null +++ b/plugins/nomad/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-nomad + title: '@backstage/plugin-nomad' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/octopus-deploy/catalog-info.yaml b/plugins/octopus-deploy/catalog-info.yaml new file mode 100644 index 0000000000..390b91f6e3 --- /dev/null +++ b/plugins/octopus-deploy/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-octopus-deploy + title: '@backstage/plugin-octopus-deploy' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/opencost/catalog-info.yaml b/plugins/opencost/catalog-info.yaml new file mode 100644 index 0000000000..277562bb32 --- /dev/null +++ b/plugins/opencost/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-opencost + title: '@backstage/plugin-opencost' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/opencost/src/components/SelectWindow.jsx b/plugins/opencost/src/components/SelectWindow.jsx index e374571c00..470770f35c 100644 --- a/plugins/opencost/src/components/SelectWindow.jsx +++ b/plugins/opencost/src/components/SelectWindow.jsx @@ -16,7 +16,6 @@ import React, { useEffect, useState } from 'react'; import { makeStyles } from '@material-ui/styles'; -import { endOfDay, startOfDay } from '@date-io/luxon'; import { MuiPickersUtilsProvider, KeyboardDatePicker, @@ -67,14 +66,14 @@ const SelectWindow = ({ windowOptions, window, setWindow }) => { }; const handleStartDateChange = date => { - if (isValid(date)) { - setStartDate(startOfDay(date)); + if (isValid(date.toJSDate())) { + setStartDate(date.startOf('day').toJSDate()); } }; const handleEndDateChange = date => { - if (isValid(date)) { - setEndDate(endOfDay(date)); + if (isValid(date.toJSDate())) { + setEndDate(date.endOf('day').toJSDate()); } }; diff --git a/plugins/org-react/catalog-info.yaml b/plugins/org-react/catalog-info.yaml new file mode 100644 index 0000000000..e020b5103e --- /dev/null +++ b/plugins/org-react/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-org-react + title: '@backstage/plugin-org-react' +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/plugins/org/api-report.md b/plugins/org/api-report.md index 704401e8d4..d85df7125c 100644 --- a/plugins/org/api-report.md +++ b/plugins/org/api-report.md @@ -30,10 +30,13 @@ export const EntityOwnershipCard: (props: { variant?: InfoCardVariants | undefined; entityFilterKind?: string[] | undefined; hideRelationsToggle?: boolean | undefined; - relationsType?: string | undefined; + relationsType?: EntityRelationAggregation | undefined; entityLimit?: number | undefined; }) => JSX_2.Element; +// @public (undocumented) +export type EntityRelationAggregation = 'direct' | 'aggregated'; + // @public (undocumented) export const EntityUserProfileCard: (props: { variant?: InfoCardVariants | undefined; @@ -77,7 +80,7 @@ export const OwnershipCard: (props: { variant?: InfoCardVariants; entityFilterKind?: string[]; hideRelationsToggle?: boolean; - relationsType?: string; + relationsType?: EntityRelationAggregation; entityLimit?: number; }) => React_2.JSX.Element; diff --git a/plugins/org/catalog-info.yaml b/plugins/org/catalog-info.yaml new file mode 100644 index 0000000000..defa276143 --- /dev/null +++ b/plugins/org/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-org + title: '@backstage/plugin-org' + description: A Backstage plugin that helps you create entity pages for your organization +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/org/package.json b/plugins/org/package.json index 0193a1592b..81e5a2aa6f 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -60,6 +60,7 @@ "@testing-library/dom": "^8.0.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", + "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.0.0", "@types/node": "^16.11.26", "msw": "^1.0.0" diff --git a/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx index 8dab5c4290..2725991d0d 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/ComponentsGrid.tsx @@ -34,6 +34,7 @@ import React from 'react'; import pluralize from 'pluralize'; import { catalogIndexRouteRef } from '../../../routes'; import { useGetEntities } from './useGetEntities'; +import { EntityRelationAggregation } from './types'; const useStyles = makeStyles((theme: BackstageTheme) => createStyles({ @@ -109,13 +110,11 @@ const EntityCountTile = ({ export const ComponentsGrid = ({ entity, relationsType, - isGroup, entityFilterKind, entityLimit = 6, }: { entity: Entity; - relationsType: string; - isGroup: boolean; + relationsType: EntityRelationAggregation; entityFilterKind?: string[]; entityLimit?: number; }) => { @@ -123,7 +122,6 @@ export const ComponentsGrid = ({ const { componentsWithCounters, loading, error } = useGetEntities( entity, relationsType, - isGroup, entityFilterKind, entityLimit, ); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 30b7dee2de..faba9b5627 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -263,10 +263,11 @@ describe('OwnershipCard', () => { }, ); - expect(getByText('OPENAPI').closest('a')).toHaveAttribute( - 'href', - '/create/?filters%5Bkind%5D=api&filters%5Btype%5D=openapi&filters%5Bowners%5D=my-team&filters%5Buser%5D=all', - ); + const href = getByText('OPENAPI').closest('a')?.href ?? ''; + // This env does not support URLSearchParams + const queryParams = decodeURIComponent(href); + + expect(queryParams).toContain('filters[owners]=my-team'); }); it('links to the catalog with the user and groups filters from an user profile', async () => { @@ -299,7 +300,7 @@ describe('OwnershipCard', () => { const { getByText } = await renderInTestApp( - + , { @@ -309,9 +310,11 @@ describe('OwnershipCard', () => { }, ); - expect(getByText('OPENAPI').closest('a')).toHaveAttribute( - 'href', - '/create/?filters%5Bkind%5D=api&filters%5Btype%5D=openapi&filters%5Bowners%5D=user%3Athe-user&filters%5Bowners%5D=my-team&filters%5Bowners%5D=custom%2Fsome-team&filters%5Buser%5D=all', + const href = getByText('OPENAPI').closest('a')?.href ?? ''; + // This env does not support URLSearchParams + const queryParams = decodeURIComponent(href); + expect(queryParams).toMatch( + /filters\[owners\]=custom\/some\-team.*filters\[owners\]=user:the-user/, ); }); diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index d104eb032d..2fb7826e52 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -27,6 +27,7 @@ import { } from '@material-ui/core'; import React, { useState } from 'react'; import { ComponentsGrid } from './ComponentsGrid'; +import { EntityRelationAggregation } from './types'; const useStyles = makeStyles(theme => ({ list: { @@ -56,7 +57,7 @@ export const OwnershipCard = (props: { variant?: InfoCardVariants; entityFilterKind?: string[]; hideRelationsToggle?: boolean; - relationsType?: string; + relationsType?: EntityRelationAggregation; entityLimit?: number; }) => { const { @@ -70,7 +71,6 @@ export const OwnershipCard = (props: { hideRelationsToggle === undefined ? false : hideRelationsToggle; const classes = useStyles(); const { entity } = useEntity(); - const isGroup = entity.kind === 'Group'; const [getRelationsType, setRelationsType] = useState( relationsType || 'direct', ); @@ -102,7 +102,6 @@ export const OwnershipCard = (props: { } name="pin" inputProps={{ 'aria-label': 'Ownership Type Switch' }} - disabled={!isGroup} /> Aggregated Relations @@ -114,7 +113,6 @@ export const OwnershipCard = (props: { entity={entity} entityLimit={entityLimit} relationsType={getRelationsType} - isGroup={isGroup} entityFilterKind={entityFilterKind} /> diff --git a/plugins/org/src/components/Cards/OwnershipCard/index.ts b/plugins/org/src/components/Cards/OwnershipCard/index.ts index dc0cef0226..a4798cc4bf 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/index.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export * from './OwnershipCard'; +export * from './types'; diff --git a/plugins/catalog-backend/src/service/request/parseFullTextFilterFields.ts b/plugins/org/src/components/Cards/OwnershipCard/types.ts similarity index 64% rename from plugins/catalog-backend/src/service/request/parseFullTextFilterFields.ts rename to plugins/org/src/components/Cards/OwnershipCard/types.ts index 02ba5864fc..c1e95ee94e 100644 --- a/plugins/catalog-backend/src/service/request/parseFullTextFilterFields.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/types.ts @@ -1,5 +1,3 @@ -import { parseStringParam } from './common'; - /* * Copyright 2023 The Backstage Authors * @@ -15,14 +13,6 @@ import { parseStringParam } from './common'; * See the License for the specific language governing permissions and * limitations under the License. */ -export function parseFullTextFilterFields(params: Record) { - const fullTextFilterFields = parseStringParam( - params.fullTextFilterFields, - 'fullTextFilterFields', - ); - if (!fullTextFilterFields) { - return undefined; - } - return fullTextFilterFields.split(','); -} +/** @public */ +export type EntityRelationAggregation = 'direct' | 'aggregated'; diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts new file mode 100644 index 0000000000..191bd10284 --- /dev/null +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts @@ -0,0 +1,150 @@ +/* + * Copyright 2023 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 { CompoundEntityRef, Entity } from '@backstage/catalog-model'; +import { useGetEntities } from './useGetEntities'; +import { CatalogApi } from '@backstage/catalog-client'; +import { renderHook } from '@testing-library/react-hooks'; +import { getEntityRelations } from '@backstage/plugin-catalog-react'; + +const givenParentGroup = 'team.squad1'; +const givenLeafGroup = 'team.squad2'; +const givenUser = 'user.john'; +const givenParentGroupEntity = { + kind: 'Group', + metadata: { + name: givenParentGroup, + }, +} as Partial as Entity; +const givenLeafGroupEntity = { + kind: 'Group', + metadata: { + name: givenLeafGroup, + }, +} as Partial as Entity; +const givenUserEntity = { + kind: 'User', + metadata: { + name: givenUser, + }, +} as Partial as Entity; + +const catalogApiMock: Pick = { + getEntities: jest.fn(async () => Promise.resolve({ items: [] })), + getEntitiesByRefs: jest.fn(async ({ entityRefs: [ref] }) => + ref.includes(givenParentGroup) + ? { items: [givenParentGroupEntity] } + : { items: [givenLeafGroupEntity] }, + ), +}; + +jest.mock('@backstage/core-plugin-api', () => ({ + useApi: jest.fn(() => catalogApiMock), +})); +jest.mock('@backstage/plugin-catalog-react', () => ({ + catalogApiRef: {}, + getEntityRelations: jest.fn(entity => { + if (entity?.metadata.name === givenParentGroup) { + return [ + { + kind: 'Group', + namespace: 'default', + name: givenLeafGroup, + } as CompoundEntityRef, + ]; + } else if (entity?.kind === 'User') { + return [ + { + kind: 'Group', + namespace: 'default', + name: givenLeafGroup, + } as CompoundEntityRef, + ]; + } + + return []; + }) as typeof getEntityRelations, +})); + +describe('useGetEntities', () => { + const ownersFilter = (...owners: string[]) => + expect.objectContaining({ + filter: expect.arrayContaining([ + expect.objectContaining({ + 'relations.ownedBy': owners, + }), + ]), + }); + + describe('given aggregated relationsType', () => { + const whenHookIsCalledWith = async (_entity: Entity) => { + const hook = renderHook( + ({ entity }) => useGetEntities(entity, 'aggregated'), + { + initialProps: { entity: _entity }, + }, + ); + + await hook.waitForNextUpdate(); + }; + + it('given group entity should aggregate child ownership', async () => { + await whenHookIsCalledWith(givenParentGroupEntity); + expect(catalogApiMock.getEntities).toHaveBeenCalledWith( + ownersFilter( + `group:default/${givenParentGroup}`, + `group:default/${givenLeafGroup}`, + ), + ); + }); + + it('given user entity should aggregate parent ownership and direct', async () => { + await whenHookIsCalledWith(givenUserEntity); + expect(catalogApiMock.getEntities).toHaveBeenCalledWith( + ownersFilter( + `group:default/${givenLeafGroup}`, + `user:default/${givenUser}`, + ), + ); + }); + }); + + describe('given direct relationsType', () => { + const whenHookIsCalledWith = async (_entity: Entity) => { + const hook = renderHook( + ({ entity }) => useGetEntities(entity, 'direct'), + { + initialProps: { entity: _entity }, + }, + ); + + await hook.waitForNextUpdate(); + }; + + it('given group entity should return directly owned entities', async () => { + await whenHookIsCalledWith(givenLeafGroupEntity); + expect(catalogApiMock.getEntities).toHaveBeenCalledWith( + ownersFilter(`group:default/${givenLeafGroup}`), + ); + }); + + it('given user entity should return directly owned entities', async () => { + await whenHookIsCalledWith(givenUserEntity); + expect(catalogApiMock.getEntities).toHaveBeenCalledWith( + ownersFilter(`user:default/${givenUser}`), + ); + }); + }); +}); diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts index a59f534300..1ff01cd8e1 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.ts @@ -31,6 +31,7 @@ import limiterFactory from 'p-limit'; import { useApi } from '@backstage/core-plugin-api'; import useAsync from 'react-use/lib/useAsync'; import qs from 'qs'; +import { EntityRelationAggregation as EntityRelationsAggregation } from './types'; const limiter = limiterFactory(10); @@ -57,78 +58,109 @@ const getQueryParams = ( return qs.stringify({ filters }, { arrayFormat: 'repeat' }); }; -const getOwnersEntityRef = (owner: Entity): string[] => { - let owners = [stringifyEntityRef(owner)]; - if (owner.kind === 'User') { - const ownerGroups = getEntityRelations(owner, RELATION_MEMBER_OF, { - kind: 'Group', +const getMemberOfEntityRefs = (owner: Entity): string[] => { + const parentGroups = getEntityRelations(owner, RELATION_MEMBER_OF, { + kind: 'Group', + }); + + const ownerGroupsNames = parentGroups.map(({ kind, namespace, name }) => + stringifyEntityRef({ + kind, + namespace, + name, + }), + ); + + return [...ownerGroupsNames, stringifyEntityRef(owner)]; +}; + +const isEntity = (entity: Entity | undefined): entity is Entity => + entity !== undefined; + +const getChildOwnershipEntityRefs = async ( + entity: Entity, + catalogApi: CatalogApi, +): Promise => { + const childGroups = getEntityRelations(entity, RELATION_PARENT_OF, { + kind: 'Group', + }); + + const hasChildGroups = childGroups.length > 0; + + if (hasChildGroups) { + const entityRefs = childGroups.map(r => stringifyEntityRef(r)); + const childGroupResponse = await catalogApi.getEntitiesByRefs({ + fields: ['kind', 'metadata.namespace', 'metadata.name'], + entityRefs, }); - const ownerGroupsName = ownerGroups.map(ownerGroup => - stringifyEntityRef({ - kind: ownerGroup.kind, - namespace: ownerGroup.namespace, - name: ownerGroup.name, - }), - ); - owners = [...owners, ...ownerGroupsName]; + const childGroupEntities = childGroupResponse.items.filter(isEntity); + + return ( + await Promise.all( + childGroupEntities.map(childGroupEntity => + limiter(() => + getChildOwnershipEntityRefs(childGroupEntity, catalogApi), + ), + ), + ) + ).flatMap(aggregated => aggregated); } + + return [stringifyEntityRef(entity)]; +}; + +const getOwners = async ( + entity: Entity, + relations: EntityRelationsAggregation, + catalogApi: CatalogApi, +): Promise => { + const isGroup = entity.kind === 'Group'; + const isAggregated = relations === 'aggregated'; + const isUserEntity = entity.kind === 'User'; + + const owners: string[] = []; + + if (isAggregated && isGroup) { + const childEntityRefs = await getChildOwnershipEntityRefs( + entity, + catalogApi, + ); + owners.push(stringifyEntityRef(entity)); + owners.push.apply(owners, childEntityRefs); + } else if (isAggregated && isUserEntity) { + const parentEntityRefs = getMemberOfEntityRefs(entity); + owners.push.apply(owners, parentEntityRefs); + } else { + owners.push(stringifyEntityRef(entity)); + } + return owners; }; -const getAggregatedOwnersEntityRef = async ( - parentGroup: Entity, +const getOwnedEntitiesByOwners = ( + owners: string[], + kinds: string[], catalogApi: CatalogApi, -): Promise => { - const requestedEntities: Entity[] = []; - const outstandingEntities = new Map>(); - const processedEntities = new Set(); - requestedEntities.push(parentGroup); - let currentEntity = parentGroup; - - while (requestedEntities.length > 0) { - const childRelations = getEntityRelations( - currentEntity, - RELATION_PARENT_OF, +) => + catalogApi.getEntities({ + filter: [ { - kind: 'Group', + kind: kinds, + 'relations.ownedBy': owners, }, - ); - - await Promise.all( - childRelations.map(childGroup => - limiter(async () => { - const promise = catalogApi.getEntityByRef(childGroup); - outstandingEntities.set(childGroup.name, promise); - try { - const processedEntity = await promise; - if (processedEntity) { - requestedEntities.push(processedEntity); - } - } finally { - outstandingEntities.delete(childGroup.name); - } - }), - ), - ); - requestedEntities.shift(); - processedEntities.add( - stringifyEntityRef({ - kind: currentEntity.kind, - namespace: currentEntity.metadata.namespace, - name: currentEntity.metadata.name, - }), - ); - // always set currentEntity to the first element of array requestedEntities - currentEntity = requestedEntities[0]; - } - - return Array.from(processedEntities); -}; + ], + fields: [ + 'kind', + 'metadata.name', + 'metadata.namespace', + 'spec.type', + 'relations', + ], + }); export function useGetEntities( entity: Entity, - relationsType: string, - isGroup: boolean, + relations: EntityRelationsAggregation, entityFilterKind?: string[], entityLimit = 6, ): { @@ -151,25 +183,13 @@ export function useGetEntities( error, value: componentsWithCounters, } = useAsync(async () => { - const owners = - relationsType === 'aggregated' && isGroup - ? await getAggregatedOwnersEntityRef(entity, catalogApi) - : getOwnersEntityRef(entity); - const ownedEntitiesList = await catalogApi.getEntities({ - filter: [ - { - kind: kinds, - 'relations.ownedBy': owners, - }, - ], - fields: [ - 'kind', - 'metadata.name', - 'metadata.namespace', - 'spec.type', - 'relations', - ], - }); + const owners = await getOwners(entity, relations, catalogApi); + + const ownedEntitiesList = await getOwnedEntitiesByOwners( + owners, + kinds, + catalogApi, + ); const counts = ownedEntitiesList.items.reduce( (acc: EntityTypeProps[], ownedEntity) => { @@ -204,7 +224,7 @@ export function useGetEntities( kind: string; queryParams: string; }>; - }, [catalogApi, entity, relationsType]); + }, [catalogApi, entity, relations]); return { componentsWithCounters, diff --git a/plugins/org/src/setupTests.ts b/plugins/org/src/setupTests.ts index 28a35d2b06..09cb508b22 100644 --- a/plugins/org/src/setupTests.ts +++ b/plugins/org/src/setupTests.ts @@ -14,3 +14,7 @@ * limitations under the License. */ import '@testing-library/jest-dom'; + +beforeEach(() => { + jest.clearAllMocks(); +}); diff --git a/plugins/pagerduty/catalog-info.yaml b/plugins/pagerduty/catalog-info.yaml new file mode 100644 index 0000000000..c8585f0e55 --- /dev/null +++ b/plugins/pagerduty/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-pagerduty + title: '@backstage/plugin-pagerduty' + description: A Backstage plugin that integrates towards PagerDuty +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/periskop-backend/catalog-info.yaml b/plugins/periskop-backend/catalog-info.yaml new file mode 100644 index 0000000000..8ad2a7f261 --- /dev/null +++ b/plugins/periskop-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-periskop-backend + title: '@backstage/plugin-periskop-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/periskop/catalog-info.yaml b/plugins/periskop/catalog-info.yaml new file mode 100644 index 0000000000..b4ca9bdd34 --- /dev/null +++ b/plugins/periskop/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-periskop + title: '@backstage/plugin-periskop' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/playlist-backend/catalog-info.yaml b/plugins/playlist-backend/catalog-info.yaml new file mode 100644 index 0000000000..79bdfd41da --- /dev/null +++ b/plugins/playlist-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-playlist-backend + title: '@backstage/plugin-playlist-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: kuangp diff --git a/plugins/playlist-common/catalog-info.yaml b/plugins/playlist-common/catalog-info.yaml new file mode 100644 index 0000000000..9367544c2c --- /dev/null +++ b/plugins/playlist-common/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-playlist-common + title: '@backstage/plugin-playlist-common' + description: Common functionalities for the playlist plugin +spec: + lifecycle: experimental + type: backstage-common-library + owner: kuangp diff --git a/plugins/playlist/catalog-info.yaml b/plugins/playlist/catalog-info.yaml new file mode 100644 index 0000000000..1b8eaaabb0 --- /dev/null +++ b/plugins/playlist/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-playlist + title: '@backstage/plugin-playlist' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: kuangp diff --git a/plugins/playlist/src/components/PlaylistPage/PlaylistEntitiesTable.tsx b/plugins/playlist/src/components/PlaylistPage/PlaylistEntitiesTable.tsx index 3d6ffc5f8c..c74859ce20 100644 --- a/plugins/playlist/src/components/PlaylistPage/PlaylistEntitiesTable.tsx +++ b/plugins/playlist/src/components/PlaylistPage/PlaylistEntitiesTable.tsx @@ -108,7 +108,7 @@ export const PlaylistEntitiesTable = ({ const actions = editAllowed ? [ { - icon: DeleteIcon, + icon: () => , tooltip: `Remove from ${singularTitleLowerCase}`, onClick: removeEntity, }, diff --git a/plugins/playlist/src/components/PlaylistPage/PlaylistHeader.tsx b/plugins/playlist/src/components/PlaylistPage/PlaylistHeader.tsx index f892e8de7f..3a31dcaade 100644 --- a/plugins/playlist/src/components/PlaylistPage/PlaylistHeader.tsx +++ b/plugins/playlist/src/components/PlaylistPage/PlaylistHeader.tsx @@ -166,7 +166,7 @@ export const PlaylistHeader = ({ playlist, onUpdate }: PlaylistHeaderProps) => { }, { label: `Delete ${singularTitle}`, - icon: , + icon: , disabled: !deleteAllowed, onClick: () => setOpenDeleteDialog(true), }, diff --git a/plugins/proxy-backend/catalog-info.yaml b/plugins/proxy-backend/catalog-info.yaml new file mode 100644 index 0000000000..a60112d9f3 --- /dev/null +++ b/plugins/proxy-backend/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-proxy-backend + title: '@backstage/plugin-proxy-backend' + description: >- + A Backstage backend plugin that helps you set up proxy endpoints in the + backend +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/puppetdb/catalog-info.yaml b/plugins/puppetdb/catalog-info.yaml new file mode 100644 index 0000000000..98c8d34d81 --- /dev/null +++ b/plugins/puppetdb/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-puppetdb + title: '@backstage/plugin-puppetdb' + description: >- + Backstage plugin to visualize resource information and Puppet facts from + PuppetDB. +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/rollbar-backend/catalog-info.yaml b/plugins/rollbar-backend/catalog-info.yaml new file mode 100644 index 0000000000..132695d53b --- /dev/null +++ b/plugins/rollbar-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-rollbar-backend + title: '@backstage/plugin-rollbar-backend' + description: A Backstage backend plugin that integrates towards Rollbar +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: andrewthauer diff --git a/plugins/rollbar/catalog-info.yaml b/plugins/rollbar/catalog-info.yaml new file mode 100644 index 0000000000..bc3f88019b --- /dev/null +++ b/plugins/rollbar/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-rollbar + title: '@backstage/plugin-rollbar' + description: A Backstage plugin that integrates towards Rollbar +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: andrewthauer diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/catalog-info.yaml b/plugins/scaffolder-backend-module-confluence-to-markdown/catalog-info.yaml new file mode 100644 index 0000000000..8daba65491 --- /dev/null +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-confluence-to-markdown + title: '@backstage/plugin-scaffolder-backend-module-confluence-to-markdown' + description: The confluence-to-markdown module for @backstage/plugin-scaffolder-backend +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-cookiecutter/catalog-info.yaml b/plugins/scaffolder-backend-module-cookiecutter/catalog-info.yaml new file mode 100644 index 0000000000..18926d3925 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-cookiecutter + title: '@backstage/plugin-scaffolder-backend-module-cookiecutter' + description: >- + A module for the scaffolder backend that lets you template projects using + cookiecutter +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-gitlab/catalog-info.yaml b/plugins/scaffolder-backend-module-gitlab/catalog-info.yaml new file mode 100644 index 0000000000..c8af1e7fa5 --- /dev/null +++ b/plugins/scaffolder-backend-module-gitlab/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-gitlab + title: '@backstage/plugin-scaffolder-backend-module-gitlab' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-rails/catalog-info.yaml b/plugins/scaffolder-backend-module-rails/catalog-info.yaml new file mode 100644 index 0000000000..989b88219e --- /dev/null +++ b/plugins/scaffolder-backend-module-rails/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-rails + title: '@backstage/plugin-scaffolder-backend-module-rails' + description: >- + A module for the scaffolder backend that lets you template projects using + Rails +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-sentry/catalog-info.yaml b/plugins/scaffolder-backend-module-sentry/catalog-info.yaml new file mode 100644 index 0000000000..75b72fa661 --- /dev/null +++ b/plugins/scaffolder-backend-module-sentry/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-sentry + title: '@backstage/plugin-scaffolder-backend-module-sentry' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend-module-yeoman/catalog-info.yaml b/plugins/scaffolder-backend-module-yeoman/catalog-info.yaml new file mode 100644 index 0000000000..28d6030832 --- /dev/null +++ b/plugins/scaffolder-backend-module-yeoman/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend-module-yeoman + title: '@backstage/plugin-scaffolder-backend-module-yeoman' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: maintainers diff --git a/plugins/scaffolder-backend/catalog-info.yaml b/plugins/scaffolder-backend/catalog-info.yaml new file mode 100644 index 0000000000..5bb93a2483 --- /dev/null +++ b/plugins/scaffolder-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-backend + title: '@backstage/plugin-scaffolder-backend' + description: The Backstage backend plugin that helps you create new things +spec: + lifecycle: production + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.test.ts new file mode 100644 index 0000000000..fe04142a98 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.test.ts @@ -0,0 +1,133 @@ +/* + * Copyright 2023 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 { + ScmIntegrations, + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, +} from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '@backstage/backend-common'; +import { TemplateAction } from '@backstage/plugin-scaffolder-node'; +import { PassThrough } from 'stream'; +import { createGithubActionsDispatchAction } from './githubActionsDispatch'; +import yaml from 'yaml'; +import { examples } from './githubActionsDispatch.examples'; + +const mockOctokit = { + rest: { + actions: { + createWorkflowDispatch: jest.fn(), + }, + }, +}; +jest.mock('octokit', () => ({ + Octokit: class { + constructor() { + return mockOctokit; + } + }, +})); + +describe('github:actions:dispatch', () => { + const config = new ConfigReader({ + integrations: { + github: [ + { host: 'github.com', token: 'tokenlols' }, + { host: 'ghe.github.com' }, + ], + }, + }); + + const integrations = ScmIntegrations.fromConfig(config); + let githubCredentialsProvider: GithubCredentialsProvider; + let action: TemplateAction; + + const mockContext = { + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'a-workflow-id', + branchOrTagName: 'main', + }, + workspacePath: 'lol', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + beforeEach(() => { + jest.resetAllMocks(); + githubCredentialsProvider = + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + action = createGithubActionsDispatchAction({ + integrations, + githubCredentialsProvider, + }); + }); + + it('should call the githubApis for creating WorkflowDispatch without an input object', async () => { + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ + data: { + foo: 'bar', + }, + }); + + const repoUrl = 'github.com?repo=repo&owner=owner'; + const workflowId = 'dispatch_workflow'; + const branchOrTagName = 'main'; + const ctx = Object.assign({}, mockContext, { + input: { repoUrl, workflowId, branchOrTagName }, + }); + await action.handler(ctx); + + expect( + mockOctokit.rest.actions.createWorkflowDispatch, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + workflow_id: workflowId, + ref: branchOrTagName, + }); + }); + + it('should call the githubApis for creating WorkflowDispatch with an input object', async () => { + mockOctokit.rest.actions.createWorkflowDispatch.mockResolvedValue({ + data: { + foo: 'bar', + }, + }); + + const repoUrl = 'github.com?repo=repo&owner=owner'; + const workflowId = 'dispatch_workflow'; + const branchOrTagName = 'main'; + const workflowInputs = yaml.parse(examples[1].example).steps[0].input + .workflowInputs; + const ctx = Object.assign({}, mockContext, { + input: { repoUrl, workflowId, branchOrTagName, workflowInputs }, + }); + await action.handler(ctx); + expect( + mockOctokit.rest.actions.createWorkflowDispatch, + ).toHaveBeenCalledWith({ + owner: 'owner', + repo: 'repo', + workflow_id: workflowId, + ref: branchOrTagName, + inputs: workflowInputs, + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.ts new file mode 100644 index 0000000000..33ad86dd89 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.examples.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2023 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 { TemplateExample } from '@backstage/plugin-scaffolder-node'; +import yaml from 'yaml'; + +export const examples: TemplateExample[] = [ + { + description: 'GitHub Action Workflow Without Inputs.', + example: yaml.stringify({ + steps: [ + { + action: 'github:actions:dispatch', + name: 'Dispatch Github Action Workflow', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'WORKFLOW_ID', + branchOrTagName: 'main', + }, + }, + ], + }), + }, + { + description: 'GitHub Action Workflow With Inputs', + example: yaml.stringify({ + steps: [ + { + action: 'github:actions:dispatch', + name: 'Dispatch Github Action Workflow with inputs', + input: { + repoUrl: 'github.com?repo=repo&owner=owner', + workflowId: 'WORKFLOW_ID', + branchOrTagName: 'main', + workflowInputs: { + input1: 'value1', + input2: 'value2', + }, + }, + }, + ], + }), + }, + { + description: 'GitHub Action Workflow With Custom Token', + example: yaml.stringify({ + steps: [ + { + action: 'github:actions:dispatch', + name: 'Dispatch GitHub Action Workflow (custom token)', + input: { + repoUrl: 'github.com?repo=reponame&owner=owner', + workflowId: 'WORKFLOW_ID', + branchOrTagName: 'release-1.0', + token: '${{ secrets.MY_CUSTOM_TOKEN }}', + }, + }, + ], + }), + }, +]; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts index cd1593ee9f..6623ee1f27 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts @@ -23,6 +23,7 @@ import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { Octokit } from 'octokit'; import { parseRepoUrl } from '../publish/util'; import { getOctokitOptions } from './helpers'; +import { examples } from './githubActionsDispatch.examples'; /** * Creates a new action that dispatches a GitHub Action workflow for a given branch or tag. @@ -44,6 +45,7 @@ export function createGithubActionsDispatchAction(options: { id: 'github:actions:dispatch', description: 'Dispatches a GitHub Action workflow for a given branch or tag', + examples, schema: { input: { type: 'object', diff --git a/plugins/scaffolder-common/catalog-info.yaml b/plugins/scaffolder-common/catalog-info.yaml new file mode 100644 index 0000000000..1a8d2481f0 --- /dev/null +++ b/plugins/scaffolder-common/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-common + title: '@backstage/plugin-scaffolder-common' + description: >- + Common functionalities for the scaffolder, to be shared between scaffolder + and scaffolder-backend plugin +spec: + lifecycle: production + type: backstage-common-library + owner: maintainers diff --git a/plugins/scaffolder-node/catalog-info.yaml b/plugins/scaffolder-node/catalog-info.yaml new file mode 100644 index 0000000000..726591cda5 --- /dev/null +++ b/plugins/scaffolder-node/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-node + title: '@backstage/plugin-scaffolder-node' + description: The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/plugins/scaffolder-react/catalog-info.yaml b/plugins/scaffolder-react/catalog-info.yaml new file mode 100644 index 0000000000..6294c39147 --- /dev/null +++ b/plugins/scaffolder-react/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder-react + title: '@backstage/plugin-scaffolder-react' + description: >- + A frontend library that helps other Backstage plugins interact with the + Scaffolder +spec: + lifecycle: production + type: backstage-web-library + owner: maintainers diff --git a/plugins/scaffolder/catalog-info.yaml b/plugins/scaffolder/catalog-info.yaml new file mode 100644 index 0000000000..ab4321be3e --- /dev/null +++ b/plugins/scaffolder/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-scaffolder + title: '@backstage/plugin-scaffolder' + description: The Backstage plugin that helps you create new things +spec: + lifecycle: production + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/search-react/src/context/SearchContext.tsx b/plugins/search-react/src/context/SearchContext.tsx index add62b39df..f6fe08e866 100644 --- a/plugins/search-react/src/context/SearchContext.tsx +++ b/plugins/search-react/src/context/SearchContext.tsx @@ -102,7 +102,7 @@ export const useSearchContextCheck = () => { * The initial state of `SearchContextProvider`. * */ -const searchInitialState: SearchContextState = { +const defaultInitialSearchState: SearchContextState = { term: '', types: [], filters: {}, @@ -111,7 +111,7 @@ const searchInitialState: SearchContextState = { }; const useSearchContextValue = ( - initialValue: SearchContextState = searchInitialState, + initialValue: SearchContextState = defaultInitialSearchState, ) => { const searchApi = useApi(searchApiRef); @@ -245,12 +245,16 @@ export const SearchContextProvider = (props: SearchContextProviderProps) => { const configApi = useApi(configApiRef); + const propsInitialSearchState = initialState ?? {}; + + const configInitialSearchState = configApi.has('search.query.pageLimit') + ? { pageLimit: configApi.getNumber('search.query.pageLimit') } + : {}; + const searchContextInitialState = { - ...searchInitialState, - ...(initialState || {}), - pageLimit: - configApi.getOptionalNumber('search.query.pageLimit') || - initialState?.pageLimit, + ...defaultInitialSearchState, + ...propsInitialSearchState, + ...configInitialSearchState, }; return hasParentContext && inheritParentContextIfAvailable ? ( diff --git a/plugins/sentry/catalog-info.yaml b/plugins/sentry/catalog-info.yaml new file mode 100644 index 0000000000..863361fe4d --- /dev/null +++ b/plugins/sentry/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-sentry + title: '@backstage/plugin-sentry' + description: A Backstage plugin that integrates towards Sentry +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/shortcuts/catalog-info.yaml b/plugins/shortcuts/catalog-info.yaml new file mode 100644 index 0000000000..cd17d626e6 --- /dev/null +++ b/plugins/shortcuts/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-shortcuts + title: '@backstage/plugin-shortcuts' + description: A Backstage plugin that provides a shortcuts feature to the sidebar +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/sonarqube-backend/catalog-info.yaml b/plugins/sonarqube-backend/catalog-info.yaml new file mode 100644 index 0000000000..5609e72120 --- /dev/null +++ b/plugins/sonarqube-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-sonarqube-backend + title: '@backstage/plugin-sonarqube-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/sonarqube-react/catalog-info.yaml b/plugins/sonarqube-react/catalog-info.yaml new file mode 100644 index 0000000000..abcae8a465 --- /dev/null +++ b/plugins/sonarqube-react/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-sonarqube-react + title: '@backstage/plugin-sonarqube-react' +spec: + lifecycle: experimental + type: backstage-web-library + owner: maintainers diff --git a/plugins/sonarqube/catalog-info.yaml b/plugins/sonarqube/catalog-info.yaml new file mode 100644 index 0000000000..19cc481e93 --- /dev/null +++ b/plugins/sonarqube/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-sonarqube + title: '@backstage/plugin-sonarqube' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: sda-se-reviewers diff --git a/plugins/splunk-on-call/catalog-info.yaml b/plugins/splunk-on-call/catalog-info.yaml new file mode 100644 index 0000000000..8c41ec7165 --- /dev/null +++ b/plugins/splunk-on-call/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-splunk-on-call + title: '@backstage/plugin-splunk-on-call' + description: A Backstage plugin that integrates towards Splunk On-Call +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/stack-overflow-backend/catalog-info.yaml b/plugins/stack-overflow-backend/catalog-info.yaml new file mode 100644 index 0000000000..797f2c857f --- /dev/null +++ b/plugins/stack-overflow-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-stack-overflow-backend + title: '@backstage/plugin-stack-overflow-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: discoverability-maintainers diff --git a/plugins/stack-overflow/catalog-info.yaml b/plugins/stack-overflow/catalog-info.yaml new file mode 100644 index 0000000000..a949dbda3b --- /dev/null +++ b/plugins/stack-overflow/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-stack-overflow + title: '@backstage/plugin-stack-overflow' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: discoverability-maintainers diff --git a/plugins/stackstorm/catalog-info.yaml b/plugins/stackstorm/catalog-info.yaml new file mode 100644 index 0000000000..1377f8d013 --- /dev/null +++ b/plugins/stackstorm/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-stackstorm + title: '@backstage/plugin-stackstorm' + description: A Backstage plugin that integrates towards StackStorm +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/tech-insights-backend-module-jsonfc/catalog-info.yaml b/plugins/tech-insights-backend-module-jsonfc/catalog-info.yaml new file mode 100644 index 0000000000..38d99d940a --- /dev/null +++ b/plugins/tech-insights-backend-module-jsonfc/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-tech-insights-backend-module-jsonfc + title: '@backstage/plugin-tech-insights-backend-module-jsonfc' +spec: + lifecycle: experimental + type: backstage-backend-plugin-module + owner: xantier diff --git a/plugins/tech-insights-backend/catalog-info.yaml b/plugins/tech-insights-backend/catalog-info.yaml new file mode 100644 index 0000000000..ac3e86c4d6 --- /dev/null +++ b/plugins/tech-insights-backend/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-tech-insights-backend + title: '@backstage/plugin-tech-insights-backend' +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: xantier diff --git a/plugins/tech-insights-common/catalog-info.yaml b/plugins/tech-insights-common/catalog-info.yaml new file mode 100644 index 0000000000..3f5cac294f --- /dev/null +++ b/plugins/tech-insights-common/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-tech-insights-common + title: '@backstage/plugin-tech-insights-common' +spec: + lifecycle: experimental + type: backstage-common-library + owner: maintainers diff --git a/plugins/tech-insights-node/catalog-info.yaml b/plugins/tech-insights-node/catalog-info.yaml new file mode 100644 index 0000000000..6f7bcc3eb9 --- /dev/null +++ b/plugins/tech-insights-node/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-tech-insights-node + title: '@backstage/plugin-tech-insights-node' +spec: + lifecycle: experimental + type: backstage-node-library + owner: maintainers diff --git a/plugins/tech-insights/catalog-info.yaml b/plugins/tech-insights/catalog-info.yaml new file mode 100644 index 0000000000..0c65b59d12 --- /dev/null +++ b/plugins/tech-insights/catalog-info.yaml @@ -0,0 +1,9 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-tech-insights + title: '@backstage/plugin-tech-insights' +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/tech-radar/catalog-info.yaml b/plugins/tech-radar/catalog-info.yaml new file mode 100644 index 0000000000..ea88b4e264 --- /dev/null +++ b/plugins/tech-radar/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-tech-radar + title: '@backstage/plugin-tech-radar' + description: A Backstage plugin that lets you display a Tech Radar for your organization +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx index df39c2ce55..7e2dabc3d4 100644 --- a/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx +++ b/plugins/tech-radar/src/components/RadarDescription/RadarDescription.tsx @@ -45,7 +45,13 @@ const RadarDescription = (props: Props): JSX.Element => { const { open, onClose, title, description, timeline, url, links } = props; return ( - + {title} diff --git a/plugins/techdocs-backend/config.d.ts b/plugins/techdocs-backend/config.d.ts index 3a05beabdc..9ad2594ffd 100644 --- a/plugins/techdocs-backend/config.d.ts +++ b/plugins/techdocs-backend/config.d.ts @@ -57,6 +57,11 @@ export interface Config { * will be broken in these scenarios. */ legacyCopyReadmeMdToIndexMd?: boolean; + + /** + * List of mkdocs plugins which should be added as default to all mkdocs.yml files. + */ + defaultPlugins?: string[]; }; }; diff --git a/plugins/techdocs-module-addons-contrib/src/LigthBox/LightBox.tsx b/plugins/techdocs-module-addons-contrib/src/LigthBox/LightBox.tsx index b74c2f9659..38269e6245 100644 --- a/plugins/techdocs-module-addons-contrib/src/LigthBox/LightBox.tsx +++ b/plugins/techdocs-module-addons-contrib/src/LigthBox/LightBox.tsx @@ -17,7 +17,7 @@ import { useEffect } from 'react'; import { useShadowRootElements } from '@backstage/plugin-techdocs-react'; // @ts-ignore -import PhotoSwipeLightbox, { DataSource } from 'photoswipe/lightbox'; +import PhotoSwipeLightbox, { DataSource, ZoomLevel } from 'photoswipe/lightbox'; import PhotoSwipe from 'photoswipe'; import 'photoswipe/style.css'; import './lightbox.css'; @@ -30,6 +30,21 @@ export const LightBoxAddon = () => { let lightbox = new PhotoSwipeLightbox({ pswpModule: PhotoSwipe, + initialZoomLevel: 1, + secondaryZoomLevel: (zoomLevelObject: ZoomLevel) => { + // photoswipe/lightbox won't zoom the image further then the given width and height. + // therefore we need to calculate the zoom factor needed to fit the complete image in the viewport manually. + const imageWidth = zoomLevelObject.elementSize.x; + const imageHeight = zoomLevelObject.elementSize.y; + const viewportWidth = zoomLevelObject.panAreaSize.x; + const viewportHeight = zoomLevelObject.panAreaSize.y; + + const widthScale = viewportWidth / imageWidth; + const heightScale = viewportHeight / imageHeight; + + const scaleFactor = Math.min(widthScale, heightScale); + return scaleFactor; + }, wheelToZoom: true, arrowPrevSVG: '', @@ -37,8 +52,9 @@ export const LightBoxAddon = () => { '', closeSVG: '', - zoomSVG: - '', + zoomSVG: ``, }); images.forEach((image, index) => { diff --git a/plugins/techdocs-module-addons-contrib/src/LigthBox/lightbox.css b/plugins/techdocs-module-addons-contrib/src/LigthBox/lightbox.css index 3371d0fa53..c864699bed 100644 --- a/plugins/techdocs-module-addons-contrib/src/LigthBox/lightbox.css +++ b/plugins/techdocs-module-addons-contrib/src/LigthBox/lightbox.css @@ -9,3 +9,13 @@ .pswp__img { cursor: pointer !important; } + +/* hide "-" icon path */ +.pswp--zoomed-allowed #photoswipe-zoom-icon-zoomout-path { + display: none; +} + +/* hide "+" path and show "-" */ +.pswp--zoomed-in #photoswipe-zoom-icon-zoomin-path { + display: none; +} diff --git a/plugins/techdocs-node/src/stages/generate/helpers.test.ts b/plugins/techdocs-node/src/stages/generate/helpers.test.ts index 6bcdf0d984..e20e20ea5b 100644 --- a/plugins/techdocs-node/src/stages/generate/helpers.test.ts +++ b/plugins/techdocs-node/src/stages/generate/helpers.test.ts @@ -33,7 +33,7 @@ import { } from './helpers'; import { patchMkdocsYmlPreBuild, - pathMkdocsYmlWithTechdocsPlugin, + patchMkdocsYmlWithPlugins, } from './mkdocsPatchers'; import yaml from 'js-yaml'; @@ -310,7 +310,7 @@ describe('helpers', () => { }); }); - describe('pathMkdocsYmlWithTechdocsPlugin', () => { + describe('patchMkdocsYmlWithPlugins', () => { beforeEach(() => { mockFs({ '/mkdocs_with_techdocs_plugin.yml': mkdocsYmlWithTechdocsPlugins, @@ -319,7 +319,7 @@ describe('helpers', () => { }); }); it('should not add additional plugins if techdocs exists already in mkdocs file', async () => { - await pathMkdocsYmlWithTechdocsPlugin( + await patchMkdocsYmlWithPlugins( '/mkdocs_with_techdocs_plugin.yml', mockLogger, ); @@ -334,7 +334,7 @@ describe('helpers', () => { expect(parsedYml.plugins).toContain('techdocs-core'); }); it("should add the needed plugin if it doesn't exist in mkdocs file", async () => { - await pathMkdocsYmlWithTechdocsPlugin( + await patchMkdocsYmlWithPlugins( '/mkdocs_without_plugins.yml', mockLogger, ); @@ -347,7 +347,7 @@ describe('helpers', () => { expect(parsedYml.plugins).toContain('techdocs-core'); }); it('should not override existing plugins', async () => { - await pathMkdocsYmlWithTechdocsPlugin( + await patchMkdocsYmlWithPlugins( '/mkdocs_with_additional_plugins.yml', mockLogger, ); @@ -362,6 +362,23 @@ describe('helpers', () => { expect(parsedYml.plugins).toContain('not-techdocs-core'); expect(parsedYml.plugins).toContain('also-not-techdocs-core'); }); + it('should add all provided default plugins', async () => { + await patchMkdocsYmlWithPlugins( + '/mkdocs_with_additional_plugins.yml', + mockLogger, + ['techdocs-core', 'custom-plugin'], + ); + + const updatedMkdocsYml = await fs.readFile( + '/mkdocs_with_additional_plugins.yml', + ); + const parsedYml = yaml.load(updatedMkdocsYml.toString()) as { + plugins: string[]; + }; + expect(parsedYml.plugins).toHaveLength(4); + expect(parsedYml.plugins).toContain('techdocs-core'); + expect(parsedYml.plugins).toContain('custom-plugin'); + }); }); describe('patchIndexPreBuild', () => { diff --git a/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts b/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts index d03b5d83c2..945aab3884 100644 --- a/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts +++ b/plugins/techdocs-node/src/stages/generate/mkdocsPatchers.ts @@ -138,27 +138,34 @@ export const patchMkdocsYmlPreBuild = async ( * Update the mkdocs.yml file before TechDocs generator uses it to generate docs site. * * List of tasks: - * - Add techdocs-core plugin to mkdocs file if it doesn't exist + * - Add all provided default plugins * * This function will not throw an error since this is not critical to the whole TechDocs pipeline. * Instead it will log warnings if there are any errors in reading, parsing or writing YAML. * * @param mkdocsYmlPath - Absolute path to mkdocs.yml or equivalent of a docs site * @param logger - A logger instance + * @param defaultPlugins - List of default mkdocs plugins */ -export const pathMkdocsYmlWithTechdocsPlugin = async ( +export const patchMkdocsYmlWithPlugins = async ( mkdocsYmlPath: string, logger: Logger, + defaultPlugins: string[] = ['techdocs-core'], ) => { await patchMkdocsFile(mkdocsYmlPath, logger, mkdocsYml => { - // Modify mkdocs.yaml to contain the needed techdocs-core plugin if it is not there + // Modify mkdocs.yaml to contain the required default plugins if (!('plugins' in mkdocsYml)) { - mkdocsYml.plugins = ['techdocs-core']; + mkdocsYml.plugins = defaultPlugins; return true; } - if (mkdocsYml.plugins && !mkdocsYml.plugins.includes('techdocs-core')) { - mkdocsYml.plugins.push('techdocs-core'); + if ( + mkdocsYml.plugins && + !defaultPlugins.every(plugin => mkdocsYml.plugins!.includes(plugin)) + ) { + mkdocsYml.plugins = [ + ...new Set([...mkdocsYml.plugins, ...defaultPlugins]), + ]; return true; } return false; diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.test.ts b/plugins/techdocs-node/src/stages/generate/techdocs.test.ts index 11c9ee0135..82bfcfff55 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.test.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.test.ts @@ -157,4 +157,24 @@ describe('readGeneratorConfig', () => { legacyCopyReadmeMdToIndexMd: true, }); }); + + it('should read the default plugins config', () => { + const config = new ConfigReader({ + techdocs: { + generator: { + runIn: 'docker', + dockerImage: 'my-org/techdocs', + pullImage: false, + mkdocs: { defaultPlugins: ['mkdocs-custom-plugin'] }, + }, + }, + }); + + expect(readGeneratorConfig(config, logger)).toEqual({ + runIn: 'docker', + dockerImage: 'my-org/techdocs', + pullImage: false, + defaultPlugins: ['mkdocs-custom-plugin'], + }); + }); }); diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index 06160b85c9..b1baf214b3 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -33,7 +33,7 @@ import { import { patchMkdocsYmlPreBuild, - pathMkdocsYmlWithTechdocsPlugin, + patchMkdocsYmlWithPlugins, } from './mkdocsPatchers'; import { GeneratorBase, @@ -121,10 +121,18 @@ export class TechdocsGenerator implements GeneratorBase { await patchIndexPreBuild({ inputDir, logger: childLogger, docsDir }); } - if (!this.options.omitTechdocsCoreMkdocsPlugin) { - await pathMkdocsYmlWithTechdocsPlugin(mkdocsYmlPath, childLogger); + // patch the list of mkdocs plugins + const defaultPlugins = this.options.defaultPlugins ?? []; + + if ( + !this.options.omitTechdocsCoreMkdocsPlugin && + !defaultPlugins.includes('techdocs-core') + ) { + defaultPlugins.push('techdocs-core'); } + await patchMkdocsYmlWithPlugins(mkdocsYmlPath, childLogger, defaultPlugins); + // Directories to bind on container const mountDirs = { [inputDir]: '/input', @@ -233,5 +241,8 @@ export function readGeneratorConfig( legacyCopyReadmeMdToIndexMd: config.getOptionalBoolean( 'techdocs.generator.mkdocs.legacyCopyReadmeMdToIndexMd', ), + defaultPlugins: config.getOptionalStringArray( + 'techdocs.generator.mkdocs.defaultPlugins', + ), }; } diff --git a/plugins/techdocs-node/src/stages/generate/types.ts b/plugins/techdocs-node/src/stages/generate/types.ts index 70c332344a..eca1057914 100644 --- a/plugins/techdocs-node/src/stages/generate/types.ts +++ b/plugins/techdocs-node/src/stages/generate/types.ts @@ -41,6 +41,7 @@ export type GeneratorConfig = { pullImage?: boolean; omitTechdocsCoreMkdocsPlugin?: boolean; legacyCopyReadmeMdToIndexMd?: boolean; + defaultPlugins?: string[]; }; /** diff --git a/plugins/todo-backend/catalog-info.yaml b/plugins/todo-backend/catalog-info.yaml new file mode 100644 index 0000000000..6406a6baae --- /dev/null +++ b/plugins/todo-backend/catalog-info.yaml @@ -0,0 +1,12 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-todo-backend + title: '@backstage/plugin-todo-backend' + description: >- + A Backstage backend plugin that lets you browse TODO comments in your source + code +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/todo/catalog-info.yaml b/plugins/todo/catalog-info.yaml new file mode 100644 index 0000000000..4ab6c9c927 --- /dev/null +++ b/plugins/todo/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-todo + title: '@backstage/plugin-todo' + description: A Backstage plugin that lets you browse TODO comments in your source code +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/user-settings-backend/catalog-info.yaml b/plugins/user-settings-backend/catalog-info.yaml new file mode 100644 index 0000000000..b380b6691f --- /dev/null +++ b/plugins/user-settings-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-user-settings-backend + title: '@backstage/plugin-user-settings-backend' + description: The Backstage backend plugin to manage user settings +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: sda-se-reviewers diff --git a/plugins/user-settings/catalog-info.yaml b/plugins/user-settings/catalog-info.yaml new file mode 100644 index 0000000000..a69f4ba1a5 --- /dev/null +++ b/plugins/user-settings/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-user-settings + title: '@backstage/plugin-user-settings' + description: A Backstage plugin that provides a settings page +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/vault-backend/catalog-info.yaml b/plugins/vault-backend/catalog-info.yaml new file mode 100644 index 0000000000..d4fd503873 --- /dev/null +++ b/plugins/vault-backend/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-vault-backend + title: '@backstage/plugin-vault-backend' + description: A Backstage backend plugin that integrates towards Vault +spec: + lifecycle: experimental + type: backstage-backend-plugin + owner: maintainers diff --git a/plugins/vault/catalog-info.yaml b/plugins/vault/catalog-info.yaml new file mode 100644 index 0000000000..3f9d97ea09 --- /dev/null +++ b/plugins/vault/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-vault + title: '@backstage/plugin-vault' + description: A Backstage plugin that integrates towards Vault +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/plugins/xcmetrics/catalog-info.yaml b/plugins/xcmetrics/catalog-info.yaml new file mode 100644 index 0000000000..5550198a2f --- /dev/null +++ b/plugins/xcmetrics/catalog-info.yaml @@ -0,0 +1,10 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage-plugin-xcmetrics + title: '@backstage/plugin-xcmetrics' + description: A Backstage plugin that shows XCode build metrics for your components +spec: + lifecycle: experimental + type: backstage-frontend-plugin + owner: maintainers diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 61718c7a2f..ae900c5eb3 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -11739,8 +11739,8 @@ __metadata: linkType: hard "webpack@npm:^5.73.0": - version: 5.88.1 - resolution: "webpack@npm:5.88.1" + version: 5.88.2 + resolution: "webpack@npm:5.88.2" dependencies: "@types/eslint-scope": ^3.7.3 "@types/estree": ^1.0.0 @@ -11771,7 +11771,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 726e7e05ab2e7c142609a673dd6aa1a711ed97f349418a2a393d650c5ddad172d191257f60e1e37f6b2a77261571c202aabd5ce9240791a686774f0801cf5ec2 + checksum: 79476a782da31a21f6dd38fbbd06b68da93baf6a62f0d08ca99222367f3b8668f5a1f2086b7bb78e23172e31fa6df6fa7ab09b25e827866c4fc4dc2b30443ce2 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index ae6b5bac8a..c33284c981 100644 --- a/yarn.lock +++ b/yarn.lock @@ -199,8 +199,8 @@ __metadata: linkType: hard "@apollo/server@npm:^4.0.0": - version: 4.9.1 - resolution: "@apollo/server@npm:4.9.1" + version: 4.9.2 + resolution: "@apollo/server@npm:4.9.2" dependencies: "@apollo/cache-control-types": ^1.0.3 "@apollo/server-gateway-interface": ^1.1.1 @@ -230,7 +230,7 @@ __metadata: whatwg-mimetype: ^3.0.0 peerDependencies: graphql: ^16.6.0 - checksum: d5299acfc8f0519adb5aed59e067880bdfd2528353711be07c935d0038ef95e06eecfc58af473011d215c92659f1858ae06c26fe9f70f865383d8e5457e17be5 + checksum: 4ae005e7959ef34ec85f5c421e983f35b6b4b4e93cd21af081c314e554296df1c2f5794b76328b780222b05f59b46ee479365310352414b289d7b58682b0a9c1 languageName: node linkType: hard @@ -1597,8 +1597,8 @@ __metadata: linkType: hard "@azure/core-http@npm:^3.0.0": - version: 3.0.0 - resolution: "@azure/core-http@npm:3.0.0" + version: 3.0.2 + resolution: "@azure/core-http@npm:3.0.2" dependencies: "@azure/abort-controller": ^1.0.0 "@azure/core-auth": ^1.3.0 @@ -1613,8 +1613,8 @@ __metadata: tslib: ^2.2.0 tunnel: ^0.0.6 uuid: ^8.3.0 - xml2js: ^0.4.19 - checksum: 9b6885a60a8fdac924cc9544e09d66757ff4ed51ed20ce3c279321e684e21c295c091ad9d45477e02baae5255e3a92c5fbb2ac46687d261d66488c294b32846f + xml2js: ^0.5.0 + checksum: 01b5a75e09533476dbb69c672dcd00d48298cf81db5015cd261c510c5be377176db8e3dc4809e6952459bfbe214f52f8a1ed84a116ac31b8a7388b2025098f66 languageName: node linkType: hard @@ -3197,12 +3197,12 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": - version: 7.21.0 - resolution: "@babel/runtime@npm:7.21.0" +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.6, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.22.10, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.0, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": + version: 7.22.11 + resolution: "@babel/runtime@npm:7.22.11" dependencies: - regenerator-runtime: ^0.13.11 - checksum: 7b33e25bfa9e0e1b9e8828bb61b2d32bdd46b41b07ba7cb43319ad08efc6fda8eb89445193e67d6541814627df0ca59122c0ea795e412b99c5183a0540d338ab + regenerator-runtime: ^0.14.0 + checksum: a5cd6683a8fcdb8065cb1677f221e22f6c67ec8f15ad1d273b180b93ab3bd86c66da2c48f500d4e72d8d2cfa85ff4872a3f350e5aa3855630036af5da765c001 languageName: node linkType: hard @@ -3877,7 +3877,7 @@ __metadata: languageName: node linkType: hard -"@backstage/core-components@npm:^0.13.3, @backstage/core-components@npm:^0.13.4": +"@backstage/core-components@npm:^0.13.4": version: 0.13.4 resolution: "@backstage/core-components@npm:0.13.4" dependencies: @@ -4140,6 +4140,7 @@ __metadata: "@backstage/config": "workspace:^" "@backstage/frontend-plugin-api": "workspace:^" "@backstage/plugin-graphiql": "workspace:^" + "@backstage/types": "workspace:^" "@testing-library/jest-dom": ^5.10.1 lodash: ^4.17.21 peerDependencies: @@ -4153,8 +4154,11 @@ __metadata: resolution: "@backstage/frontend-plugin-api@workspace:packages/frontend-plugin-api" dependencies: "@backstage/cli": "workspace:^" + "@backstage/types": "workspace:^" "@testing-library/jest-dom": ^5.10.1 lodash: ^4.17.21 + zod: ^3.21.4 + zod-to-json-schema: ^3.21.4 peerDependencies: react: ^16.13.1 || ^17.0.0 languageName: unknown @@ -4179,14 +4183,14 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@npm:^1.1.15": - version: 1.1.17 - resolution: "@backstage/integration-react@npm:1.1.17" +"@backstage/integration-react@npm:^1.1.18": + version: 1.1.18 + resolution: "@backstage/integration-react@npm:1.1.18" dependencies: "@backstage/config": ^1.0.8 "@backstage/core-components": ^0.13.4 "@backstage/core-plugin-api": ^1.5.3 - "@backstage/integration": ^1.6.1 + "@backstage/integration": ^1.6.2 "@backstage/theme": ^0.4.1 "@material-ui/core": ^4.12.2 "@material-ui/icons": ^4.9.1 @@ -4196,7 +4200,7 @@ __metadata: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 11885153a4eb93423650333c48b853432b5c5e622809a6ab10d0121037a4433e6fd0343b56c8e3738fe0d2794bfb48ef3ae9fcd4b4947a11edef528b5e468b29 + checksum: ec2ea86510947f4ecb59838fb540f7318e1e13f859b16340fa52e33d7e014e8796528fcd2e5a37a7c525d48c7df0533954565b561c89575f7075d933ba1ed340 languageName: node linkType: hard @@ -4230,9 +4234,9 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration@npm:^1.6.1": - version: 1.6.1 - resolution: "@backstage/integration@npm:1.6.1" +"@backstage/integration@npm:^1.6.2": + version: 1.6.2 + resolution: "@backstage/integration@npm:1.6.2" dependencies: "@azure/identity": ^3.2.1 "@backstage/config": ^1.0.8 @@ -4243,7 +4247,7 @@ __metadata: git-url-parse: ^13.0.0 lodash: ^4.17.21 luxon: ^3.0.0 - checksum: 1846abd59b8366219956ecedf6a24a20f65bbe8594eda37f8cca06485c31df173630774fed543a617a26498b9ba367224eb019edc29a36c7c32435fc8e1b24f1 + checksum: 5ae6a3def92ee9feb114fecea8a36337df592f49f23875fa619b0eba223f287ed7bbe993c15cac99c2b45c3150e10bb2e1bb4389b34a09de5377365ab13e9ae7 languageName: node linkType: hard @@ -4764,6 +4768,7 @@ __metadata: "@backstage/types": "workspace:^" "@davidzemon/passport-okta-oauth": ^0.0.5 "@google-cloud/firestore": ^6.0.0 + "@node-saml/passport-saml": ^4.0.4 "@types/body-parser": ^1.19.0 "@types/cookie-parser": ^1.4.2 "@types/express": ^4.17.6 @@ -4806,7 +4811,6 @@ __metadata: passport-microsoft: ^1.0.0 passport-oauth2: ^1.6.1 passport-onelogin-oauth: ^0.0.1 - passport-saml: ^3.1.2 supertest: ^6.1.3 uuid: ^8.0.0 winston: ^3.2.1 @@ -5698,16 +5702,16 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@npm:^1.2.4, @backstage/plugin-catalog-react@npm:^1.8.0, @backstage/plugin-catalog-react@npm:^1.8.2": - version: 1.8.2 - resolution: "@backstage/plugin-catalog-react@npm:1.8.2" +"@backstage/plugin-catalog-react@npm:^1.2.4, @backstage/plugin-catalog-react@npm:^1.8.3": + version: 1.8.3 + resolution: "@backstage/plugin-catalog-react@npm:1.8.3" dependencies: "@backstage/catalog-client": ^1.4.3 "@backstage/catalog-model": ^1.4.1 "@backstage/core-components": ^0.13.4 "@backstage/core-plugin-api": ^1.5.3 "@backstage/errors": ^1.2.1 - "@backstage/integration": ^1.6.1 + "@backstage/integration": ^1.6.2 "@backstage/plugin-catalog-common": ^1.0.15 "@backstage/plugin-permission-common": ^0.7.7 "@backstage/plugin-permission-react": ^0.4.14 @@ -5731,7 +5735,7 @@ __metadata: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: e49413cfe0a41f9d8991355f74903c8b84a000134dd6ea07a3410a0940f0c28311bf75c50ae9f9450f4322a628b286209a56e00c8326402bb64f49f8854abf8d + checksum: 8d334dc8eb35399fb4fde7eb218589cc3bedf6f65665a0f379a578ca892ab4a06a83a6e0de2c392e16d12981a91c903efdaa78f577639dd8f73604c61541f60e languageName: node linkType: hard @@ -7166,15 +7170,15 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-home@npm:^0.5.4": - version: 0.5.6 - resolution: "@backstage/plugin-home@npm:0.5.6" +"@backstage/plugin-home@npm:^0.5.7": + version: 0.5.7 + resolution: "@backstage/plugin-home@npm:0.5.7" dependencies: "@backstage/catalog-model": ^1.4.1 "@backstage/config": ^1.0.8 "@backstage/core-components": ^0.13.4 "@backstage/core-plugin-api": ^1.5.3 - "@backstage/plugin-catalog-react": ^1.8.2 + "@backstage/plugin-catalog-react": ^1.8.3 "@backstage/plugin-home-react": ^0.1.2 "@backstage/theme": ^0.4.1 "@material-ui/core": ^4.12.2 @@ -7194,7 +7198,7 @@ __metadata: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: cbf90edc4957009a61c661f7462218ffeabd20df6dc052be6804c98004585ad5886c6fb5e6a38943eaabd6da23a7af982acf2e9848ddaff2fa6a9897fbce7a92 + checksum: 9d6d77fc07507b5b861fa650eaba318af693f9c972eb33513b499eda4abba242ae75621403a59ae9a0ba1370c25229405081af208a3bd52455942cfa17aa89fe languageName: node linkType: hard @@ -7232,7 +7236,7 @@ __metadata: react-grid-layout: ^1.3.4 react-resizable: ^3.0.4 react-use: ^17.2.4 - zod: ~3.21.4 + zod: ^3.21.4 peerDependencies: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 @@ -7430,6 +7434,7 @@ __metadata: "@backstage/plugin-kubernetes-common": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-permission-node": "workspace:^" + "@backstage/types": "workspace:^" "@google-cloud/container": ^4.0.0 "@jest-mock/express": ^2.0.1 "@kubernetes/client-node": 0.18.1 @@ -7918,6 +7923,7 @@ __metadata: "@testing-library/dom": ^8.0.0 "@testing-library/jest-dom": ^5.10.1 "@testing-library/react": ^12.1.3 + "@testing-library/react-hooks": ^8.0.1 "@testing-library/user-event": ^14.0.0 "@types/node": ^16.11.26 "@types/react": ^16.13.1 || ^17.0.0 @@ -10427,16 +10433,16 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:^11.10.5, @emotion/cache@npm:^11.10.7": - version: 11.10.7 - resolution: "@emotion/cache@npm:11.10.7" +"@emotion/cache@npm:^11.10.5, @emotion/cache@npm:^11.11.0": + version: 11.11.0 + resolution: "@emotion/cache@npm:11.11.0" dependencies: - "@emotion/memoize": ^0.8.0 - "@emotion/sheet": ^1.2.1 - "@emotion/utils": ^1.2.0 - "@emotion/weak-memoize": ^0.3.0 - stylis: 4.1.3 - checksum: 6b1efed2dffc93dac419409d91f6d57a200d858ec5ffa4b7c30080fdbd93db431ff86bb779c5b8830b8373f3c5dd754d9beb386604ed2667c7d55608ff653dfc + "@emotion/memoize": ^0.8.1 + "@emotion/sheet": ^1.2.2 + "@emotion/utils": ^1.2.1 + "@emotion/weak-memoize": ^0.3.1 + stylis: 4.2.0 + checksum: 8eb1dc22beaa20c21a2e04c284d5a2630a018a9d51fb190e52de348c8d27f4e8ca4bbab003d68b4f6cd9cc1c569ca747a997797e0f76d6c734a660dc29decf08 languageName: node linkType: hard @@ -10454,19 +10460,19 @@ __metadata: languageName: node linkType: hard -"@emotion/is-prop-valid@npm:^1.1.0, @emotion/is-prop-valid@npm:^1.2.0": - version: 1.2.0 - resolution: "@emotion/is-prop-valid@npm:1.2.0" +"@emotion/is-prop-valid@npm:^1.1.0, @emotion/is-prop-valid@npm:^1.2.0, @emotion/is-prop-valid@npm:^1.2.1": + version: 1.2.1 + resolution: "@emotion/is-prop-valid@npm:1.2.1" dependencies: - "@emotion/memoize": ^0.8.0 - checksum: cc7a19850a4c5b24f1514665289442c8c641709e6f7711067ad550e05df331da0692a16148e85eda6f47e31b3261b64d74c5e25194d053223be16231f969d633 + "@emotion/memoize": ^0.8.1 + checksum: 8f42dc573a3fad79b021479becb639b8fe3b60bdd1081a775d32388bca418ee53074c7602a4c845c5f75fa6831eb1cbdc4d208cc0299f57014ed3a02abcad16a languageName: node linkType: hard -"@emotion/memoize@npm:^0.8.0": - version: 0.8.0 - resolution: "@emotion/memoize@npm:0.8.0" - checksum: c87bb110b829edd8e1c13b90a6bc37cebc39af29c7599a1e66a48e06f9bec43e8e53495ba86278cc52e7589549492c8dfdc81d19f4fdec0cee6ba13d2ad2c928 +"@emotion/memoize@npm:^0.8.0, @emotion/memoize@npm:^0.8.1": + version: 0.8.1 + resolution: "@emotion/memoize@npm:0.8.1" + checksum: a19cc01a29fcc97514948eaab4dc34d8272e934466ed87c07f157887406bc318000c69ae6f813a9001c6a225364df04249842a50e692ef7a9873335fbcc141b0 languageName: node linkType: hard @@ -10507,10 +10513,10 @@ __metadata: languageName: node linkType: hard -"@emotion/sheet@npm:^1.2.1": - version: 1.2.1 - resolution: "@emotion/sheet@npm:1.2.1" - checksum: ce78763588ea522438156344d9f592203e2da582d8d67b32e1b0b98eaba26994c6c270f8c7ad46442fc9c0a9f048685d819cd73ca87e544520fd06f0e24a1562 +"@emotion/sheet@npm:^1.2.2": + version: 1.2.2 + resolution: "@emotion/sheet@npm:1.2.2" + checksum: d973273c9c15f1c291ca2269728bf044bd3e92a67bca87943fa9ec6c3cd2b034f9a6bfe95ef1b5d983351d128c75b547b43ff196a00a3875f7e1d269793cecfe languageName: node linkType: hard @@ -10567,17 +10573,17 @@ __metadata: languageName: node linkType: hard -"@emotion/utils@npm:^1.2.0": - version: 1.2.0 - resolution: "@emotion/utils@npm:1.2.0" - checksum: 55457a49ddd4db6a014ea0454dc09eaa23eedfb837095c8ff90470cb26a303f7ceb5fcc1e2190ef64683e64cfd33d3ba3ca3109cd87d12bc9e379e4195c9a4dd +"@emotion/utils@npm:^1.2.0, @emotion/utils@npm:^1.2.1": + version: 1.2.1 + resolution: "@emotion/utils@npm:1.2.1" + checksum: e0b44be0705b56b079c55faff93952150be69e79b660ae70ddd5b6e09fc40eb1319654315a9f34bb479d7f4ec94be6068c061abbb9e18b9778ae180ad5d97c73 languageName: node linkType: hard -"@emotion/weak-memoize@npm:^0.3.0": - version: 0.3.0 - resolution: "@emotion/weak-memoize@npm:0.3.0" - checksum: f43ef4c8b7de70d9fa5eb3105921724651e4188e895beb71f0c5919dc899a7b8743e1fdd99d38b9092dd5722c7be2312ebb47fbdad0c4e38bea58f6df5885cc0 +"@emotion/weak-memoize@npm:^0.3.0, @emotion/weak-memoize@npm:^0.3.1": + version: 0.3.1 + resolution: "@emotion/weak-memoize@npm:0.3.1" + checksum: b2be47caa24a8122622ea18cd2d650dbb4f8ad37b636dc41ed420c2e082f7f1e564ecdea68122b546df7f305b159bf5ab9ffee872abd0f052e687428459af594 languageName: node linkType: hard @@ -11015,6 +11021,44 @@ __metadata: languageName: node linkType: hard +"@floating-ui/core@npm:^1.4.1": + version: 1.4.1 + resolution: "@floating-ui/core@npm:1.4.1" + dependencies: + "@floating-ui/utils": ^0.1.1 + checksum: be4ab864fe17eeba5e205bd554c264b9a4895a57c573661bbf638357fa3108677fed7ba3269ec15b4da90e29274c9b626d5a15414e8d1fe691e210d02a03695c + languageName: node + linkType: hard + +"@floating-ui/dom@npm:^1.3.0": + version: 1.5.1 + resolution: "@floating-ui/dom@npm:1.5.1" + dependencies: + "@floating-ui/core": ^1.4.1 + "@floating-ui/utils": ^0.1.1 + checksum: ddb509030978536ba7b321cf8c764ae9d0142a3b1fefb7e6bc050a5de7e825e12131fa5089009edabf7c125fb274886da211a5220fe17a71d875a7a96eb1386c + languageName: node + linkType: hard + +"@floating-ui/react-dom@npm:^2.0.1": + version: 2.0.1 + resolution: "@floating-ui/react-dom@npm:2.0.1" + dependencies: + "@floating-ui/dom": ^1.3.0 + peerDependencies: + react: ">=16.8.0" + react-dom: ">=16.8.0" + checksum: 00fef2cf69ac2b15952e47505fd9e23f6cc5c20a26adc707862932826d1682f3c30f83c9887abfc93574fdca2d34dd2fc00271527318b1db403549cd6bc9eb00 + languageName: node + linkType: hard + +"@floating-ui/utils@npm:^0.1.1": + version: 0.1.1 + resolution: "@floating-ui/utils@npm:0.1.1" + checksum: 548acdda7902f45b0afbe34e2e7f4cbff0696b95bad8c039f80936519de24ef2ec20e79902825b7815294b37f51a7c52ee86288b0688869a57cc229a164d86b4 + languageName: node + linkType: hard + "@fmvilas/pseudo-yaml-ast@npm:^0.3.1": version: 0.3.1 resolution: "@fmvilas/pseudo-yaml-ast@npm:0.3.1" @@ -12920,8 +12964,8 @@ __metadata: linkType: hard "@material-ui/pickers@npm:^3.2.10, @material-ui/pickers@npm:^3.3.10": - version: 3.3.10 - resolution: "@material-ui/pickers@npm:3.3.10" + version: 3.3.11 + resolution: "@material-ui/pickers@npm:3.3.11" dependencies: "@babel/runtime": ^7.6.0 "@date-io/core": 1.x @@ -12935,7 +12979,7 @@ __metadata: prop-types: ^15.6.0 react: ^16.8.0 || ^17.0.0 react-dom: ^16.8.0 || ^17.0.0 - checksum: b8ec52f2e8a4dfae8f14e29c41428a708c843d2c64b5ece64747d8d9401edaeb4d7de6cce985d13e486f2addec1b7787eeed7a3dfcbba90803c23f1ba2ed2420 + checksum: c97822ae407877d1aa9ab7b14c335511d6879ca2546455ac7a3b156d70966b5678372a6d4d3470c2dced84e59857e2c1e1b2be61d26ab43f7f29806666f33064 languageName: node linkType: hard @@ -13167,16 +13211,17 @@ __metadata: languageName: node linkType: hard -"@mui/base@npm:5.0.0-alpha.127": - version: 5.0.0-alpha.127 - resolution: "@mui/base@npm:5.0.0-alpha.127" +"@mui/base@npm:5.0.0-beta.12": + version: 5.0.0-beta.12 + resolution: "@mui/base@npm:5.0.0-beta.12" dependencies: - "@babel/runtime": ^7.21.0 - "@emotion/is-prop-valid": ^1.2.0 + "@babel/runtime": ^7.22.10 + "@emotion/is-prop-valid": ^1.2.1 + "@floating-ui/react-dom": ^2.0.1 "@mui/types": ^7.2.4 - "@mui/utils": ^5.12.0 - "@popperjs/core": ^2.11.7 - clsx: ^1.2.1 + "@mui/utils": ^5.14.6 + "@popperjs/core": ^2.11.8 + clsx: ^2.0.0 prop-types: ^15.8.1 react-is: ^18.2.0 peerDependencies: @@ -13186,29 +13231,29 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 330baeef1fdaa513707f295e07a3436af24d0831a18109ed877f76b86cf1f392309833d33a2c7ca250db79fdf53f5e88c2618f876f50dbbbf9323bbf08e367e7 + checksum: 3ed9b43323595588ccea7325a79bbaab60bf5a99a970821555c449f53ca4c8f7f298a4d5c8a280c5ea0b2b47273a0b1a03e33ef6cbbbd428b14bcc33222b5f75 languageName: node linkType: hard -"@mui/core-downloads-tracker@npm:^5.12.2": - version: 5.12.2 - resolution: "@mui/core-downloads-tracker@npm:5.12.2" - checksum: d748bdc56df6fdfe6712550a94263cf094c53ddcb70f6a8f633caf23927edf5ac88b1f888429d895fe2a5045b27eb7aa9826ccee5b917e31973667d604ed87fe +"@mui/core-downloads-tracker@npm:^5.14.6": + version: 5.14.6 + resolution: "@mui/core-downloads-tracker@npm:5.14.6" + checksum: 500b6a0e709f75fa8798caf4ad993d1024c046f25f0624e7d36c454ca53aebf119eb8645bb6cf762a2e27520deb843e546c95874e32183f1255044fe73184c79 languageName: node linkType: hard "@mui/material@npm:^5.12.2": - version: 5.12.2 - resolution: "@mui/material@npm:5.12.2" + version: 5.14.6 + resolution: "@mui/material@npm:5.14.6" dependencies: - "@babel/runtime": ^7.21.0 - "@mui/base": 5.0.0-alpha.127 - "@mui/core-downloads-tracker": ^5.12.2 - "@mui/system": ^5.12.1 + "@babel/runtime": ^7.22.10 + "@mui/base": 5.0.0-beta.12 + "@mui/core-downloads-tracker": ^5.14.6 + "@mui/system": ^5.14.6 "@mui/types": ^7.2.4 - "@mui/utils": ^5.12.0 - "@types/react-transition-group": ^4.4.5 - clsx: ^1.2.1 + "@mui/utils": ^5.14.6 + "@types/react-transition-group": ^4.4.6 + clsx: ^2.0.0 csstype: ^3.1.2 prop-types: ^15.8.1 react-is: ^18.2.0 @@ -13226,16 +13271,16 @@ __metadata: optional: true "@types/react": optional: true - checksum: a3464dfbcc496164967e134d6f1d40e060ee02fc2ddb5fcf648ca85580efdbd5eff5aab6a73486d0a7f329f7299127b52501a13993bb131cbd4e310c7f2e633f + checksum: e22977cb010b54ba43e7b13105c7dbe3e5361f960fd4eecc3ed28480a8abc531b44abdb093bafe9ce291c6a23c25039e0677d5d965210d7fc9ac5e25bb68a876 languageName: node linkType: hard -"@mui/private-theming@npm:^5.12.0": - version: 5.12.0 - resolution: "@mui/private-theming@npm:5.12.0" +"@mui/private-theming@npm:^5.14.6": + version: 5.14.6 + resolution: "@mui/private-theming@npm:5.14.6" dependencies: - "@babel/runtime": ^7.21.0 - "@mui/utils": ^5.12.0 + "@babel/runtime": ^7.22.10 + "@mui/utils": ^5.14.6 prop-types: ^15.8.1 peerDependencies: "@types/react": ^17.0.0 || ^18.0.0 @@ -13243,16 +13288,16 @@ __metadata: peerDependenciesMeta: "@types/react": optional: true - checksum: 761bc7a57e1643c2c4c327886882fa5efc7bacae1c6fffe6be4197f49d337261c916a883d96996445efcdedc0672251725c1e5b264b6250d6c9527fd0cafcc62 + checksum: 2d477657c5c10bbc2628b741c85d5bc66f36f379eaafc97f534500584e4ab2f098e8cf29eec0f079b9a34a414027235ab433f3f41771ea4b9cacdcd783111726 languageName: node linkType: hard -"@mui/styled-engine@npm:^5.12.0": - version: 5.12.0 - resolution: "@mui/styled-engine@npm:5.12.0" +"@mui/styled-engine@npm:^5.14.6": + version: 5.14.6 + resolution: "@mui/styled-engine@npm:5.14.6" dependencies: - "@babel/runtime": ^7.21.0 - "@emotion/cache": ^11.10.7 + "@babel/runtime": ^7.22.10 + "@emotion/cache": ^11.11.0 csstype: ^3.1.2 prop-types: ^15.8.1 peerDependencies: @@ -13264,20 +13309,20 @@ __metadata: optional: true "@emotion/styled": optional: true - checksum: 4a415473cf62aa05012f667dd2e9b1dc2fb175be5b0c4d0b8df541e2dac3d7db410e920e0d5910c8e2b8996a4fb51f74d79483e2878a9b5c0d334498f5537d74 + checksum: 7bbef4574195d70945041a645f5d0e372762cad28d873794e750bbd570daf901392205d69c3593ad55c43088361e1b90c7b9798a5d013c4a1783553d42c758dd languageName: node linkType: hard -"@mui/system@npm:^5.12.1": - version: 5.12.1 - resolution: "@mui/system@npm:5.12.1" +"@mui/system@npm:^5.14.6": + version: 5.14.6 + resolution: "@mui/system@npm:5.14.6" dependencies: - "@babel/runtime": ^7.21.0 - "@mui/private-theming": ^5.12.0 - "@mui/styled-engine": ^5.12.0 + "@babel/runtime": ^7.22.10 + "@mui/private-theming": ^5.14.6 + "@mui/styled-engine": ^5.14.6 "@mui/types": ^7.2.4 - "@mui/utils": ^5.12.0 - clsx: ^1.2.1 + "@mui/utils": ^5.14.6 + clsx: ^2.0.0 csstype: ^3.1.2 prop-types: ^15.8.1 peerDependencies: @@ -13292,7 +13337,7 @@ __metadata: optional: true "@types/react": optional: true - checksum: b951959bf5e5af581319354c044f441d97849ff120cfec111d2ed5e7fa05efd63721acff96f48093b8e2bdeb5ccbe35c48613fb925be2b58c596447d10dbed3e + checksum: 5090761f86b858df6109c57c7c1d6912462781b1280d43d672b64e095564eefe4f64f5bf8ebf5ad9cefdc27efba8964e59bc2c234005ca33da4831aff4fed8db languageName: node linkType: hard @@ -13308,18 +13353,18 @@ __metadata: languageName: node linkType: hard -"@mui/utils@npm:^5.12.0": - version: 5.12.0 - resolution: "@mui/utils@npm:5.12.0" +"@mui/utils@npm:^5.14.6": + version: 5.14.6 + resolution: "@mui/utils@npm:5.14.6" dependencies: - "@babel/runtime": ^7.21.0 + "@babel/runtime": ^7.22.10 "@types/prop-types": ^15.7.5 - "@types/react-is": ^16.7.1 || ^17.0.0 + "@types/react-is": ^18.2.1 prop-types: ^15.8.1 react-is: ^18.2.0 peerDependencies: react: ^17.0.0 || ^18.0.0 - checksum: 87b2c7468803b083f50af28d7c215c45291e73fef16570848b596d0f1cde1fc613c20e8951f431217b31451de254744abd50eda5013dedec4982420b5bf1c6b6 + checksum: c5d2f37dec7984b9cf4da10a2fd9eabe23e7481ad0f3674cc9cdc2b7a3938eca28e92068f6d58b42cc807192dbb5e40d4885f8d896a4a01bab32a05c5ac3920b languageName: node linkType: hard @@ -13407,6 +13452,39 @@ __metadata: languageName: node linkType: hard +"@node-saml/node-saml@npm:^4.0.4": + version: 4.0.5 + resolution: "@node-saml/node-saml@npm:4.0.5" + dependencies: + "@types/debug": ^4.1.7 + "@types/passport": ^1.0.11 + "@types/xml-crypto": ^1.4.2 + "@types/xml-encryption": ^1.2.1 + "@types/xml2js": ^0.4.11 + "@xmldom/xmldom": ^0.8.6 + debug: ^4.3.4 + xml-crypto: ^3.0.1 + xml-encryption: ^3.0.2 + xml2js: ^0.5.0 + xmlbuilder: ^15.1.1 + checksum: 7d97575111a381ef2d0f16e1fc85ae3f84322ccba06dcb0594b00cf598e429658f45e479b78836943f69f249c08a8593e5168404acf7f1ed659ead53ceef465e + languageName: node + linkType: hard + +"@node-saml/passport-saml@npm:^4.0.4": + version: 4.0.4 + resolution: "@node-saml/passport-saml@npm:4.0.4" + dependencies: + "@node-saml/node-saml": ^4.0.4 + "@types/express": ^4.17.14 + "@types/passport": ^1.0.11 + "@types/passport-strategy": ^0.2.35 + passport: ^0.6.0 + passport-strategy: ^1.0.0 + checksum: 75178669d7d47038c33bb0602454cb5030fc9b3ecdcae9163a35cef436bc6c22e68e57d06213e0118ff1cb0dcd2f2fa25112672ebe4cbad90578df21bec67fce + languageName: node + linkType: hard + "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -14288,10 +14366,10 @@ __metadata: languageName: node linkType: hard -"@popperjs/core@npm:^2.11.7": - version: 2.11.7 - resolution: "@popperjs/core@npm:2.11.7" - checksum: 5b6553747899683452a1d28898c1b39173a4efd780e74360bfcda8eb42f1c5e819602769c81a10920fc68c881d07fb40429604517d499567eac079cfa6470f19 +"@popperjs/core@npm:^2.11.8": + version: 2.11.8 + resolution: "@popperjs/core@npm:2.11.8" + checksum: e5c69fdebf52a4012f6a1f14817ca8e9599cb1be73dd1387e1785e2ed5e5f0862ff817f420a87c7fc532add1f88a12e25aeb010ffcbdc98eace3d55ce2139cf0 languageName: node linkType: hard @@ -14518,13 +14596,13 @@ __metadata: linkType: hard "@roadiehq/backstage-plugin-buildkite@npm:^2.0.8": - version: 2.1.13 - resolution: "@roadiehq/backstage-plugin-buildkite@npm:2.1.13" + version: 2.1.14 + resolution: "@roadiehq/backstage-plugin-buildkite@npm:2.1.14" dependencies: "@backstage/catalog-model": ^1.4.1 - "@backstage/core-components": ^0.13.3 + "@backstage/core-components": ^0.13.4 "@backstage/core-plugin-api": ^1.5.3 - "@backstage/plugin-catalog-react": ^1.8.0 + "@backstage/plugin-catalog-react": ^1.8.3 "@backstage/theme": ^0.4.1 "@material-ui/core": ^4.12.1 "@material-ui/icons": ^4.11.2 @@ -14537,19 +14615,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: 03945723ee9f71717f7ea1ac0a240fb516b33740b86a11c0be0f22021bb0cfa5eec541b3cadc5e77efa9ef432c4e978885cd0124b8eabc74f410b6fbc7d55f91 + checksum: 938fa38d6199a1c5199eb47851ddc0edb2f0d3064fa189baf104ff820096700cb95843c499353efc77b10c1fc721aa2a32b0146c1d3df1bea31f5af61dfd250b languageName: node linkType: hard "@roadiehq/backstage-plugin-github-insights@npm:^2.0.5": - version: 2.3.16 - resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.3.16" + version: 2.3.18 + resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.3.18" dependencies: "@backstage/catalog-model": ^1.4.1 - "@backstage/core-components": ^0.13.3 + "@backstage/core-components": ^0.13.4 "@backstage/core-plugin-api": ^1.5.3 - "@backstage/integration-react": ^1.1.15 - "@backstage/plugin-catalog-react": ^1.8.0 + "@backstage/integration-react": ^1.1.18 + "@backstage/plugin-catalog-react": ^1.8.3 "@backstage/theme": ^0.4.1 "@date-io/core": 2.10.7 "@material-ui/core": ^4.11.0 @@ -14565,19 +14643,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: 31f857ea5992910a0ef94c53adbe14992aba53021a377123a2cd217a0537c9b310028626f27c97692ea2265ce1b95ea8c5f33605eac54dada3684c2c90333f3f + checksum: 891ab977ef1edb5afd921c52788db19734227248c75dde515e9bed092884d29a9c91c3a0a824f69a7d3fd741d862dba9d83c00fae447e3011e88966d2a30307f languageName: node linkType: hard "@roadiehq/backstage-plugin-github-pull-requests@npm:^2.2.7": - version: 2.5.14 - resolution: "@roadiehq/backstage-plugin-github-pull-requests@npm:2.5.14" + version: 2.5.15 + resolution: "@roadiehq/backstage-plugin-github-pull-requests@npm:2.5.15" dependencies: "@backstage/catalog-model": ^1.4.1 - "@backstage/core-components": ^0.13.3 + "@backstage/core-components": ^0.13.4 "@backstage/core-plugin-api": ^1.5.3 - "@backstage/plugin-catalog-react": ^1.8.0 - "@backstage/plugin-home": ^0.5.4 + "@backstage/plugin-catalog-react": ^1.8.3 + "@backstage/plugin-home": ^0.5.7 "@material-ui/core": ^4.11.0 "@material-ui/icons": ^4.9.1 "@material-ui/lab": ^4.0.0-alpha.60 @@ -14594,18 +14672,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: 6a51fa45c716c0313baedfb5e67a898b1cd0b02b7742473c46e7f6dc9742c88db950da110de7aa0ad2019fb2ed855ae5b9c567d5348ad31a3340ac49c77e1b11 + checksum: 3332a560f8741536ab12ff1e14567e739aa6f60767d8eb0a2e5b9873b54947b0cdaac7c44cb5bb3156dc8b2a7d88d33ee20191c8920bfe8f67b87857dfda2780 languageName: node linkType: hard "@roadiehq/backstage-plugin-travis-ci@npm:^2.0.5": - version: 2.1.13 - resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.1.13" + version: 2.1.14 + resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.1.14" dependencies: "@backstage/catalog-model": ^1.4.1 - "@backstage/core-components": ^0.13.3 + "@backstage/core-components": ^0.13.4 "@backstage/core-plugin-api": ^1.5.3 - "@backstage/plugin-catalog-react": ^1.8.0 + "@backstage/plugin-catalog-react": ^1.8.3 "@backstage/theme": ^0.4.1 "@material-ui/core": ^4.11.3 "@material-ui/icons": ^4.11.2 @@ -14620,7 +14698,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: c8ddeadcc5edec0b25276b65ddf7971943939eb2c82d363919c748b8a740ed53c2642bda6cb509d7fc8c02f002166abf8fc4cb7a2cfa83577e1139c5ba31841b + checksum: f6e297460aa2dccd3ac5b639838a253017229ecf6ec03541702ccb33f93ec15327894e35f7dfa565bc58c137abfd94129a858d43b2b861283370a003b2685dda languageName: node linkType: hard @@ -16834,11 +16912,11 @@ __metadata: linkType: hard "@types/codemirror@npm:^5.0.0": - version: 5.60.8 - resolution: "@types/codemirror@npm:5.60.8" + version: 5.60.9 + resolution: "@types/codemirror@npm:5.60.9" dependencies: "@types/tern": "*" - checksum: bc3a63eab0308b3ef5ed2ca22afb7f1c0b8acde6477e1d569df7e93f3fe4f78754d28a3e081c72931259fe1bf906a319b54bcdeba74600b989e5558d32478496 + checksum: 89da80a8d5c904f573289769098ad7721a4b58e056c1d08168b967bcfb63e30433d9d0bee0c43ff4b6679b0b7b87cfe942cc3f250ca0365ee244c9fb68ac9a70 languageName: node linkType: hard @@ -17007,9 +17085,9 @@ __metadata: linkType: hard "@types/d3-force@npm:^3.0.0": - version: 3.0.4 - resolution: "@types/d3-force@npm:3.0.4" - checksum: 779fb597fb41e7bc6a5e1b8969d500deb95c4a73428c7c268bf0ca6f3ed668dd2ed6aa652de7af14d2f9c192dad4f6e7badf2c5bc330624bd8405ac88440b278 + version: 3.0.5 + resolution: "@types/d3-force@npm:3.0.5" + checksum: 09aa0e999841f69b34296fa92273b5b251c50f74c1b7ec2bdef7a3a7fd2d756467e4ede7cc0dcfc4151c04bde0f3feaa48d9b669c38620fb51cee2d7faa01e24 languageName: node linkType: hard @@ -17046,9 +17124,9 @@ __metadata: linkType: hard "@types/d3-selection@npm:*, @types/d3-selection@npm:^3.0.1": - version: 3.0.5 - resolution: "@types/d3-selection@npm:3.0.5" - checksum: 77ea35c92273cd2d736355ab5a4cf6477086b064a1f608489a6fef4198ee6bc1d5440e5dfd880099265ac96c4f4b4fda659d89fbaaef37ba03708663196ba8ad + version: 3.0.6 + resolution: "@types/d3-selection@npm:3.0.6" + checksum: 01f9f3a41b98280947109911bcc6ebffff5c8c3e617695979c04ef9beef445a8cdc48a9fbdb3c9cff45a7345e83e8a5d6b346d7ceedca6ebc11dfd9717dbff6b languageName: node linkType: hard @@ -17062,11 +17140,11 @@ __metadata: linkType: hard "@types/d3-shape@npm:^3.0.1, @types/d3-shape@npm:^3.1.0": - version: 3.1.1 - resolution: "@types/d3-shape@npm:3.1.1" + version: 3.1.2 + resolution: "@types/d3-shape@npm:3.1.2" dependencies: "@types/d3-path": "*" - checksum: 8f1762ecdeb4833a3802be1c65363cbc7cca753d0b836a3855fde4ba12f8e6fc142dba3c5f6d669a9e89374cc6dc414464e4f2d04e72fafd4bc64819ce30bb63 + checksum: 17399a78872aa89cab600cb7977405136a72798dbbb59a4b3684012c2a2ffb9b62720cceaaedc412088b7da086518a6722c909e03cb81e3e238569722feb60f9 languageName: node linkType: hard @@ -17085,12 +17163,12 @@ __metadata: linkType: hard "@types/d3-zoom@npm:^3.0.1": - version: 3.0.3 - resolution: "@types/d3-zoom@npm:3.0.3" + version: 3.0.4 + resolution: "@types/d3-zoom@npm:3.0.4" dependencies: "@types/d3-interpolate": "*" "@types/d3-selection": "*" - checksum: 1c828538e2b04c47f659ead552824bed1c545dd1934f38abfec95f4a3c1cb4eaf7e107959ebd05a3dfec0166fa98677037ede6f633383c6502b1a05eaeb1d418 + checksum: 6330b8411822b979ab0fd5a7a7755295377b4dfc61fd8f925d97e5b388e3dd604b455c0009b0eda3320c14c4ea367f740a04991a71d35befdca5f75218492a68 languageName: node linkType: hard @@ -17204,14 +17282,14 @@ __metadata: linkType: hard "@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.30, @types/express-serve-static-core@npm:^4.17.33, @types/express-serve-static-core@npm:^4.17.5": - version: 4.17.35 - resolution: "@types/express-serve-static-core@npm:4.17.35" + version: 4.17.36 + resolution: "@types/express-serve-static-core@npm:4.17.36" dependencies: "@types/node": "*" "@types/qs": "*" "@types/range-parser": "*" "@types/send": "*" - checksum: cc8995d10c6feda475ec1b3a0e69eb0f35f21ab6b49129ad5c6f279e0bc5de8175bc04ec51304cb79a43eec3ed2f5a1e01472eb6d5f827b8c35c6ca8ad24eb6e + checksum: 410b13cbd663f18c0f8729e7f2ff54d960d96de76ebbae7cadb612972f85cc66c54051e00d32f11aa230c0a683d81a6d6fc7f7e4e383a95c0801494c517f36e1 languageName: node linkType: hard @@ -17234,7 +17312,7 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.17.13, @types/express@npm:^4.17.17, @types/express@npm:^4.17.6": +"@types/express@npm:*, @types/express@npm:^4.17.13, @types/express@npm:^4.17.14, @types/express@npm:^4.17.17, @types/express@npm:^4.17.6": version: 4.17.17 resolution: "@types/express@npm:4.17.17" dependencies: @@ -17461,11 +17539,11 @@ __metadata: linkType: hard "@types/jquery@npm:^3.3.34": - version: 3.5.16 - resolution: "@types/jquery@npm:3.5.16" + version: 3.5.17 + resolution: "@types/jquery@npm:3.5.17" dependencies: "@types/sizzle": "*" - checksum: 13c995f15d1c2f1d322103dc1cb0a22b95eecc3e7546f00279b8731aea21d7ec04550af40e609ee48e755d4e11bf61c25b4aa9f53df3bcbec4b8fe8e81471732 + checksum: be289003382a8d4a1c6eab9c68845e919e467926557493033e78f6793ed8acd482b01b7b47955e4bf51f65f9b05607beccb32d31d649a2b42f3b07f1444d1f1a languageName: node linkType: hard @@ -17716,11 +17794,11 @@ __metadata: linkType: hard "@types/morgan@npm:^1.9.0": - version: 1.9.4 - resolution: "@types/morgan@npm:1.9.4" + version: 1.9.5 + resolution: "@types/morgan@npm:1.9.5" dependencies: "@types/node": "*" - checksum: d1e99c66a43501dcdf6e94e013dfff4e6c152cbb5f782d954bb722d230a9c1c0fe06fab6df3f3dfa3547735a7598f9471633cc6813d794e9562fd022e217c6ae + checksum: f98deb4c7f2ad6049ad34ed7b0f0d427546bdf2358011070af9d597de1b0a03b38cc10cfe65ef2e7673e569c384303d949e76df701acefe288d547f614142973 languageName: node linkType: hard @@ -17770,9 +17848,9 @@ __metadata: linkType: hard "@types/node@npm:*, @types/node@npm:>=12.12.47, @types/node@npm:>=13.7.0": - version: 20.5.0 - resolution: "@types/node@npm:20.5.0" - checksum: 659bc5fc93b5c02bd88ca4bfae4f6b9dc307d45884d1dd9d69df85819a9943cdc00cd3c87eec3048866df6a67f52297f74d170e47a44f61edb3e8f770d94e85e + version: 20.5.6 + resolution: "@types/node@npm:20.5.6" + checksum: d2ce44f1cfa3fd00fe7426f7cf9a46d680cd57802b874ed5618e7d9101a9c6b8de37f08c0e7185ee06fb363ad492549c3ea69665c7e8e31c7813210ed8e89005 languageName: node linkType: hard @@ -17784,9 +17862,9 @@ __metadata: linkType: hard "@types/node@npm:^14.14.31": - version: 14.18.54 - resolution: "@types/node@npm:14.18.54" - checksum: 9fd66f91fcd8e9b25067f784a9c60bd710ef86a89c838c131ab2b1921398adc53b1c70d741bceed48bb2403b75c434b1bbbb255240773819cde36295c4b6abf1 + version: 14.18.56 + resolution: "@types/node@npm:14.18.56" + checksum: 990cd0e853caa264586aa703ed923767fb445ad240e54cfe7c877c8db2e0b377f85c1bc4b80575c7f07dc9f4c56bdfbc6b5c2704a689cf7c8f9ac4ec2ed84975 languageName: node linkType: hard @@ -17798,16 +17876,16 @@ __metadata: linkType: hard "@types/node@npm:^16.0.0, @types/node@npm:^16.11.26, @types/node@npm:^16.9.2": - version: 16.18.41 - resolution: "@types/node@npm:16.18.41" - checksum: b12650d8e4289edafcf0453c8a66c00d6397d465a48b1c683babba4f16f92a6418f678e98a85751e3fef78d23c2b07f641df8da14bf5428ad8282b57b2695243 + version: 16.18.46 + resolution: "@types/node@npm:16.18.46" + checksum: 1aed3fe9693f2098b8dac8c76c809c1925a456da00dd6f06c1ccb55c62ccfbbe7ec43ccfd12001f9cb4ff84e138292ae53456435dfecd5c4bb1324394c3e09c7 languageName: node linkType: hard "@types/node@npm:^18.11.17": - version: 18.17.5 - resolution: "@types/node@npm:18.17.5" - checksum: b8c658a99234b99425243c324b641ed7b9ceb6bee6b06421fdc9bb7c58f9a5552e353225cc549e6982462ac384abe1985022ed76e2e4728797f59b21f659ca2b + version: 18.17.11 + resolution: "@types/node@npm:18.17.11" + checksum: c31d9f4a6f7c5c413b6548ded0fab046571832718361c5cda325960f32d6b386dd0a7fa9228911111fee54c180d09df82a622efdabdbef89fa9e1923891892bc languageName: node linkType: hard @@ -17927,7 +18005,7 @@ __metadata: languageName: node linkType: hard -"@types/passport@npm:*, @types/passport@npm:^1.0.3": +"@types/passport@npm:*, @types/passport@npm:^1.0.11, @types/passport@npm:^1.0.3": version: 1.0.12 resolution: "@types/passport@npm:1.0.12" dependencies: @@ -18034,12 +18112,12 @@ __metadata: languageName: node linkType: hard -"@types/react-is@npm:^16.7.1 || ^17.0.0": - version: 17.0.3 - resolution: "@types/react-is@npm:17.0.3" +"@types/react-is@npm:^18.2.1": + version: 18.2.1 + resolution: "@types/react-is@npm:18.2.1" dependencies: "@types/react": "*" - checksum: 6abb7c47d54f012272650df8a962a28bd82f219291e5ef8b4dfa7fe0bb98ae243b060bf9fbe8ceff6213141794855a006db194b490b00ffd15842ae19d0ce1f0 + checksum: b44c3262efa2c68fa6fe2beb9ef86170b18305469461a3f97aa14943cc033cb21a26944f718bdb6434eea6e8f7fcba251c4f45b65b897a3fcf751b5a6003cf82 languageName: node linkType: hard @@ -18091,12 +18169,12 @@ __metadata: languageName: node linkType: hard -"@types/react-transition-group@npm:^4.2.0, @types/react-transition-group@npm:^4.4.5": - version: 4.4.5 - resolution: "@types/react-transition-group@npm:4.4.5" +"@types/react-transition-group@npm:^4.2.0, @types/react-transition-group@npm:^4.4.6": + version: 4.4.6 + resolution: "@types/react-transition-group@npm:4.4.6" dependencies: "@types/react": "*" - checksum: 265f1c74061556708ffe8d15559e35c60d6c11478c9950d3735575d2c116ca69f461d85effa06d73a613eb8b73c84fd32682feb57cf7c5f9e4284021dbca25b0 + checksum: 0872143821d7ee20a1d81e965f8b1e837837f11cd2206973f1f98655751992d9390304d58bac192c9cd923eca95bff107d8c9e3364a180240d5c2a6fd70fd7c3 languageName: node linkType: hard @@ -18119,13 +18197,13 @@ __metadata: linkType: hard "@types/react@npm:^17": - version: 17.0.64 - resolution: "@types/react@npm:17.0.64" + version: 17.0.65 + resolution: "@types/react@npm:17.0.65" dependencies: "@types/prop-types": "*" "@types/scheduler": "*" csstype: ^3.0.2 - checksum: 71a7b402e8849ff31ed542d1d1a2ff6adb0e6f0e17321d126e7382a2fcb671ec1e1d1bf7045306922fa90b0319c57a8a45e6215bbf00fb6f73c3860d3f869870 + checksum: bb862f0f97a49e690cd5c62743307d17398ee62b87bbb2c85c1b01cb992244c0aef4ecdedaab0e8d33c6315d5a8f067cf90e7f1c366b8819678c59204e202932 languageName: node linkType: hard @@ -18542,13 +18620,13 @@ __metadata: linkType: hard "@types/webpack@npm:^5.28.0": - version: 5.28.1 - resolution: "@types/webpack@npm:5.28.1" + version: 5.28.2 + resolution: "@types/webpack@npm:5.28.2" dependencies: "@types/node": "*" tapable: ^2.2.0 webpack: ^5 - checksum: 9f9bfe041382acfab36015f95011f6539705ba116fd9b3b8d7599a6396d996b7efcdb71947dba6d24a1a1bf397d15fdfb3c50d2f73e0aa5366a76198d5ec98b3 + checksum: 0b147aaaaa8992417a5ed65af83eb0df9b5247c38ab482f3c844495935a29c474cb21bc865ca36052c353034f40d14c8d232f0c831fdc1a6956baa985d7b1135 languageName: node linkType: hard @@ -18568,7 +18646,26 @@ __metadata: languageName: node linkType: hard -"@types/xml2js@npm:*, @types/xml2js@npm:^0.4.7": +"@types/xml-crypto@npm:^1.4.2": + version: 1.4.2 + resolution: "@types/xml-crypto@npm:1.4.2" + dependencies: + "@types/node": "*" + xpath: 0.0.27 + checksum: 8fe8fc709ba070c46249800105301a533eb95e1a7a696986e4ac753aed1d238a826bce2b1e0d2548559e03f4e57bee27e26df69417766814bc0dd6a9bf2a1763 + languageName: node + linkType: hard + +"@types/xml-encryption@npm:^1.2.1": + version: 1.2.1 + resolution: "@types/xml-encryption@npm:1.2.1" + dependencies: + "@types/node": "*" + checksum: abfc6d779e1674c268258296e9c03b307cb80383c5d598c0cda1ad75dc901b8d4104b5818990086b08bac199dbe7374706913a29c58cf614a741847f912854f1 + languageName: node + linkType: hard + +"@types/xml2js@npm:*, @types/xml2js@npm:^0.4.11, @types/xml2js@npm:^0.4.7": version: 0.4.11 resolution: "@types/xml2js@npm:0.4.11" dependencies: @@ -18816,9 +18913,9 @@ __metadata: languageName: node linkType: hard -"@uiw/codemirror-extensions-basic-setup@npm:4.21.9": - version: 4.21.9 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.21.9" +"@uiw/codemirror-extensions-basic-setup@npm:4.21.10": + version: 4.21.10 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.21.10" dependencies: "@codemirror/autocomplete": ^6.0.0 "@codemirror/commands": ^6.0.0 @@ -18835,19 +18932,19 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: e7f2a78dd26a919b00247ad85211a09b8242d57585c7689cc44ea9de99e02e8fd8dd281ebf48e61c4126704b031babb5a596307299015bb0aa8e561a0ad2d9b1 + checksum: 3fd2fdf5869937de6256605eaaa6953c26d3e6b5e2b7de896e03a2dacbbe7df92eb26439e321f7ad40deca5241696963e97f06d341910920641ca52e2c6531fd languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.9.3": - version: 4.21.9 - resolution: "@uiw/react-codemirror@npm:4.21.9" + version: 4.21.10 + resolution: "@uiw/react-codemirror@npm:4.21.10" dependencies: "@babel/runtime": ^7.18.6 "@codemirror/commands": ^6.1.0 "@codemirror/state": ^6.1.1 "@codemirror/theme-one-dark": ^6.0.0 - "@uiw/codemirror-extensions-basic-setup": 4.21.9 + "@uiw/codemirror-extensions-basic-setup": 4.21.10 codemirror: ^6.0.0 peerDependencies: "@babel/runtime": ">=7.11.0" @@ -18857,7 +18954,7 @@ __metadata: codemirror: ">=6.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: c09a121e0c37f0dfb720e05f080b5f420eaec9bf1502cad38a100f2cefc731f50e854d682898163ad23aaefefcb505b57f4621b9724eca39d334258e0f25fddb + checksum: e880e57f4784ebd1cf56fc2f98e8ff257b319e0eaa0a6ae25b3d56ece1ae17743e7009b6f08c1f0df8c4c845d0b1cfff81693d24531a37fb4796e76fcb7f3eaa languageName: node linkType: hard @@ -19107,13 +19204,20 @@ __metadata: languageName: node linkType: hard -"@xmldom/xmldom@npm:^0.7.0, @xmldom/xmldom@npm:^0.7.5, @xmldom/xmldom@npm:^0.7.6": +"@xmldom/xmldom@npm:^0.7.5": version: 0.7.9 resolution: "@xmldom/xmldom@npm:0.7.9" checksum: 66e37b7800132f891b885b2eceeeebc53f60b69789da10276f1584256b963d79a28c7ae2071bc53a9cd842d9b03554c761b2701fe8036d6052f26bcd0ae8f2bb languageName: node linkType: hard +"@xmldom/xmldom@npm:^0.8.5, @xmldom/xmldom@npm:^0.8.6, @xmldom/xmldom@npm:^0.8.8": + version: 0.8.10 + resolution: "@xmldom/xmldom@npm:0.8.10" + checksum: 4c136aec31fb3b49aaa53b6fcbfe524d02a1dc0d8e17ee35bd3bf35e9ce1344560481cd1efd086ad1a4821541482528672306d5e37cdbd187f33d7fadd3e2cf0 + languageName: node + linkType: hard + "@xobotyi/scrollbar-width@npm:^1.9.5": version: 1.9.5 resolution: "@xobotyi/scrollbar-width@npm:1.9.5" @@ -19585,17 +19689,17 @@ __metadata: linkType: hard "archiver@npm:^5.0.2, archiver@npm:^5.3.1": - version: 5.3.1 - resolution: "archiver@npm:5.3.1" + version: 5.3.2 + resolution: "archiver@npm:5.3.2" dependencies: archiver-utils: ^2.1.0 - async: ^3.2.3 + async: ^3.2.4 buffer-crc32: ^0.2.1 readable-stream: ^3.6.0 - readdir-glob: ^1.0.0 + readdir-glob: ^1.1.2 tar-stream: ^2.2.0 zip-stream: ^4.1.0 - checksum: 905b198ed04d26c951b80545d45c7f2e0432ef89977a93af8a762501d659886e39dda0fbffb0d517ff3fa450a3d09a29146e4273c2170624e1988f889fb5302c + checksum: 7d3b9b9b51cf54d88c89fbca9b0847c120bfcf9776c7025c52dd0b62f6603dc63dc0f3f1a09582f936f67e3906b46d58954cc762a255be45e8d3e14e3cb0b0b1 languageName: node linkType: hard @@ -19940,10 +20044,10 @@ __metadata: languageName: node linkType: hard -"async@npm:^3.2.0, async@npm:^3.2.3": - version: 3.2.3 - resolution: "async@npm:3.2.3" - checksum: c4bee57ab2249af3dc83ca3ef9acfa8e822c0d5e5aa41bae3eaf7f673648343cd64ecd7d26091ffd357f3f044428b17b5f00098494b6cf8b6b3e9681f0636ca1 +"async@npm:^3.2.0, async@npm:^3.2.3, async@npm:^3.2.4": + version: 3.2.4 + resolution: "async@npm:3.2.4" + checksum: 43d07459a4e1d09b84a20772414aa684ff4de085cbcaec6eea3c7a8f8150e8c62aa6cd4e699fe8ee93c3a5b324e777d34642531875a0817a35697522c1b02e89 languageName: node linkType: hard @@ -20461,13 +20565,13 @@ __metadata: linkType: hard "better-sqlite3@npm:^8.0.0": - version: 8.5.1 - resolution: "better-sqlite3@npm:8.5.1" + version: 8.5.2 + resolution: "better-sqlite3@npm:8.5.2" dependencies: bindings: ^1.5.0 node-gyp: latest prebuild-install: ^7.1.0 - checksum: ba06e7ae5d150f8d9dc77daff6299689476337b820fd82dae5bd844d39009c605e595758145b453362dc68e37041c285ae4cc73d63028b9e4a085b5ad2d3a2c4 + checksum: c9b3bdc1e2b8458d42769aa370d602206696d3dd5486b17f773dc4c39070effedb6c4bc0a3d3540d07db33965ec5a5a97567054e31b80d54ed81e6b0f01e7c6d languageName: node linkType: hard @@ -21210,16 +21314,6 @@ __metadata: languageName: node linkType: hard -"chalk@npm:4.1.1": - version: 4.1.1 - resolution: "chalk@npm:4.1.1" - dependencies: - ansi-styles: ^4.1.0 - supports-color: ^7.1.0 - checksum: 036e973e665ba1a32c975e291d5f3d549bceeb7b1b983320d4598fb75d70fe20c5db5d62971ec0fe76cdbce83985a00ee42372416abfc3a5584465005a7855ed - languageName: node - linkType: hard - "chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.0.2, chalk@npm:^4.1.0, chalk@npm:^4.1.1, chalk@npm:^4.1.2": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -21660,13 +21754,20 @@ __metadata: languageName: node linkType: hard -"clsx@npm:^1.0.2, clsx@npm:^1.0.4, clsx@npm:^1.1.1, clsx@npm:^1.2.1": +"clsx@npm:^1.0.2, clsx@npm:^1.0.4, clsx@npm:^1.1.1": version: 1.2.1 resolution: "clsx@npm:1.2.1" checksum: 30befca8019b2eb7dbad38cff6266cf543091dae2825c856a62a8ccf2c3ab9c2907c4d12b288b73101196767f66812365400a227581484a05f968b0307cfaf12 languageName: node linkType: hard +"clsx@npm:^2.0.0": + version: 2.0.0 + resolution: "clsx@npm:2.0.0" + checksum: a2cfb2351b254611acf92faa0daf15220f4cd648bdf96ce369d729813b85336993871a4bf6978ddea2b81b5a130478339c20d9d0b5c6fc287e5147f0c059276e + languageName: node + linkType: hard + "cluster-key-slot@npm:^1.1.0": version: 1.1.0 resolution: "cluster-key-slot@npm:1.1.0" @@ -22158,8 +22259,8 @@ __metadata: linkType: hard "concurrently@npm:^8.0.0": - version: 8.2.0 - resolution: "concurrently@npm:8.2.0" + version: 8.2.1 + resolution: "concurrently@npm:8.2.1" dependencies: chalk: ^4.1.2 date-fns: ^2.30.0 @@ -22173,7 +22274,7 @@ __metadata: bin: conc: dist/bin/concurrently.js concurrently: dist/bin/concurrently.js - checksum: eafe6a4d9b7fda87f55ea285cfc6acd937a5286ceec8991ab48e6cc27c45fce6a5c6f45e18d7555defa15dc7d7e8941bc5a9d1ceaf182e31441d420e00333434 + checksum: 216cb16d5b301cbd9c657b19430836d1686fe8fa9b9ef35ef7ac601e1a5cf6535166a3e57de446696dbd5e7e3f45d78fc70f33c5fd4bb565342cd5e752c5b069 languageName: node linkType: hard @@ -24929,8 +25030,8 @@ __metadata: linkType: hard "eslint-plugin-import@npm:^2.25.4": - version: 2.28.0 - resolution: "eslint-plugin-import@npm:2.28.0" + version: 2.28.1 + resolution: "eslint-plugin-import@npm:2.28.1" dependencies: array-includes: ^3.1.6 array.prototype.findlastindex: ^1.2.2 @@ -24941,18 +25042,17 @@ __metadata: eslint-import-resolver-node: ^0.3.7 eslint-module-utils: ^2.8.0 has: ^1.0.3 - is-core-module: ^2.12.1 + is-core-module: ^2.13.0 is-glob: ^4.0.3 minimatch: ^3.1.2 object.fromentries: ^2.0.6 object.groupby: ^1.0.0 object.values: ^1.1.6 - resolve: ^1.22.3 semver: ^6.3.1 tsconfig-paths: ^3.14.2 peerDependencies: eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: f9eba311b93ca1bb89311856b1f7285bd79e0181d7eb70fe115053ff77e2235fea749b30f538b78927dc65769340b5be61f4c9581d1c82bcdcccb2061f440ad1 + checksum: e8ae6dd8f06d8adf685f9c1cfd46ac9e053e344a05c4090767e83b63a85c8421ada389807a39e73c643b9bff156715c122e89778169110ed68d6428e12607edf languageName: node linkType: hard @@ -25796,8 +25896,8 @@ __metadata: linkType: hard "express-openapi-validator@npm:^5.0.4": - version: 5.0.4 - resolution: "express-openapi-validator@npm:5.0.4" + version: 5.0.5 + resolution: "express-openapi-validator@npm:5.0.5" dependencies: "@apidevtools/json-schema-ref-parser": ^9.1.2 "@types/multer": ^1.4.7 @@ -25805,6 +25905,7 @@ __metadata: ajv-draft-04: ^1.0.0 ajv-formats: ^2.1.1 content-type: ^1.0.5 + json-schema-traverse: ^1.0.0 lodash.clonedeep: ^4.5.0 lodash.get: ^4.4.2 lodash.uniq: ^4.5.0 @@ -25813,7 +25914,7 @@ __metadata: multer: ^1.4.5-lts.1 ono: ^7.1.3 path-to-regexp: ^6.2.0 - checksum: 5f41d632324fd4217ca74353cb9c9bf50ada6f6ecd5396e9a059f6ce05e66c4698706bb14b7a4ea5a88a4190b82327e37d899dd9a22301dfd6717e24930c81b6 + checksum: e3a4a1fe63f72f9ff8d74019b8b4ea9f088b8295ae860d5a7b64abd1f8273e445ab5e14a972ceb78bf95cee6382580db85272ec852702217ece6e3c954d3f13b languageName: node linkType: hard @@ -28735,7 +28836,7 @@ __metadata: languageName: node linkType: hard -"is-core-module@npm:^2.1.0, is-core-module@npm:^2.11.0, is-core-module@npm:^2.12.1, is-core-module@npm:^2.13.0, is-core-module@npm:^2.9.0": +"is-core-module@npm:^2.1.0, is-core-module@npm:^2.11.0, is-core-module@npm:^2.13.0, is-core-module@npm:^2.9.0": version: 2.13.0 resolution: "is-core-module@npm:2.13.0" dependencies: @@ -33273,15 +33374,15 @@ __metadata: linkType: hard "msw@npm:^1.0.0, msw@npm:^1.0.1, msw@npm:^1.2.1, msw@npm:^1.2.3": - version: 1.2.3 - resolution: "msw@npm:1.2.3" + version: 1.2.4 + resolution: "msw@npm:1.2.4" dependencies: "@mswjs/cookies": ^0.2.2 "@mswjs/interceptors": ^0.17.5 "@open-draft/until": ^1.0.3 "@types/cookie": ^0.4.1 "@types/js-levenshtein": ^1.1.1 - chalk: 4.1.1 + chalk: ^4.1.1 chokidar: ^3.4.2 cookie: ^0.4.2 graphql: ^15.0.0 || ^16.0.0 @@ -33302,7 +33403,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 832a6fc3973726a97e03ce2690c3b6e1774b5156d064a478657a1b33f9933e4834af833cc8a859e1b717fa9b71f1271b3e66a1ec6eed7f76bfe79406e9ec3986 + checksum: ff32efd2e89b6e4e0b646362907658c2b37756a92d46a294171e0ef5c4488217f87381b7740a6c9e5d7ed9c490ce33474f56f5dba8524340233cd89f1de0b0e1 languageName: node linkType: hard @@ -34936,21 +35037,6 @@ __metadata: languageName: node linkType: hard -"passport-saml@npm:^3.1.2": - version: 3.2.4 - resolution: "passport-saml@npm:3.2.4" - dependencies: - "@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: 8e885af4d44c2d862b2ea0d051ab2a36bc6f9a70e62f90daf7ce4eefd126ac2ab4d5fc070693eba05f5e1be248af23fa018611bbfa7fad31708371f387f5dd77 - languageName: node - linkType: hard - "passport-strategy@npm:1.x.x, passport-strategy@npm:^1.0.0": version: 1.0.0 resolution: "passport-strategy@npm:1.0.0" @@ -37500,12 +37586,12 @@ __metadata: languageName: node linkType: hard -"readdir-glob@npm:^1.0.0": - version: 1.1.1 - resolution: "readdir-glob@npm:1.1.1" +"readdir-glob@npm:^1.1.2": + version: 1.1.3 + resolution: "readdir-glob@npm:1.1.3" dependencies: - minimatch: ^3.0.4 - checksum: 8dc4ff606aa9ac8f6ac628dfad918aed6514c8b427922928f2ef380a1be106d5b6f1d106af34607955ad504f89f39d83a9b42c5316ed8b96b5f75391e33a6afc + minimatch: ^5.1.0 + checksum: 1dc0f7440ff5d9378b593abe9d42f34ebaf387516615e98ab410cf3a68f840abbf9ff1032d15e0a0dbffa78f9e2c46d4fafdbaac1ca435af2efe3264e3f21874 languageName: node linkType: hard @@ -37744,13 +37830,6 @@ __metadata: languageName: node linkType: hard -"regenerator-runtime@npm:^0.13.11": - version: 0.13.11 - resolution: "regenerator-runtime@npm:0.13.11" - checksum: 27481628d22a1c4e3ff551096a683b424242a216fee44685467307f14d58020af1e19660bf2e26064de946bad7eff28950eae9f8209d55723e2d9351e632bbb4 - languageName: node - linkType: hard - "regenerator-runtime@npm:^0.14.0": version: 0.14.0 resolution: "regenerator-runtime@npm:0.14.0" @@ -38075,7 +38154,7 @@ __metadata: languageName: node linkType: hard -"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:^1.22.3, resolve@npm:~1.22.1": +"resolve@npm:^1.1.6, resolve@npm:^1.10.0, resolve@npm:^1.14.2, resolve@npm:^1.19.0, resolve@npm:^1.20.0, resolve@npm:^1.22.1, resolve@npm:~1.22.1": version: 1.22.4 resolution: "resolve@npm:1.22.4" dependencies: @@ -38111,7 +38190,7 @@ __metadata: languageName: node linkType: hard -"resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin, resolve@patch:resolve@^1.22.3#~builtin, resolve@patch:resolve@~1.22.1#~builtin": +"resolve@patch:resolve@^1.1.6#~builtin, resolve@patch:resolve@^1.10.0#~builtin, resolve@patch:resolve@^1.14.2#~builtin, resolve@patch:resolve@^1.19.0#~builtin, resolve@patch:resolve@^1.20.0#~builtin, resolve@patch:resolve@^1.22.1#~builtin, resolve@patch:resolve@~1.22.1#~builtin": version: 1.22.4 resolution: "resolve@patch:resolve@npm%3A1.22.4#~builtin::version=1.22.4&hash=07638b" dependencies: @@ -40233,13 +40312,20 @@ __metadata: languageName: node linkType: hard -"stylis@npm:4.1.3, stylis@npm:^4.0.6": +"stylis@npm:4.1.3": version: 4.1.3 resolution: "stylis@npm:4.1.3" checksum: d04dbffcb9bf2c5ca8d8dc09534203c75df3bf711d33973ea22038a99cc475412a350b661ebd99cbc01daa50d7eedcf0d130d121800eb7318759a197023442a6 languageName: node linkType: hard +"stylis@npm:4.2.0, stylis@npm:^4.0.6": + version: 4.2.0 + resolution: "stylis@npm:4.2.0" + checksum: 0eb6cc1b866dc17a6037d0a82ac7fa877eba6a757443e79e7c4f35bacedbf6421fadcab4363b39667b43355cbaaa570a3cde850f776498e5450f32ed2f9b7584 + languageName: node + linkType: hard + "subscriptions-transport-ws@npm:^0.11.0": version: 0.11.0 resolution: "subscriptions-transport-ws@npm:0.11.0" @@ -43039,24 +43125,24 @@ __metadata: languageName: node linkType: hard -"xml-crypto@npm:^2.1.3": - version: 2.1.3 - resolution: "xml-crypto@npm:2.1.3" +"xml-crypto@npm:^3.0.1": + version: 3.2.0 + resolution: "xml-crypto@npm:3.2.0" dependencies: - "@xmldom/xmldom": ^0.7.0 + "@xmldom/xmldom": ^0.8.8 xpath: 0.0.32 - checksum: 04273568b4ed3d1422b7e93ae7c5e8e14180e9d377038a5767ef6af4d1da2ec766f3a476f4cf0140ecedb93036dac63e563f60d9bb249c5a344d77c93c07ff87 + checksum: 6c4974a7518307ea006dcfc1405f61c6738b45574b4d9d1e62f53b602bfcf894d34017f99d618f26f67c40a5e6d78e6228116ded2768b2ca5b2df5c8bf7774b7 languageName: node linkType: hard -"xml-encryption@npm:^2.0.0": - version: 2.0.0 - resolution: "xml-encryption@npm:2.0.0" +"xml-encryption@npm:^3.0.2": + version: 3.0.2 + resolution: "xml-encryption@npm:3.0.2" dependencies: - "@xmldom/xmldom": ^0.7.0 + "@xmldom/xmldom": ^0.8.5 escape-html: ^1.0.3 xpath: 0.0.32 - checksum: a454445704c5e3aa3f992128c413c02f3c00c346cb0d63b01beae4b6a341cfc0a52a0219ccec47dcce250e336ba7b09d95909913b1f199ca43604961a00a1995 + checksum: aac1b987d5de5becfc747c88c3a656c00799a153ab541078b875a69e1ac1f1c2f29bf85f22eab6a78382dc2919f79401a916cc392aba7994475919e0695893eb languageName: node linkType: hard @@ -43074,7 +43160,7 @@ __metadata: languageName: node linkType: hard -"xml2js@npm:^0.4.11, xml2js@npm:^0.4.19, xml2js@npm:^0.4.23": +"xml2js@npm:^0.4.11, xml2js@npm:^0.4.19": version: 0.4.23 resolution: "xml2js@npm:0.4.23" dependencies: @@ -43084,6 +43170,16 @@ __metadata: languageName: node linkType: hard +"xml2js@npm:^0.5.0": + version: 0.5.0 + resolution: "xml2js@npm:0.5.0" + dependencies: + sax: ">=0.6.0" + xmlbuilder: ~11.0.0 + checksum: 1aa71d62e5bc2d89138e3929b9ea46459157727759cbc62ef99484b778641c0cd21fb637696c052d901a22f82d092a3e740a16b4ce218e81ac59b933535124ea + languageName: node + linkType: hard + "xml2js@npm:^0.6.0": version: 0.6.2 resolution: "xml2js@npm:0.6.2" @@ -43129,6 +43225,13 @@ __metadata: languageName: node linkType: hard +"xpath@npm:0.0.27": + version: 0.0.27 + resolution: "xpath@npm:0.0.27" + checksum: 51f45d211a9a552a8f6a12a474061e89bafb07e0aecd4bad18a557411feb975919c158e1a66e4ea0542198c6ed442481d9f709c625cca57b97aaedeaeded902e + languageName: node + linkType: hard + "xpath@npm:0.0.32": version: 0.0.32 resolution: "xpath@npm:0.0.32"