diff --git a/.changeset/brave-donuts-sink.md b/.changeset/brave-donuts-sink.md new file mode 100644 index 0000000000..47827345a5 --- /dev/null +++ b/.changeset/brave-donuts-sink.md @@ -0,0 +1,5 @@ +--- +'@backstage/test-utils': patch +--- + +Added support for interpolating JSX elements with the `MockTranslationApi`. diff --git a/.changeset/brave-pandas-beam.md b/.changeset/brave-pandas-beam.md new file mode 100644 index 0000000000..8dd525d749 --- /dev/null +++ b/.changeset/brave-pandas-beam.md @@ -0,0 +1,17 @@ +--- +'@backstage/core-app-api': minor +--- + +Support custom `AuthConnector` for `OAuth2`. + +A user can pass their own `AuthConnector` implementation in `OAuth2` constructor. +In which case the session manager will use that instead of the `DefaultAuthConnector` to interact with the +authentication provider. + +A custom `AuthConnector` may call the authentication provider from the front-end, store and retrieve tokens +in the session storage, for example, and otherwise send custom requests to the authentication provider and +handle its responses. + +Note, that if the custom `AuthConnector` transforms scopes returned from the authentication provider, +the transformation must be the same as `OAuth2CreateOptions#scopeTransform` passed to `OAuth2` constructor. +See creating `DefaultAuthConnector` in `OAuth2#create(...)` for an example. diff --git a/.changeset/busy-badgers-hang.md b/.changeset/busy-badgers-hang.md new file mode 100644 index 0000000000..fc4b0e907e --- /dev/null +++ b/.changeset/busy-badgers-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Removed `lerna-debug.log*` pattern from `.gitignore` as Lerna was removed from the package in version `@backstage/create-app@0.5.19`. diff --git a/.changeset/chatty-months-grow.md b/.changeset/chatty-months-grow.md new file mode 100644 index 0000000000..1495e7732e --- /dev/null +++ b/.changeset/chatty-months-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Use correct colour token for TextField clear button icon, prevent layout shift whenever it is hidden or shown and properly size focus area around it. Also stop leading icon shrinking when used together with clear button. diff --git a/.changeset/cool-bikes-push.md b/.changeset/cool-bikes-push.md new file mode 100644 index 0000000000..00825c1ad0 --- /dev/null +++ b/.changeset/cool-bikes-push.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': patch +--- + +Updated `I18nextTranslationApi` to support interpolation of JSX elements. diff --git a/.changeset/cool-colts-float.md b/.changeset/cool-colts-float.md new file mode 100644 index 0000000000..c001f81ade --- /dev/null +++ b/.changeset/cool-colts-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/repo-tools': patch +--- + +Support passing additional properties to OpenAPI server generator diff --git a/.changeset/crazy-chefs-sin.md b/.changeset/crazy-chefs-sin.md new file mode 100644 index 0000000000..8d33add929 --- /dev/null +++ b/.changeset/crazy-chefs-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': patch +--- + +Fix slack notification processor to handle a notification with an empty description diff --git a/.changeset/deep-ties-move.md b/.changeset/deep-ties-move.md new file mode 100644 index 0000000000..57f6c7772c --- /dev/null +++ b/.changeset/deep-ties-move.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Improves error reporting for missing metadata.name in LDAP catalog provider. diff --git a/.changeset/funny-papayas-tell.md b/.changeset/funny-papayas-tell.md new file mode 100644 index 0000000000..98beaf89e0 --- /dev/null +++ b/.changeset/funny-papayas-tell.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': minor +--- + +Show the pagination text for the offset-paginated catalog table, and remove the pagination bar from the top of the `CatalogTable` when pagination is enabled. diff --git a/.changeset/khaki-grapes-sink.md b/.changeset/khaki-grapes-sink.md new file mode 100644 index 0000000000..d96521e029 --- /dev/null +++ b/.changeset/khaki-grapes-sink.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Added new icon and onClear props to the TextField to make it easier to accessorize inputs. diff --git a/.changeset/mean-parents-build.md b/.changeset/mean-parents-build.md new file mode 100644 index 0000000000..d3bba29019 --- /dev/null +++ b/.changeset/mean-parents-build.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Pin version of @base-ui-components/react. diff --git a/.changeset/neat-glasses-occur.md b/.changeset/neat-glasses-occur.md new file mode 100644 index 0000000000..5fea5b1886 --- /dev/null +++ b/.changeset/neat-glasses-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': patch +--- + +Add i18n support for `catalog-import` plugin. diff --git a/.changeset/neat-glasses-occured.md b/.changeset/neat-glasses-occured.md new file mode 100644 index 0000000000..289e03e243 --- /dev/null +++ b/.changeset/neat-glasses-occured.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': minor +--- + +**BREAKING**: `generateStepper` and `defaultGenerateStepper` now require a translation argument to be passed through for supporting translations. diff --git a/.changeset/open-lands-shop.md b/.changeset/open-lands-shop.md new file mode 100644 index 0000000000..5205e83cbe --- /dev/null +++ b/.changeset/open-lands-shop.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Update keyboard focus on when clicking hash links. This fixes the issue where the "skip to content" link rendered by Material MkDocs isn't focused when used. diff --git a/.changeset/pre.json b/.changeset/pre.json index e980d2a115..51d53067bd 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -203,11 +203,13 @@ "changesets": [ "angry-sites-fold", "beige-kiwis-flow", + "brave-donuts-sink", "brave-eggs-mate", "brave-toes-switch", "breezy-hotels-deny", "bright-moles-sort", "bumpy-showers-design", + "busy-badgers-hang", "calm-toys-occur", "chatty-showers-cheat", "chilly-trams-cheer", @@ -215,12 +217,16 @@ "chubby-needles-vanish", "cold-humans-check", "common-goats-raise", + "cool-bikes-push", "cool-cities-grab", + "cool-colts-float", "cool-knives-design", + "crazy-chefs-sin", "create-app-1745325336", "create-app-1745936753", "cruel-lights-sip", "cyan-pots-appear", + "deep-ties-move", "dirty-grapes-vanish", "dry-carpets-hope", "dull-doodles-trade", @@ -239,15 +245,20 @@ "heavy-onions-swim", "huge-olives-do", "icy-mugs-glow", + "khaki-grapes-sink", "large-experts-sort", "large-lemons-clap", "lazy-tires-show", "lovely-cats-take", + "mean-parents-build", "mighty-carrots-decide", + "neat-glasses-occur", + "neat-glasses-occured", "new-hands-scream", "nice-vans-vanish", "old-crews-serve", "open-ghosts-fix", + "open-lands-shop", "pretty-corners-speak", "pretty-seas-hug", "public-socks-agree", @@ -267,15 +278,23 @@ "silent-clubs-roll", "slick-brooms-start", "slimy-peas-post", + "slow-drinks-enjoy", "small-eggs-develop", "spicy-steaks-swim", "spotty-doors-design", "stale-symbols-joke", + "tame-areas-behave", + "ten-spies-explode", "ten-tables-build", + "thick-hotels-rhyme", "true-breads-rhyme", "twenty-kiwis-punch", + "warm-cases-bathe", "warm-llamas-return", + "wicked-dingos-stand", + "wise-cobras-sink", "wise-pillows-smile", + "yellow-beans-eat", "yellow-cows-tickle" ] } diff --git a/.changeset/slow-drinks-enjoy.md b/.changeset/slow-drinks-enjoy.md new file mode 100644 index 0000000000..5a171b8fd1 --- /dev/null +++ b/.changeset/slow-drinks-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration-aws-node': patch +--- + +Fixed bug in DefaultAwsCredentialsManager where aws.mainAccount.region has no effect on the STS region used for account ID lookup during credential provider lookup when falling back to the main account, and it does not default to us-east-1 diff --git a/.changeset/soft-ravens-build.md b/.changeset/soft-ravens-build.md new file mode 100644 index 0000000000..dfa14f9b0c --- /dev/null +++ b/.changeset/soft-ravens-build.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': patch +--- + +Fix dataloader caching, and use the proper catalog service ref diff --git a/.changeset/spicy-camels-bet.md b/.changeset/spicy-camels-bet.md new file mode 100644 index 0000000000..0289980ff9 --- /dev/null +++ b/.changeset/spicy-camels-bet.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Internal refactor to move things closer to home diff --git a/.changeset/sweet-papayas-slide.md b/.changeset/sweet-papayas-slide.md new file mode 100644 index 0000000000..9fbc067ca7 --- /dev/null +++ b/.changeset/sweet-papayas-slide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-react': patch +--- + +Fix memoization of `filterValue` in `SearchFilter.Autocomplete` to prevent unintended resets diff --git a/.changeset/tame-areas-behave.md b/.changeset/tame-areas-behave.md new file mode 100644 index 0000000000..6d5c776d8b --- /dev/null +++ b/.changeset/tame-areas-behave.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-notifications': patch +--- + +Change notification send scaffolder action to use native zod schemas diff --git a/.changeset/ten-spies-explode.md b/.changeset/ten-spies-explode.md new file mode 100644 index 0000000000..53a37fbd74 --- /dev/null +++ b/.changeset/ten-spies-explode.md @@ -0,0 +1,7 @@ +--- +'@backstage/integration': minor +--- + +Implement Edit URL feature for Gerrit 3.9+. + +It's possible to disable the edit url by adding the `disableEditUrl: true` config in the Gerrit integration. diff --git a/.changeset/true-trains-tie.md b/.changeset/true-trains-tie.md new file mode 100644 index 0000000000..678e6a85e9 --- /dev/null +++ b/.changeset/true-trains-tie.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-notifications-backend-module-slack': patch +--- + +Notifications which mention user entity refs are now replaced with Slack compatible mentions. + +Example: `Welcome <@user:default/billy>!` -> `Welcome <@U123456890>!` diff --git a/.changeset/warm-cases-bathe.md b/.changeset/warm-cases-bathe.md new file mode 100644 index 0000000000..21e9ceb098 --- /dev/null +++ b/.changeset/warm-cases-bathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-defaults': patch +--- + +The `GithubUrlReader` will now use the token from `options` when fetching repo details diff --git a/.changeset/wicked-clubs-dream.md b/.changeset/wicked-clubs-dream.md new file mode 100644 index 0000000000..c866ac6312 --- /dev/null +++ b/.changeset/wicked-clubs-dream.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-search-react': patch +'@backstage/plugin-search': patch +--- + +search plugin support i18n diff --git a/.changeset/wicked-dingos-stand.md b/.changeset/wicked-dingos-stand.md new file mode 100644 index 0000000000..92d84825d1 --- /dev/null +++ b/.changeset/wicked-dingos-stand.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': patch +--- + +The `TranslationApi` now supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. diff --git a/.changeset/wise-cobras-sink.md b/.changeset/wise-cobras-sink.md new file mode 100644 index 0000000000..7a6a73c726 --- /dev/null +++ b/.changeset/wise-cobras-sink.md @@ -0,0 +1,5 @@ +--- +'@backstage/canon': patch +--- + +Use the Field component from Base UI within the TextField. diff --git a/.changeset/yellow-beans-eat.md b/.changeset/yellow-beans-eat.md new file mode 100644 index 0000000000..2380b19a6f --- /dev/null +++ b/.changeset/yellow-beans-eat.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Fix for missing `routeRef` when using `core-plugin-api` in a dialog context diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 4c51c8f111..41f409a651 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -7,28 +7,50 @@ * @backstage/maintainers @backstage/reviewers yarn.lock @backstage/maintainers @backstage-service */yarn.lock @backstage/maintainers @backstage-service +/.changeset @backstage/operations-maintainers /.changeset/*.md +/.github @backstage/operations-maintainers /beps/0001-notifications-system @backstage/maintainers @backstage/notifications-maintainers /docs @backstage/maintainers @backstage/documentation-maintainers /docs/assets/search @backstage/search-maintainers -/docs/features/search @backstage/search-maintainers +/docs/auth @backstage/auth-maintainers +/docs/backend-system @backstage/framework-maintainers +/docs/deployment @backstage/tooling-maintainers /docs/dls @backstage/design-system-maintainers +/docs/features/search @backstage/search-maintainers /docs/features/techdocs @backstage/techdocs-maintainers +/docs/permissions @backstage/permission-maintainers /docs/plugins/integrating-search-into-plugins.md @backstage/search-maintainers +/docs/releases @backstage/operations-maintainers +/docs/tooling @backstage/tooling-maintainers /microsite @backstage/documentation-maintainers /microsite/data/plugins @backstage/maintainers -/packages/cli/src/commands/onboard @backstage/sharks +/packages @backstage/framework-maintainers /packages/backend-openapi-utils @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers /packages/canon @backstage/design-system-maintainers +/packages/catalog-client @backstage/catalog-maintainers +/packages/catalog-model @backstage/catalog-maintainers +/packages/cli @backstage/tooling-maintainers +/packages/cli-* @backstage/tooling-maintainers +/packages/cli/src/commands/onboard @backstage/sharks +/packages/e2e-test @backstage/operations-maintainers +/packages/eslint-plugin @backstage/tooling-maintainers +/packages/release-manifests @backstage/operations-maintainers +/packages/repo-tools @backstage/tooling-maintainers /packages/techdocs-cli @backstage/techdocs-maintainers /packages/techdocs-cli-embedded-app @backstage/techdocs-maintainers +/packages/yarn-plugin @backstage/tooling-maintainers +/plugins/app @backstage/framework-maintainers +/plugins/app-* @backstage/framework-maintainers +/plugins/auth @backstage/auth-maintainers +/plugins/auth-* @backstage/auth-maintainers /plugins/api-docs @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers /plugins/bitbucket-cloud-common @backstage/maintainers @backstage/reviewers @pjungermann /plugins/catalog @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-* @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-backend-module-aws @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-bitbucket-cloud @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann -/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers +/plugins/catalog-backend-module-backstage-openapi @backstage/maintainers @backstage/reviewers @backstage/openapi-tooling-maintainers /plugins/catalog-backend-module-msgraph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @pjungermann /plugins/catalog-backend-module-puppetdb @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers /plugins/catalog-graph @backstage/maintainers @backstage/reviewers @backstage/catalog-maintainers @backstage/sda-se-reviewers @@ -56,6 +78,8 @@ yarn.lock @backstage/maintainers @backst /plugins/user-settings-backend @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers /plugins/user-settings-common @backstage/maintainers @backstage/reviewers @backstage/sda-se-reviewers +/scripts @backstage/operations-maintainers + /packages/backend-plugin-api/src/services/definitions/AuditorService.ts @backstage/maintainers @backstage/auditor-maintainers /packages/backend-defaults/src/entrypoints/auditor @backstage/maintainers @backstage/auditor-maintainers /packages/backend-defaults/report-auditor.api.md @backstage/maintainers @backstage/auditor-maintainers diff --git a/.github/ISSUE_TEMPLATE/.common.yaml b/.github/ISSUE_TEMPLATE/.common.yaml index c870b21b94..eac60aaa2b 100644 --- a/.github/ISSUE_TEMPLATE/.common.yaml +++ b/.github/ISSUE_TEMPLATE/.common.yaml @@ -42,6 +42,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/ISSUE_TEMPLATE/01_bug.yaml b/.github/ISSUE_TEMPLATE/01_bug.yaml index ccaebe8bff..303a4fcc61 100644 --- a/.github/ISSUE_TEMPLATE/01_bug.yaml +++ b/.github/ISSUE_TEMPLATE/01_bug.yaml @@ -58,6 +58,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/ISSUE_TEMPLATE/02_documentation.yaml b/.github/ISSUE_TEMPLATE/02_documentation.yaml index dc9ede2c53..aaf54fc4a0 100644 --- a/.github/ISSUE_TEMPLATE/02_documentation.yaml +++ b/.github/ISSUE_TEMPLATE/02_documentation.yaml @@ -37,6 +37,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/ISSUE_TEMPLATE/03_suggestion.yaml b/.github/ISSUE_TEMPLATE/03_suggestion.yaml index cafdd7066f..05a98e0956 100644 --- a/.github/ISSUE_TEMPLATE/03_suggestion.yaml +++ b/.github/ISSUE_TEMPLATE/03_suggestion.yaml @@ -50,6 +50,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/ISSUE_TEMPLATE/04_maintenance.yaml b/.github/ISSUE_TEMPLATE/04_maintenance.yaml index bf81efa5f6..da25225e68 100644 --- a/.github/ISSUE_TEMPLATE/04_maintenance.yaml +++ b/.github/ISSUE_TEMPLATE/04_maintenance.yaml @@ -48,6 +48,7 @@ body: - Events System - Home - Kubernetes Plugin + - Management of this repository - Microsite - Notifications - OpenAPI Tooling diff --git a/.github/advanced-issue-labeler.yml b/.github/advanced-issue-labeler.yml index 4148b4236c..dfd6a036c9 100644 --- a/.github/advanced-issue-labeler.yml +++ b/.github/advanced-issue-labeler.yml @@ -9,8 +9,8 @@ policy: keys: ['Auth'] - name: 'area:catalog' keys: ['Catalog'] - - name: 'area:core' - keys: ['Core Framework', 'CLI Tooling'] + - name: 'area:framework' + keys: ['Core Framework'] - name: 'area:design-system' keys: ['Design System'] - name: 'area:documentation' @@ -25,6 +25,8 @@ policy: keys: ['Notifications'] - name: 'area:openapi-tooling' keys: ['OpenAPI Tooling'] + - name: 'area:operations' + keys: ['Management of this repository'] - name: 'area:permission' keys: ['Permission Framework'] - name: 'area:search' @@ -33,6 +35,8 @@ policy: keys: ['Software Templates'] - name: 'area:techdocs' keys: ['TechDocs'] + - name: 'area:tooling' + keys: ['CLI Tooling'] - id: ['integration'] block-list: [] label: diff --git a/.github/vale/config/vocabularies/Backstage/accept.txt b/.github/vale/config/vocabularies/Backstage/accept.txt index c845a6060b..0ca8986140 100644 --- a/.github/vale/config/vocabularies/Backstage/accept.txt +++ b/.github/vale/config/vocabularies/Backstage/accept.txt @@ -89,6 +89,7 @@ CVEs daemonsets Datadog dataflow +dataloader dayjs debounce debounces @@ -252,6 +253,7 @@ makefile Matomo md memcache +memoization memoize memoized microservice diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index de08e8a69c..5045e0c96f 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -211,13 +211,13 @@ jobs: working-directory: microsite - name: download stable reference - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-reference path: docs/reference - name: download stable OpenAPI API docs - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-openapi-docs path: docs @@ -245,13 +245,13 @@ jobs: working-directory: microsite - name: download next reference - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-reference path: docs/reference - name: download next OpenAPI API docs - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-openapi-docs path: docs @@ -260,7 +260,7 @@ jobs: run: yarn build working-directory: microsite - - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: storybook path: microsite/build/storybook diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index e16a481b4f..45eb5de5c8 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: results.sarif diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index 5f0b44d8c6..741471d558 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -58,6 +58,6 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} NODE_OPTIONS: --max-old-space-size=7168 - name: Upload Snyk report - uses: github/codeql-action/upload-sarif@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: snyk.sarif diff --git a/.github/workflows/verify_codeql.yml b/.github/workflows/verify_codeql.yml index 5a1fb93d92..c4b21a9e1f 100644 --- a/.github/workflows/verify_codeql.yml +++ b/.github/workflows/verify_codeql.yml @@ -55,7 +55,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -66,7 +66,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/autobuild@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -80,4 +80,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@28deaeda66b76a05916b6923827895f2b14ab387 # v3.28.16 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index 74ffed4955..c22648bb8c 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -222,13 +222,13 @@ jobs: run: node scripts/verify-lockfile-duplicates.js - name: download stable reference - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-reference path: docs/reference - name: download stable OpenAPI API docs - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: stable-openapi-docs path: docs @@ -257,13 +257,13 @@ jobs: run: mkdocs build --strict - name: download next reference - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-reference path: docs/reference - name: download next OpenAPI API docs - uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: next-openapi-docs path: docs diff --git a/LABELS.md b/LABELS.md index 9ce65d0a71..7f8804f75a 100644 --- a/LABELS.md +++ b/LABELS.md @@ -39,19 +39,21 @@ These labels indicate which part of Backstage an issue or pull request relates t - `area:auditor` - Auditor service and it's use in plugins. - `area:auth` - Authentication and 3rd party authorization. - `area:catalog` - The Catalog plugin and the Software Catalog model and integrations. -- `area:core` - The core Backstage framework. - `area:design-system` - The Canon design system and library. - `area:documentation` - Documentation for adopters, users, and developers. - `area:events` - The Events system and integrations for other plugins. +- `area:framework` - The core Backstage framework. - `area:home` - The Home plugin and the main page of the Backstage site. - `area:kubernetes` - The Kubernetes plugin and integrations for other plugins. - `area:microsite` - The microsite at [backstage.io](https://backstage.io), excluding the documentation. - `area:notifications` - The Notifications plugin and integrations for other plugins. - `area:openapi-tooling` - The OpenAPI tooling it's use in plugins. +- `area:operations` - The management and operations of the main Backstage repository. - `area:permission` - The Permissions system and permission integrations from other plugins. - `area:scaffolder` - The Scaffolder plugin that powers Software Templates. - `area:search` - The Search plugin and search integrations for other plugins. - `area:techdocs` - The TechDocs plugin. +- `area:tooling` - The Backstage CLI and repository tooling. ## Integration Labels - `integration:*` diff --git a/OWNERS.md b/OWNERS.md index c6bbcf71d7..b29889fec1 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -15,6 +15,21 @@ Team: @backstage/maintainers These are the separate project areas of Backstage, each with their own project area maintainers +### Auth + +Team: @backstage/auth-maintainers + +Scope: The Backstage auth plugin and modules, as well as client-side implementations. + +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | + ### Catalog Team: @backstage/catalog-maintainers @@ -41,6 +56,21 @@ Scope: The Backstage design system, component library, as well as surrounding to | Charles de Dreuille | Spotify | | [cdedreuille](https://github.com/cdedreuille) | `cdedreuille` | | Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip` | +### Framework + +Team: @backstage/framework-maintainers + +Scope: The Backstage core framework, including all revisions of the frontend and backend systems, as well as the App plugin. + +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | + ### Helm Charts Team: @backstage/helm-chart-maintainers @@ -76,6 +106,21 @@ Scope: The Kubernetes plugin and the base it provides for other plugins to build | -------------- | ------------ | ---- | ---------------------------------------- | ------------ | | Matthew Clarke | Spotify | | [mclarke47](http://github.com/mclarke47) | mclarke#0725 | +### Operations + +Team: @backstage/operations-maintainers + +Scope: The management and operation of the main Backstage repository and release process, along with the surrounding tooling. + +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | + ### Permission Framework Team: @backstage/permission-maintainers @@ -117,6 +162,21 @@ Scope: The TechDocs plugin and related tooling | John Philip | Spotify | ProTean | [johnphilip283](https://github.com/johnphilip283) | `john_philip#2399` | | Sydney Achinger | Spotify | ProTean | [squid-ney](https://github.com/squid-ney) | - | +### Tooling + +Team: @backstage/tooling-maintainers + +Scope: All published Backstage CLI tools in the main `backstage` repository that do not belong to other areas, including `@backstage/cli` and `@backstage/repo-tools`. + +| Name | Organization | Team | GitHub | Discord | +| -------------------- | ------------ | ------------- | ----------------------------------------------- | --------------- | +| Patrik Oldsberg | Spotify | Cubic Belugas | [Rugvip](https://github.com/Rugvip) | `Rugvip#0019` | +| Ben Lambert | Spotify | Cubic Belugas | [benjdlambert](https://github.com/benjdlambert) | `blam#2159` | +| Camila Loiola | Spotify | Cubic Belugas | [camilaibs](http://github.com/camilaibs) | `camilal#0226` | +| Fredrik Adelöw | Spotify | Cubic Belugas | [freben](https://github.com/freben) | `freben#3926` | +| Mihai Tabara | Spotify | Cubic Belugas | [MihaiTabara](http://github.com/MihaiTabara) | `mihait#3107` | +| Vincenzo Scamporlino | Spotify | Cubic Belugas | [vinzscam](http://github.com/vinzscam) | `vinzscam#6944` | + ## Incubating Project Areas These incubating project areas have shared ownership with @backstage/maintainers. @@ -137,12 +197,13 @@ Team: @backstage/community-plugins-maintainers Scope: Tooling and Community Repo Maintainers for the Backstage [Community Plugins repository](https://github.com/backstage/community-plugins) -| Name | Organization | GitHub | Discord | -| -------------------- | ------------ | ------------------------------------------- | ------------ | -| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | -| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` | -| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` | -| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` | +| Name | Organization | GitHub | Discord | +| -------------------- | ------------- | ------------------------------------------- | ------------ | +| André Wanlin | Spotify | [awanlin](https://github.com/awanlin) | `ahhhndre` | +| Bethany Griggs | Red Hat | [BethGriggs](https://github.com/BethGriggs) | `bethgriggs` | +| Kashish Mittal | Red Hat | [04kash](https://github.com/04kash) | `kashh._.` | +| Vincenzo Scamporlino | Spotify | [vinzscam](https://github.com/vinzscam) | `vinzscam` | +| Peter Macdonald | VodafoneZiggo | [Parsifal-M](https://github.com/Parsifal-M) | `parsifal` | ### Documentation diff --git a/canon-docs/yarn.lock b/canon-docs/yarn.lock index d4049730ff..cbcdc2a55c 100644 --- a/canon-docs/yarn.lock +++ b/canon-docs/yarn.lock @@ -1100,8 +1100,8 @@ __metadata: linkType: hard "@uiw/codemirror-themes@npm:^4.23.7": - version: 4.23.11 - resolution: "@uiw/codemirror-themes@npm:4.23.11" + version: 4.23.12 + resolution: "@uiw/codemirror-themes@npm:4.23.12" dependencies: "@codemirror/language": "npm:^6.0.0" "@codemirror/state": "npm:^6.0.0" @@ -1110,7 +1110,7 @@ __metadata: "@codemirror/language": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 10/cbdb7865410e4dead2a96857d14e7dd50fcaf3af9f013ce932578b7fdd824648aa8f21f7964e3b1d657f79961f14db605f28178fc81ec53ee77e27c1a066ff32 + checksum: 10/472121efadf2b9eb110030f06d657cb9693861e1ca360c81c7b0d7aa4533e6e6791737dfd14bcbe496bcb818d33f4cf6a18caf504ec628807e273441f969ef83 languageName: node linkType: hard diff --git a/docs/auth/auth0/provider--old.md b/docs/auth/auth0/provider--old.md deleted file mode 100644 index a089236aca..0000000000 --- a/docs/auth/auth0/provider--old.md +++ /dev/null @@ -1,75 +0,0 @@ ---- -id: provider--old -title: Auth0 Authentication Provider -sidebar_label: Auth0 -description: Adding Auth0 as an authentication provider in Backstage ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../../backend-system/index.md), being the default since -Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./provider.md) -instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage `core-plugin-api` package comes with an Auth0 authentication -provider that can authenticate users using OAuth. - -## Create an Auth0 Application - -1. Log in to the [Auth0 dashboard](https://manage.auth0.com/dashboard/) -2. Navigate to **Applications** -3. Create an Application - - Name: Backstage (or your custom app name) - - Application type: Single Page Web Application -4. Click on the Settings tab -5. Add under `Application URIs` > `Allowed Callback URLs`: - `http://localhost:7007/api/auth/auth0/handler/frame` -6. Click `Save Changes` - -## Configuration - -The provider configuration can then be added to your `app-config.yaml` under the -root `auth` configuration: - -```yaml -auth: - environment: development - providers: - auth0: - development: - clientId: ${AUTH_AUTH0_CLIENT_ID} - clientSecret: ${AUTH_AUTH0_CLIENT_SECRET} - domain: ${AUTH_AUTH0_DOMAIN_ID} - audience: ${AUTH_AUTH0_AUDIENCE} - connection: ${AUTH_AUTH0_CONNECTION} - connectionScope: ${AUTH_AUTH0_CONNECTION_SCOPE} - session: - secret: ${AUTH_SESSION_SECRET} -``` - -The Auth0 provider is a structure with these configuration keys: - -- `clientId`: The Application client ID, found on the Auth0 Application page -- `clientSecret`: The Application client secret, found on the Auth0 Application - page -- `domain`: The Application domain, found on the Auth0 Application page - -It additionally relies on the following configuration to function: - -- `session.secret`: The session secret is a key used for signing and/or encrypting cookies set by the application to maintain session state. In this case, 'your session secret' should be replaced with a long, complex, and unique string that only your application knows. - -Auth0 requires a session, so you need to give the session a secret key. - -## Optional Configuration - -- `audience`: The intended recipients of the token -- `connection`: Social identity provider name. To check the available social connections, please visit [Auth0 Social Connections](https://marketplace.auth0.com/features/social-connections). -- `connectionScope`: Additional scopes in the interactive token request. It should always be used in combination with the `connection` parameter - -## Adding the provider to the Backstage frontend - -To add the provider to the frontend, add the `auth0AuthApi` reference and -`SignInPage` component as shown in -[Adding the provider to the sign-in page](../index.md#sign-in-configuration). diff --git a/docs/auth/auth0/provider.md b/docs/auth/auth0/provider.md index 44a5bb0e43..90ac36f07d 100644 --- a/docs/auth/auth0/provider.md +++ b/docs/auth/auth0/provider.md @@ -8,7 +8,7 @@ description: Adding Auth0 as an authentication provider in Backstage :::info This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./provider--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/auth0/provider--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: diff --git a/docs/auth/bitbucketServer/provider--old.md b/docs/auth/bitbucketServer/provider--old.md deleted file mode 100644 index fdd8fa35be..0000000000 --- a/docs/auth/bitbucketServer/provider--old.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -id: provider--od -title: Bitbucket Server Authentication Provider -sidebar_label: Bitbucket Server -description: Adding Bitbucket Server OAuth as an authentication provider in Backstage ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../../backend-system/index.md), being the default since -Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./provider.md) -instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage `core-plugin-api` package comes with a Bitbucket Server authentication provider that can authenticate -users using Bitbucket Server. This does **NOT** work with Bitbucket Cloud. - -## Create an Application Link in Bitbucket Server - -To add Bitbucket Server authentication, you must create an incoming application link. Follow the steps described in -the [Bitbucket Server documentation](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html) -to create one. - -## Configuration - -The provider configuration can then be added to your `app-config.yaml` under the root `auth` configuration: - -```yaml -auth: - environment: development - providers: - bitbucketServer: - development: - host: bitbucket.org - clientId: ${AUTH_BITBUCKET_SERVER_CLIENT_ID} - clientSecret: ${AUTH_BITBUCKET_SERVER_CLIENT_SECRET} -``` - -The Bitbucket Server provider is a structure with two configuration keys: - -- `clientId`: The client ID that was generated by Bitbucket, e.g. `b0f868455c15dcdff5c5fb5d173ae684`. -- `clientSecret`: The client secret tied to the generated client ID. - -## Adding the provider to the Backstage frontend - -To add the provider to the frontend, add the `bitbucketServerAuthApi` reference and `SignInPage` component as shown -in [Adding the provider to the sign-in page](../index.md#sign-in-configuration). - -## Using Bitbucket Server for sign-in - -In order to use the Bitbucket Server provider for sign-in, you must configure it with a `signIn.resolver`. See -the [Sign-In Resolver documentation](../identity-resolver.md) for more details on how this is done. Note that for the -Bitbucket Server provider, you'll want to use `bitbucketServer` as the provider ID, -and `providers.bitbucketServer.create` for the provider factory. - -The `@backstage/plugin-auth-backend` plugin also comes with a built-in resolver that can be used if desired. -The `emailMatchingUserEntityProfileEmail` identifies users by matching their Bitbucket Server email address to the email -address of `User` entities in the catalog. Note that you must populate your catalog with matching entities or users will -not be able to sign in with this resolver. diff --git a/docs/auth/bitbucketServer/provider.md b/docs/auth/bitbucketServer/provider.md index 0da600d3fd..89b8be3ff6 100644 --- a/docs/auth/bitbucketServer/provider.md +++ b/docs/auth/bitbucketServer/provider.md @@ -8,7 +8,7 @@ description: Adding Bitbucket Server OAuth as an authentication provider in Back :::info This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./provider--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/bitbucketServer/provider--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: diff --git a/docs/auth/identity-resolver--old.md b/docs/auth/identity-resolver--old.md deleted file mode 100644 index ce41fbcf2b..0000000000 --- a/docs/auth/identity-resolver--old.md +++ /dev/null @@ -1,379 +0,0 @@ ---- -id: identity-resolver--old -title: Sign-in Identities and Resolvers (Old Backend System) -description: An introduction to Backstage user identities and sign-in resolvers in the old backend system ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../backend-system/index.md), being the default since -Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./identity-resolver.md) -instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -By default, every Backstage auth provider is configured only for the use-case of -access delegation. This enables Backstage to request resources and actions from -external systems on behalf of the user, for example re-triggering a build in CI. - -If you want to use an auth provider to sign in users, you need to explicitly configure -it have sign-in enabled and also tell it how the external identities should -be mapped to user identities within Backstage. - -## Quick Start - -> See [the auth docs](./index.md) -> for a full list of auth providers and their built-in sign-in resolvers. - -Backstage projects created with `npx @backstage/create-app` come configured with a -sign-in resolver for GitHub guest access. This resolver makes all users share -a single "guest" identity and is only intended as a minimum requirement to quickly -get up and running. You can replace `github` for any of the other providers if you need. - -This resolver should not be used in production, as it uses a single shared identity, -and has no restrictions on who is able to sign-in. Be sure to read through the rest -of this page to understand the Backstage identity system once you need to install -a resolver for your production environment. - -The guest resolver can be useful for testing purposes too, and it looks like this: - -```ts -signIn: { - resolver(_, ctx) { - const userRef = 'user:default/guest' - return ctx.issueToken({ - claims: { - sub: userRef, - ent: [userRef], - }, - }), - }, -}, -``` - -## Backstage User Identity - -A user identity within Backstage is built up from two pieces of information, a -user [entity reference](../features/software-catalog/references.md), and a -set of ownership entity references. -When a user signs in, a Backstage token is generated with these two pieces of information, -which is then used to identify the user within the Backstage ecosystem. - -The user entity reference should uniquely identify the logged in user in Backstage. -It is encouraged that a matching user entity also exists within the Software Catalog, -but it is not required. If the user entity exists in the catalog it can be used to -store additional data about the user. There may even be some plugins that require -this for them to be able to function. - -The ownership references are also entity references, and it is likewise -encouraged that these entities exist within the catalog, but it is not a requirement. -The ownership references are used to determine what the user owns, as a set -of references that the user claims ownership though. For example, a user -Jane (`user:default/jane`) might have the ownership references `user:default/jane`, -`group:default/team-a`, and `group:default/admins`. Given these ownership claims, -any entity that is marked as owned by either of `user:jane`, `team-a`, or `admins` would -be considered owned by Jane. - -The ownership claims often contain the user entity reference itself, but it is not -required. It is also worth noting that the ownership claims can also be used to -resolve other relations similar to ownership, such as a claim for a `maintainer` or -`operator` status. - -The Backstage token that encapsulates the user identity is a JWT. The user entity -reference is stored in the `sub` claim of the payload, while the ownership references -are stored in a custom `ent` claim. Both the user and ownership references should -always be full entity references, as opposed to shorthands like just `jane` or `user:jane`. - -## Sign-in Resolvers - -Signing in a user into Backstage requires a mapping of the user identity from the -third-party auth provider to a Backstage user identity. This mapping can vary quite -a lot between different organizations and auth providers, and because of that there's -no default way to resolve user identities. The auth provider that one wants to use -for sign-in must instead be configured with a sign-in resolver, which is a function -that is responsible for creating this user identity mapping. - -The input to the sign-in resolver function is the result of a successful log in with -the given auth provider, as well as a context object that contains various helpers -for looking up users and issuing tokens. There are also a number of built-in sign-in -resolvers that can be used, which are covered a bit further down. - -Note that while it possible to configure multiple auth providers to be used for sign-in, -you should take care when doing so. It is best to make sure that the different auth -providers either do not have any user overlap, or that any users that are able to log -in with multiple providers always end up with the same Backstage identity. - -### Custom Resolver Example - -Let's look at an example of a custom sign-in resolver for the Google auth provider. -This all typically happens within your `packages/backend/src/plugins/auth.ts` file, -which is responsible for setting up and configuring the auth backend plugin. - -You provide the resolver as part of the options you pass when creating a new auth -provider factory. This means you need to replace the default Google provider with -one that you create. Be sure to also include the existing `defaultAuthProviderFactories` -if you want to keep all of the built-in auth providers installed. - -Now let's look at the example, with the rest of the commentary being made with in -the code comments: - -```ts -// File: packages/backend/src/plugins/auth.ts -import { - createRouter, - providers, - defaultAuthProviderFactories, -} from '@backstage/plugin-auth-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ...env, - providerFactories: { - ...defaultAuthProviderFactories, - google: providers.google.create({ - signIn: { - resolver: async (info, ctx) => { - const { - profile: { email }, - } = info; - // Profiles are not always guaranteed to have an email address. - // You can also find more provider-specific information in `info.result`. - // It typically contains a `fullProfile` object as well as ID and/or access - // tokens that you can use for additional lookups. - if (!email) { - throw new Error('User profile contained no email'); - } - - // You can add your own custom validation logic here. - // Logins can be prevented by throwing an error like the one above. - myEmailValidator(email); - - // This example resolver simply uses the local part of the email as the name. - const [name] = email.split('@'); - - // This helper function handles sign-in by looking up a user in the catalog. - // The lookup can be done either by reference, annotations, or custom filters. - // - // The helper also issues a token for the user, using the standard group - // membership logic to determine the ownership references of the user. - return ctx.signInWithCatalogUser({ - entityRef: { name }, - }); - }, - }, - }), - }, - }); -} -``` - -### Built-in Resolvers - -You don't always have to write your own custom resolver. The auth backend plugin provides -built-in resolvers for many of the common sign-in patterns. You access these via the `resolvers` -property of each of the auth provider integrations. For example, the Google provider has -a built in resolver that works just like the one we defined above: - -```ts -// File: packages/backend/src/plugins/auth.ts -export default async function createPlugin( - // ... - return await createRouter({ - // ... - providerFactories: { - // ... - google: providers.google.create({ - signIn: { - resolver: providers.google.resolvers.emailLocalPartMatchingUserEntityName(), - }, - }); - } - }) -) -``` - -There are also other options, like the this one that looks up a user -by matching the `google.com/email` annotation of user entities in the catalog: - -```ts -providers.google.create({ - signIn: { - resolver: providers.google.resolvers.emailMatchingUserEntityAnnotation(), - }, -}); -``` - -## Custom Ownership Resolution - -If you want to have more control over the membership resolution and token generation -that happens during sign-in you can replace `ctx.signInWithCatalogUser` with a set -of lower-level calls: - -```ts -// File: packages/backend/src/plugins/auth.ts -import { getDefaultOwnershipEntityRefs } from '@backstage/plugin-auth-backend'; - -export default async function createPlugin( - // ... - return await createRouter({ - // ... - providerFactories: { - // ... - google: async ({ profile: { email } }, ctx) => { - if (!email) { - throw new Error('User profile contained no email'); - } - - // This step calls the catalog to look up a user entity. You could for example - // replace it with a call to a different external system. - const { entity } = await ctx.findCatalogUser({ - annotations: { - 'acme.org/email': email, - }, - }); - - // In this step we extract the ownership references from the user entity using - // the standard logic. It uses a reference to the entity itself, as well as the - // target of each `memberOf` relation where the target is of the kind `Group`. - // - // If you replace the catalog lookup with something that does not return - // an entity you will need to replace this step as well. - // - // You might also replace it if you for example want to filter out certain groups. - // - // Note that `getDefaultOwnershipEntityRefs` only includes groups to which the - // user has a direct MEMBER_OF relationship. It's perfectly fine to include - // groups that the user is transitively part of in the claims array, but the - // catalog doesn't currently provide a direct way of accessing this list of - // groups. - const ownershipRefs = getDefaultOwnershipEntityRefs(entity); - - // The last step is to issue the token, where we might provide more options in the future. - return ctx.issueToken({ - claims: { - sub: stringifyEntityRef(entity), - ent: ownershipRefs, - }, - }); - }; - } - }) -) -``` - -## Sign-In without Users in the Catalog - -While populating the catalog with organizational data unlocks more powerful ways -to browse your software ecosystem, it might not always be a viable or prioritized -option. However, even if you do not have user entities populated in your catalog, you -can still sign in users. As there are currently no built-in sign-in resolvers for -this scenario you will need to implement your own. - -Signing in a user that doesn't exist in the catalog is as simple as skipping the -catalog lookup step from the above example. Rather than looking up the user, we -instead immediately issue a token using whatever information is available. One caveat -is that it can be tricky to determine the ownership references, although it can -be achieved for example through a lookup to an external service. You typically -want to at least use the user itself as a lone ownership reference. - -Because we no longer use the catalog as an allow-list of users, it is often important -that you limit what users are allowed to sign in. This could be a simple email domain -check like in the example below, or you might for example look up the GitHub organizations -that the user belongs to using the user access token in the provided result object. - -```ts -// File: packages/backend/src/plugins/auth.ts -import { createRouter, providers } from '@backstage/plugin-auth-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; -import { - stringifyEntityRef, - DEFAULT_NAMESPACE, -} from '@backstage/catalog-model'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ...env, - providerFactories: { - google: providers.google.create({ - signIn: { - resolver: async ({ profile }, ctx) => { - if (!profile.email) { - throw new Error( - 'Login failed, user profile does not contain an email', - ); - } - // Split the email into the local part and the domain. - const [localPart, domain] = profile.email.split('@'); - - // Next we verify the email domain. It is recommended to include this - // kind of check if you don't look up the user in an external service. - if (domain !== 'acme.org') { - throw new Error( - `Login failed, this email ${profile.email} does not belong to the expected domain`, - ); - } - - // By using `stringifyEntityRef` we ensure that the reference is formatted correctly - const userEntity = stringifyEntityRef({ - kind: 'User', - name: localPart, - namespace: DEFAULT_NAMESPACE, - }); - return ctx.issueToken({ - claims: { - sub: userEntity, - ent: [userEntity], - }, - }); - }, - }, - }), - }, - }); -} -``` - -## AuthHandler - -Similar to a custom sign-in resolver, you can also write a custom auth handler -function which is used to verify and convert the auth response into the profile -that will be presented to the user. This is where you can customize things like -display name and profile picture. - -This is also the place where you can do authorization and validation of the user -and throw errors if the user should not be allowed access in Backstage. - -```ts -// File: packages/backend/src/plugins/auth.ts -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ... - providerFactories: { - google: providers.google.create({ - authHandler: async ({ - fullProfile // Type: passport.Profile, - idToken // Type: (Optional) string, - }) => { - // Custom validation code goes here - return { - profile: { - email, - picture, - displayName, - } - }; - } - }) - } - }) -} -``` diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index 6a59805b0b..3d15c9541e 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -7,7 +7,7 @@ description: An introduction to Backstage user identities and sign-in resolvers :::info This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./identity-resolver--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/identity-resolver--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: diff --git a/docs/auth/oidc--old.md b/docs/auth/oidc--old.md deleted file mode 100644 index 904e48c9a3..0000000000 --- a/docs/auth/oidc--old.md +++ /dev/null @@ -1,289 +0,0 @@ ---- -id: oidc--old -title: OIDC provider from scratch -description: This section shows how to use an OIDC provider from scratch, same steps apply for custom providers. ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../backend-system/index.md), being the default since -Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./oidc.md) -instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -This section shows how to use an OIDC provider from scratch, same steps apply for custom -providers. Please note these steps are for using a provider, not how to implement one, -and Backstage recommends creating custom providers specific to the IDP, so we'll use a -`azureOIDC` provider throughout this example, feel free to change any of those refs -to your provider name. - -## Summary - -To add providers not enabled by default like OIDC, we need to follow some steps, we -assume you already have a sign in page to which we'll add the provider so users can -sign in through the provider. In simple steps here's how you enable the provider: - -- Create an API reference to identify the provider. -- Create the API factory that will handle the authentication. -- Add or reuse an auth provider so you can authenticate. -- Add or reuse a resolver to handle the result from the authentication. -- Configure the provider to access your 3rd party auth solution. -- Add the provider to sign in page so users can login with it. - -We'll explain each step more in detail next. - -### The API reference - -An API reference exist for the sake of **Dependency Injection**, check [Utility APIs][4] -for extended explanation. - -In this OIDC example, we'll create the API reference directly in the -`packages/app/src/apis.ts` file, it is not a requirement to put the reference in this -file. Any location will do as long as it's available to be imported to where the API -factory is, as well as easily accessible to the rest of the application so any package -and plugin can inject the API instance when necessary. - -An example of such would be when you use an auth provider from a library installed with -NPM, or any other library repository, you would import the API ref from the library. - -```ts -export const azureOIDCAuthApiRef: ApiRef< - OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'auth.my-custom-provider', -}); -``` - -Please note a few things, the ID can be anything you want as long as it doesn't conflict -with other refs, backstage recommends to use a custom name that references your custom -provider, for example we are using OIDC protocol with Azure, so we could use something -like `auth.azure.oidc` as well. - -Also we're exporting this reference, as well as the `typings`, we need to -be able to import this reference anywhere in the app, and the `typings` will tell typescript -what instance we're getting from DI when injecting the API. In this case we are defining -an API for authentication, so we tell TS that this instance complies with 4 API -interfaces: - -- The OICD API that will handle authentication. -- Profile API for requesting user profile info from the auth provider in question. -- Backstage identity API to handle and associate the user profile with backstage identity. -- Session API, to handle the session the user will have while logged in. - -### The API Factory - -A factory is a function that can take some parameters or dependencies and return an -instance of something, in our case it will be a function that requests some backstage -APIs and use them to create an instance of an OIDC API provider. - -Please note that this function only runs (creates the instance) when somewhere else in -the app you request the DI to give you an instance of the OIDC provider using the API ref -defined above, and the DI will only run this function the first time, from then on any -other DI injection will just receive the same instance created the first time, basically -the instance is cached by the DI library, a singleton. - -Let's add our OIDC API factory to the APIs array in the `packages/app/src/apis.ts` file: - -```ts title="packages/app/src/apis.ts" -/* highlight-add-next-line */ -import { OAuth2 } from '@backstage/core-app-api'; - -export const apis: AnyApiFactory[] = [ - /* highlight-add-start */ - createApiFactory({ - api: azureOIDCAuthApiRef, - deps: { - discoveryApi: discoveryApiRef, - oauthRequestApi: oauthRequestApiRef, - configApi: configApiRef, - }, - factory: ({ discoveryApi, oauthRequestApi, configApi }) => - OAuth2.create({ - configApi, - discoveryApi, - oauthRequestApi, - provider: { - id: 'my-auth-provider', - title: 'My custom auth provider', - icon: () => null, - }, - environment: configApi.getOptionalString('auth.environment'), - defaultScopes: ['openid', 'profile', 'email'], - popupOptions: { - // optional, used to customize login in popup size - size: { - fullscreen: true, - }, - /** - * or specify popup width and height - * size: { - width: 1000, - height: 1000, - } - */ - }, - }), - }), - /* highlight-add-end */ - // .. -]; -``` - -Please note we're importing the `OAuth2` class from `@backstage/core-app-api` effectively -delegating the authentication to it. Also we're using the `my-auth-provider` ID to tell -`OAuth2` to use the auth provider we'll define in the next section, and added the default -scopes to request ID, profile, email and user read permissions. - -## The Auth Provider - -The Auth Provider is responsible for authenticating with the 3rd party service, and give -us back the credentials, here's where you pick which protocol to use, be it Auth0, OAuth2, -OIDC, SAML or any other that your 3rd party IDP provider supports. - -For this example we'll use OIDC, we pass a factory to the `providerFactories` object with -the ID you picked to represent the Auth provider, this ID has to match with the provider's -`id` inside the API factory, the yaml config provider key under `auth.providers`, and the -callback URI provider segment (you'll have to configure your IDP to handle the callback -URI properly). - -```ts -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - providerFactories: { - ...defaultAuthProviderFactories, - /* highlight-add-next-line */ - 'my-auth-provider': providers.oidc.create({}), - }, - // .. -}) -``` - -### The Resolver - -Resolvers exist to map user identity from the 3rd party (in this case an azure IDP -provider) to the backstage user identity, for a detailed explanation check the -[Identity Resolver][1] page, it explains how to write a custom resolver as well as -linking the built in resolvers of backstage. - -The default OIDC provider does not support SignIn, we need to add such support by -adding a resolver for a SignIn request. - -The OIDC provider doesn't provide any build-in resolvers, so we'll need to define our own: - -```ts -import { - DEFAULT_NAMESPACE, - /* highlight-add-next-line */ - stringifyEntityRef, -} from '@backstage/catalog-model'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - providerFactories: { - ...defaultAuthProviderFactories, - 'my-auth-provider': providers.oidc.create({ - /* highlight-add-start */ - signIn: { - resolver(info, ctx) { - const userRef = stringifyEntityRef({ - kind: 'User', - name: info.result.userinfo.sub, - namespace: DEFAULT_NAMESPACE, - }); - return ctx.issueToken({ - claims: { - sub: userRef, // The user's own identity - ent: [userRef], // A list of identities that the user claims ownership through - }, - }); - }, - }, - /* highlight-add-end */ - }), - }, - // .. - }) -``` - -### The configuration - -Since we are using our custom OIDC Auth Provider, we need to add a configuration based -on the provider used, in this case based on OIDC protocol (remember the 3rd party has to -support the protocol). - -In this example we'll configure OIDC with `my-auth-provider`, to do so we need to -[Create app registration][2] in the Azure console, the only difference is that the -`http://localhost:7007/api/auth/microsoft/handler/frame` URL needs to change to -`http://localhost:7007/api/auth/my-auth-provider/handler/frame`. - -Then we need to configure the env variables for the provider, based on the provider's code -in `plugins/auth-backend/src/providers/oidc/provider.ts` we need the following variables -in the `app-config.yaml`: - -```yaml title="app-config.yaml" -auth: - environment: development - ### Providing an auth.session.secret will enable session support in the auth-backend - session: - secret: ${SESSION_SECRET} - providers: - my-auth-provider: - development: - metadataUrl: https://example.com/.well-known/openid-configuration - clientId: ${AUTH_MY_CLIENT_ID} - clientSecret: ${AUTH_MY_CLIENT_SECRET} -``` - -Anything enclosed in `${}` can be replaced directly in the yaml, or provided as -environment variables, the way you obtain all these except `scope` and `prompt` is to -check the App Registration you created: - -- `clientId`: Grab from the Overview page. -- `clientSecret`: Can only be seen when creating the secret, if you lose it you'll need a - new secret. -- `metadataUrl`: In Overview > Endpoints tab, grab OpenID Connect metadata document URL. -- `authorizationUrl` and `tokenUrl`: Open the `metadataUrl` in a browser, that json will - hold these 2 urls somewhere in there. -- `tokenEndpointAuthMethod`: Don't define it, use the default unless you know what it does. -- `tokenSignedResponseAlg`: Don't define it, use the default unless you know what it does. -- `scope`: Only used if we didn't specify `defaultScopes` in the provider's factory, - basically the same thing. -- `prompt`: Recommended to use `auto` so the browser will request login to the IDP if the - user has no active session. - -Note that for the time being, any change in this yaml file requires a restart of the app, -also you need to have the `session.secret` part to use OIDC (some other providers might -need this as well) to support user sessions. - -### The Sign In provider - -The last step is to add the provider to the `SignInPage` so users can sign in with your -new provider, please follow the [Sign In Configuration][3] docs, here's where you import -and use the API reference we defined earlier. - -## Note - -These steps apply to most if not all the providers, including custom providers, the main -difference between different providers will be the contents of the API factory, the code -in the Auth Provider Factory, the resolver, and the different variables each provider -needs in the YAML config or env variables. - -[1]: https://backstage.io/docs/auth/identity-resolver -[2]: https://backstage.io/docs/auth/microsoft/provider#create-an-app-registration-on-azure -[3]: https://backstage.io/docs/auth/#sign-in-configuration -[4]: https://backstage.io/docs/api/utility-apis diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md index 838ac51f1f..250736cec7 100644 --- a/docs/auth/oidc.md +++ b/docs/auth/oidc.md @@ -7,7 +7,7 @@ description: This section shows how to use an OIDC provider from scratch, same s :::info This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./oidc--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/oidc--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: diff --git a/docs/auth/service-to-service-auth--old.md b/docs/auth/service-to-service-auth--old.md deleted file mode 100644 index 8341d69341..0000000000 --- a/docs/auth/service-to-service-auth--old.md +++ /dev/null @@ -1,177 +0,0 @@ ---- -id: service-to-service-auth--old -title: Service to Service Auth -# prettier-ignore -description: This section describes how to use service to service authentication, both internally within Backstage plugins and towards external services. ---- - -:::info -This documentation is written for the old backend which has been replaced by -[the new backend system](../backend-system/index.md), being the default since -Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new -backend system, you may want to read [its own article](./identity-resolver.md) -instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -This article describes the steps needed to introduce _service-to-service auth_ (formerly _backend-to-backend_ auth). -This allows plugin backends to determine whether a given request originates from -a legitimate Backstage plugin (or other external caller), by requiring a special -type of service-to-service token which is signed with a shared secret. - -When enabling this protection on your Backstage backend plugins, for example the -catalog, other callers in the ecosystem such as the search indexer and -scaffolder would need to present a valid token to the catalog to be able to -request its contents. - -## Setup - -In a newly created Backstage app, the backend is setup up to not require any -auth at all. This means that generated service-to-service tokens are empty, and -that incoming requests are not validated. If you want to enable -service-to-service auth, the first step is to switch out the following line in -your backend setup at `packages/backend/src/index.ts`: - -```ts title="packages/backend/src/index.ts" -/* highlight-remove-next-line */ -const tokenManager = ServerTokenManager.noop(); -/* highlight-add-next-line */ -const tokenManager = ServerTokenManager.fromConfig(config, { logger: root }); -``` - -By switching from the no-op `ServiceTokenManager` to one created from config, -you enable service-to-service auth for any plugin that implements it. The local -development setup will generally not be impacted by this, as temporary keys are -generated under the hood. But for the production setup, this means you must now -provide a shared secret that enables your backend plugins to communicate with -each other. - -Backstage service-to-service tokens are currently always signed with a single -secret key. It needs to be shared across all backend plugins and services that -ones wishes to communicate across. The key can be any base64 encoded secret. -The following command can be used to generate such a key in a terminal: - -```bash -node -p 'require("crypto").randomBytes(24).toString("base64")' -``` - -Then place it in the backend configuration, either as a direct value or -injected as an env variable. - -```yaml -# commonly in your app-config.production.yaml -backend: - auth: - keys: - - secret: - # - secret: ${BACKEND_SECRET} - if you want to use an env variable instead -``` - -**NOTE**: For ease of development, we auto-generate a key for you if you haven't -configured a secret in dev mode. You _must set your own secret_ in order for -service-to-service auth to work in production; the `ServiceTokenManager` will -throw an exception in production if it has no keys to work with, which will lead -to the backend failing to start up. - -## Usage in Backend Plugins - -There are a few steps if you want to make use of the service-to-service auth in -your own backend plugin. First you need to add the `TokenManager` dependency to -the `createRouter` options. Typically as `tokenManager: TokenManager`. Along -with this you'll need to ask users to start providing this new dependency in -their backend setup code. - -Once the `TokenManager` is available, you use the `.getToken()` method to generate -a new token for any outgoing requests towards other Backstage backend plugins. -This method should be called for every request that you make; do not store the -token for later use. The `TokenManager` implementations should already cache -tokens as needed. The returned token should then be added as a `Bearer` token -for the upstream request, for example: - -```ts -const { token } = await this.tokenManager.getToken(); - -const response = await fetch(pluginBackendApiUrl, { - method: 'GET', - headers: { - ...headers, - Authorization: `Bearer ${token}`, - }, -}); -``` - -To authenticate an incoming request you use the `.authenticate(token)` method. -At the time of writing this method doesn't return anything, it will simply -throw if the token is invalid. - -```ts -await tokenManager.authenticate(token); // throws if token is invalid -``` - -## Usage in External Callers - -If you have enabled server-to-server auth, you may be interested in generating -tokens in code that is external to Backstage itself. External callers may even -be written in other languages than Node.js. This section explains how to generate -a valid token yourself. - -The token must be a JWT with a `HS256` signature, using the raw base64 decoded -value of the configured key as the secret. It must also have the following payload: - -- `sub`: "backstage-server" (only this value supported currently) -- `exp`: one hour from the time it was generated, in epoch seconds - -> NOTE: The JWT must encode the `alg` header as a protected header, such as with -> [setProtectedHeader](https://github.com/panva/jose/blob/main/docs/classes/jwt_sign.SignJWT.md#setprotectedheader). - -## Granular Access Control - -We plan to build out the service-to-service auth to be much more powerful in the -future, but before that is done there are a few tricks you can use with the -current system to harden your deployments. This section assumes that you have -already split your backend plugins into more than one backend deployment, in -order to scale or isolate them. - -The backend auth configuration has support for providing multiple keys, for -example: - -```yaml -backend: - auth: - keys: - - secret: my-secret-key-1 - - secret: my-secret-key-2 - - secret: my-secret-key-3 -``` - -The first key will be used for signing requests, while all of the keys will be -used for validation. This means that you can set up an asymmetric configuration -where some backend deployments do not have access to each other. - -For example, consider the case where we have split up the catalog, scaffolder, -and search plugin into three separate backend deployments. We can use the -following configurations to allow both the scaffolder and search plugin to speak -to the -catalog, but not the other way around, and to not allow any communication between -the scaffolder and search plugins. - -```yaml -# catalog config -backend: - auth: - keys: - - secret: my-secret-key-catalog - - secret: my-secret-key-scaffolder - - secret: my-secret-key-search - -# scaffolder config -backend: - auth: - keys: - - secret: my-secret-key-scaffolder - -# search config -backend: - auth: - keys: - - secret: my-secret-key-search -``` diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md index 126b136d0b..fecb3f5191 100644 --- a/docs/auth/service-to-service-auth.md +++ b/docs/auth/service-to-service-auth.md @@ -8,7 +8,7 @@ description: This section describes service to service authentication works, bot :::info This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend -system, you may want to read [its own article](./service-to-service-auth--old.md) +system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/auth/service-to-service-auth--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: @@ -219,7 +219,7 @@ provider's documentation. The subject returned from the token verification will become part of the credentials object that the request recipient plugins get. All subjects will have the prefix -`external:`, but you can also provide a custom subjectPrefix which will get appended before the +`external:`, but you can also provide a custom `subjectPrefix` which will get appended before the subject returned from your JWKS service (ex. `external:custom-prefix:sub`). Callers must pass along tokens with requests in the `Authorization` header when diff --git a/docs/backend-system/building-plugins-and-modules/02-testing.md b/docs/backend-system/building-plugins-and-modules/02-testing.md index 439fed597a..f72d2de49b 100644 --- a/docs/backend-system/building-plugins-and-modules/02-testing.md +++ b/docs/backend-system/building-plugins-and-modules/02-testing.md @@ -37,7 +37,7 @@ describe('myPlugin', () => { features: [ myPlugin(), mockServices.rootConfig.factory({ data: fakeConfig }), - mockLogger, + mockLogger.factory, ], }); diff --git a/docs/features/software-templates/builtin-actions.md b/docs/features/software-templates/builtin-actions.md index bf6875764e..d732bd8a27 100644 --- a/docs/features/software-templates/builtin-actions.md +++ b/docs/features/software-templates/builtin-actions.md @@ -10,15 +10,19 @@ git repository. ## Action Modules -There are also several modules available for various SCM tools: +There are several action modules that are available to be added: -- Azure DevOps: `@backstage/plugin-scaffolder-backend-module-azure` -- Bitbucket Cloud: `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` -- Bitbucket Server: `@backstage/plugin-scaffolder-backend-module-bitbucket-server` -- Gerrit: `@backstage/plugin-scaffolder-backend-module-gerrit` -- Gitea: `@backstage/plugin-scaffolder-backend-module-gitea` -- GitHub: `@backstage/plugin-scaffolder-backend-module-github` -- GitLab: `@backstage/plugin-scaffolder-backend-module-gitlab` +- [Azure DevOps](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-azure): `@backstage/plugin-scaffolder-backend-module-azure` +- [Bitbucket Cloud](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-bitbucket-cloud): `@backstage/plugin-scaffolder-backend-module-bitbucket-cloud` +- [Bitbucket Server](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-bitbucket-server): `@backstage/plugin-scaffolder-backend-module-bitbucket-server` +- [Gerrit](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gerrit): `@backstage/plugin-scaffolder-backend-module-gerrit` +- [Gitea](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gitea): `@backstage/plugin-scaffolder-backend-module-gitea` +- [GitHub](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-github): `@backstage/plugin-scaffolder-backend-module-github` +- [GitLab](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-gitlab): `@backstage/plugin-scaffolder-backend-module-gitlab` +- [Rails](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-rails): `@backstage/plugin-scaffolder-backend-module-rails` +- [Yeoman](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-yeoman): `@backstage/plugin-scaffolder-backend-module-yeoman` +- [Sentry](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-sentry): `@backstage/plugin-scaffolder-backend-module-sentry` +- [Cookiecutter](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend-module-cookiecutter): `@backstage/plugin-scaffolder-backend-module-cookiecutter` ## Installing Action Modules @@ -37,7 +41,6 @@ const backend = createBackend(); backend.add(import('@backstage/plugin-app-backend')); -// catalog plugin backend.add(import('@backstage/plugin-catalog-backend')); backend.add( import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'), @@ -51,12 +54,6 @@ backend.add(import('@backstage/plugin-scaffolder-backend-module-github')); backend.start(); ``` -:::note Note - -This is a simplified example of what your backend may look like, you may have more code in here then this. - -::: - ## Listing Actions A list of all registered actions can be found under `/create/actions`. For local diff --git a/docs/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md index 571f1150d0..a692335ffe 100644 --- a/docs/features/software-templates/configuration.md +++ b/docs/features/software-templates/configuration.md @@ -78,11 +78,11 @@ Once you have more than a few software templates you may want to customize your accomplish this by creating `groups` and passing them to your `ScaffolderPage` like below -``` +```tsx entity?.metadata?.tags?.includes('recommended') ?? false, }, @@ -96,6 +96,7 @@ top of the page above any other templates not filtered by this group or others. You can also further customize groups by passing in a `titleComponent` instead of a `title` which will be a component to use as the header instead of just the default `ContentHeader` with the `title` set as it's value. + ![Grouped Templates](../../assets/software-templates/grouped-templates.png) There is also an option to hide some templates. diff --git a/docs/features/software-templates/input-examples.md b/docs/features/software-templates/input-examples.md index bb4ecde946..f278351255 100644 --- a/docs/features/software-templates/input-examples.md +++ b/docs/features/software-templates/input-examples.md @@ -12,6 +12,8 @@ It is important to remember that all examples are based on [react-jsonschema-for ### Simple input with basic validations +We can use a `pattern` to validate the input. The `pattern` is a regular expression that the input must match. + ```yaml parameters: - title: Fill in some steps @@ -28,6 +30,8 @@ parameters: #### Custom validation error message +This example shows how to customize the error message shown when the `pattern` validation fails. + ```yaml parameters: - title: Fill in some steps @@ -47,6 +51,8 @@ parameters: ### Multi line text input +If you need to insert a multi-line string, you can use the `ui:widget: textarea` option. This will create a text area input instead of a single line input. + ```yaml parameters: - title: Fill in some steps @@ -74,6 +80,8 @@ parameters: ### Array with custom titles +In the example below the user will see the `enumNames` instead of the `enum` values, making it easier to read. + ```yaml parameters: - title: Fill in some steps @@ -103,6 +111,8 @@ parameters: ### A multiple choices list +This is a simple multiple choice list. + ```yaml parameters: - title: Fill in some steps @@ -122,6 +132,8 @@ parameters: ### Array with another types +In the example below, it will create an array of custom objects. Once you add one, you will see an object where each one contains 3 fields, "How are you?", "Is it sunny?" and "Anything else?". + ```yaml parameters: - title: Fill in some steps @@ -138,19 +150,19 @@ parameters: type: object properties: array: - title: Array string with default value + title: How are you? type: string - default: value3 + default: good enum: - - value1 - - value2 - - value3 + - good + - okay + - great flag: - title: Boolean flag + title: Is it sunny? type: boolean ui:widget: radio someInput: - title: Simple text input + title: Anything else? type: string ``` @@ -158,6 +170,8 @@ parameters: ### Boolean +This adds a simple checkbox to the form. The value will be `true` or `false`. + ```yaml parameters: - title: Fill in some steps @@ -169,6 +183,8 @@ parameters: ### Boolean Yes or No options +This example shows how to use a radio button instead of a checkbox with `Yes` or `No` options. + ```yaml parameters: - title: Fill in some steps @@ -181,6 +197,8 @@ parameters: ### Boolean multiple options +You can create multiple checkboxes with different options. The example below shows how to create a list of features that can be enabled or disabled for example. + ```yaml parameters: - title: Fill in some steps @@ -200,6 +218,8 @@ parameters: ## Markdown text blocks +Its possible to render markdown text blocks in the form. This is useful to add some help text or instructions for the user. + ```yaml parameters: - title: Fill in some steps @@ -217,7 +237,7 @@ parameters: ## Use parameters as condition in steps -Conditions use Javascript equality operators. +Its possible to conditionally run steps based on the value of a parameter. In the example below, we trigger the steps depending on the value of the `environment` parameter. ```yaml - name: Only development environments @@ -241,6 +261,8 @@ Conditions use Javascript equality operators. ## Use parameters as conditional for fields +Its also possible to conditionally show fields based on the value of a parameter. In the example below, we show the `lastName` field only if the `includeName` parameter is set to `true`. + ```yaml parameters: - title: Fill in some steps @@ -269,6 +291,9 @@ parameters: ### Multiple conditional fields with custom ordering +In this example, we show how to conditionally show multiple fields based on the value of a parameter. The `ui:order` property is used to control the order of the fields in the form. +In this case, we show the `lastName` and `address` fields only if the `includeName` and `includeAddress` parameters are set to `true`. + ```yaml parameters: - title: Fill in some steps @@ -349,12 +374,16 @@ Testing of this functionality is not yet supported using _create/edit_. In addit ::: +Its possible to use placeholders to reference remote files. This is useful when you have some standard parameters or actions that you want to reuse across multiple templates. + ### template.yaml +In our template, we use the `$yaml` placeholder to reference the `parameters.yaml` and `action.yaml` files. The `parameters.yaml` file contains some parameters that we want to use in our template, and the `action.yaml` file contains the action that we want to run. + ```yaml spec: parameters: - - $yaml: https://github.com/example/path/to/example.yaml + - $yaml: https://github.com/example/path/to/parameters.yaml # This would become the parameters as referenced in the parameters.yaml file - title: Fill in some steps properties: path: @@ -362,7 +391,7 @@ spec: type: string steps: - - $yaml: https://github.com/example/path/to/action.yaml + - $yaml: https://github.com/example/path/to/action.yaml # This would become the publish action as referenced in the action.yaml file - id: fetch name: Fetch template @@ -371,7 +400,9 @@ spec: url: ${{ parameters.path if parameters.path else '/root' }} ``` -### example.yaml +### parameters.yaml + +The `url` parameter will be added to the template. ```yaml title: Provide simple information @@ -385,6 +416,8 @@ properties: ### action.yaml +The `publish:github` action will be included in our template. + ```yaml id: publish name: Publish files diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index dd7a5e60a1..9a502d7195 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -25,7 +25,7 @@ setup process, allowing you to focus on your actions' unique functionality. Start by using the `yarn backstage-cli new` command to generate a scaffolder module. This command sets up the necessary boilerplate code, providing a smooth start: -``` +```sh $ yarn backstage-cli new ? What do you want to create? plugin-common - A new isomorphic common plugin package @@ -34,8 +34,6 @@ $ yarn backstage-cli new > scaffolder-module - An module exporting custom actions for @backstage/plugin-scaffolder-backend ``` -You can find a [list](../../tooling/cli/03-commands.md) of all commands provided by the Backstage CLI. - When prompted, select the option to generate a scaffolder module. This creates a solid foundation for your custom action. Enter the name of the module you wish to create, and the CLI will generate the required files and directory structure. @@ -87,7 +85,7 @@ for reference. The `createTemplateAction` takes an object which specifies the following: -- `id` - A unique ID for your custom action. We encourage you to namespace these +- `id` - A **unique** ID for your custom action. We encourage you to namespace these in some way so that they won't collide with future built-in actions that we may ship with the `scaffolder-backend` plugin. - `description` - An optional field to describe the purpose of the action. This will populate in the `/create/actions` @@ -151,9 +149,6 @@ Also feel free to use your company name to namespace them if you prefer too, for Prefer to use `camelCase` over `snake_case` or `kebab-case` for these actions if possible, which leads to better reading and writing of template entity definitions. -> We're aware that there are some exceptions to this, but try to follow as close as possible. We'll be working on -> migrating these in the repository over time too. - ### Adding a TemplateExample A TemplateExample is a predefined structure that can be used to create custom actions in your software templates. It @@ -185,9 +180,23 @@ export const examples: TemplateExample[] = [ ]; ``` -Add the example to the `createTemplateAction` under the object property `examples`: +Add the example to `createTemplateAction` by including the `examples` property: -`return createTemplateAction<{ contents: string; filename: string }>({id: 'acme:file:create', description: 'Create an Acme file', examples, ...};` +```ts +return createTemplateAction({ + id: 'acme:file:create', + description: 'Create an Acme file', + schema: { + input: { + contents: d => d.string().describe('The contents of the file'), + filename: d => + d.string().describe('The filename of the file that will be created'), + }, + }, + examples, + // ...rest of the action configuration +}); +``` ### The context object @@ -275,9 +284,7 @@ env.registerInit({ ### Using Checkpoints in Custom Actions (Experimental) -Idempotent action could be achieved via the usage of checkpoints. - -Example: +Idempotent action could be achieved via the usage of checkpoints, for example: ```ts title="plugins/my-company-scaffolder-actions-plugin/src/vendor/my-custom-action.ts" const res = await ctx.checkpoint?.({ @@ -305,78 +312,9 @@ If you'll preserve the same key, and you'll try to restart the affected task, it The cached result will not match with the expected updated return type. By changing the key, you'll invalidate the cache of the checkpoint. -### Register Custom Actions with the Legacy Backend System +## Contributed Community Actions -Once you have your Custom Action ready for usage with the scaffolder, you'll -need to pass this into the `scaffolder-backend` `createRouter` function. You -should have something similar to the below in -`packages/backend/src/plugins/scaffolder.ts` +You can find a list of community-contributed and open-source actions by: -```ts -return await createRouter({ - catalogClient, - logger: env.logger, - config: env.config, - database: env.database, - reader: env.reader, -}); -``` - -There's another property you can pass here, which is an array of `actions` which -will set the available actions that the scaffolder has access to. - -```ts -import { createBuiltinActions } from '@backstage/plugin-scaffolder-backend'; -import { ScmIntegrations } from '@backstage/integration'; -import { createNewFileAction } from './scaffolder/actions/custom'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const catalogClient = new CatalogClient({ discoveryApi: env.discovery }); - const integrations = ScmIntegrations.fromConfig(env.config); - - const builtInActions = createBuiltinActions({ - integrations, - catalogClient, - config: env.config, - reader: env.reader, - }); - - const actions = [...builtInActions, createNewFileAction()]; - - return createRouter({ - actions, - catalogClient: catalogClient, - logger: env.logger, - config: env.config, - database: env.database, - reader: env.reader, - }); -} -``` - -## List of custom action packages - -Here is a list of Open Source custom actions that you can add to your Backstage -scaffolder backend: - -| Name | Package | Owner | -| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| Yeoman | [plugin-scaffolder-backend-module-yeoman](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-yeoman) | [Backstage](https://backstage.io) | -| Cookiecutter | [plugin-scaffolder-backend-module-cookiecutter](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-cookiecutter) | [Backstage](https://backstage.io) | -| Rails | [plugin-scaffolder-backend-module-rails](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-rails) | [Backstage](https://backstage.io) | -| HTTP requests | [scaffolder-backend-module-http-request](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request) | [Roadie](https://roadie.io) | -| Utility actions | [scaffolder-backend-module-utils](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-utils) | [Roadie](https://roadie.io) | -| AWS cli actions | [scaffolder-backend-module-aws](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-aws) | [Roadie](https://roadie.io) | -| Scaffolder .NET Actions | [plugin-scaffolder-dotnet-backend](https://www.npmjs.com/package/@plusultra/plugin-scaffolder-dotnet-backend) | [Alef Carlos](https://github.com/alefcarlos) | -| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) | -| Azure Pipeline Actions | [scaffolder-backend-module-azure-pipelines](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-pipelines) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | -| Azure Repository Actions | [scaffolder-backend-module-azure-repositories](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-repositories) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | -| Snyk Import Project | [plugin-scaffolder-backend-module-snyk](https://www.npmjs.com/package/@ma11hewthomas/plugin-scaffolder-backend-module-snyk) | [Matthew Thomas](https://github.com/Ma11hewThomas) | -| JSON Merge Actions | [plugin-scaffolder-json-merge-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-json-merge-actions) | [Drew Hill](https://github.com/arhill05) | -| NPM Actions | [plugin-scaffolder-npm-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-npm-actions) | [Drew Hill](https://github.com/arhill05) | -| Slack Actions | [plugin-scaffolder-backend-module-slack](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-backend-module-slack) | [Drew Hill](https://github.com/arhill05) | -| Microsoft Teams Actions | [plugin-scaffolder-backend-module-ms-teams](https://www.npmjs.com/package/@grvpandey11/backstage-plugin-scaffolder-backend-module-ms-teams) | [Gaurav Pandey](https://github.com/grvpandey11) | - -Have fun! 🚀 +- Going to the [Backstage Plugin Directory](https://backstage.io/plugins/) and filter by `scaffolder`! +- Checking out the [Community Plugins Repo](https://github.com/backstage/community-plugins)! diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 23786db54f..f02b2722f6 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -4,8 +4,7 @@ title: Writing Templates description: Details around creating your own custom Software Templates --- -Templates are stored in the **Software Catalog** under a kind `Template`. You -can create your own templates with a small `yaml` definition which describes the +You can create your own templates with a small `yaml` definition which describes the template and its metadata, along with some input variables that your template will need, and then a list of actions which are then executed by the scaffolding service. diff --git a/docs/getting-started/config/database.md b/docs/getting-started/config/database.md index e068361966..eed1586dc8 100644 --- a/docs/getting-started/config/database.md +++ b/docs/getting-started/config/database.md @@ -153,6 +153,9 @@ services: environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: + # If you want to set a timezone you can use the following environment variables, this is handy when trying to figure out when scheduled tasks will run! + # TZ: Europe/Stockholm + # PGTZ: Europe/Stockholm ports: - 5432:5432 ``` diff --git a/docs/integrations/aws-s3/discovery--old.md b/docs/integrations/aws-s3/discovery--old.md deleted file mode 100644 index 3a9d30a370..0000000000 --- a/docs/integrations/aws-s3/discovery--old.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -id: discovery--old -title: AWS S3 Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from an AWS S3 Bucket ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The AWS S3 integration has a special entity provider for discovering catalog -entities located in an S3 Bucket. If you have a bucket that contains multiple -catalog files, and you want to automatically discover them, you can use this -provider. The provider will crawl your S3 bucket and register entities -matching the configured path. This can be useful as an alternative to static -locations or manually adding things to the catalog. - -To use the entity provider, you'll need an AWS S3 integration -[set up](locations.md) with `accessKeyId` and `secretAccessKey`, and/or -a `roleArn` or none of these (e.g., profile- or instance-based credentials). - -At production deployments, you likely manage these with the permissions attached -to your instance. - -In your configuration, you add a provider config per bucket: - -```yaml -# app-config.yaml - -catalog: - providers: - awsS3: - yourProviderId: # identifies your dataset / provider independent of config changes - bucketName: sample-bucket - prefix: prefix/ # optional - region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in SchedulerServiceTaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } -``` - -For simple setups, you can omit the provider ID at the config -which has the same effect as using `default` for it. - -```yaml -# app-config.yaml - -catalog: - providers: - awsS3: - # uses "default" as provider ID - bucketName: sample-bucket - prefix: prefix/ # optional - region: us-east-2 # optional, uses the default region otherwise - schedule: # same options as in SchedulerServiceTaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } -``` - -As this provider is not one of the default providers, you will first need to install -the AWS catalog plugin: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-aws -``` - -Once you've done that, you'll also need to add the segment below to `packages/backend/src/plugins/catalog.ts`: - -```ts -/* packages/backend/src/plugins/catalog.ts */ - -import { AwsS3EntityProvider } from '@backstage/plugin-catalog-backend-module-aws'; - -const builder = await CatalogBuilder.create(env); -/** ... other processors and/or providers ... */ -builder.addEntityProvider( - AwsS3EntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), -); -``` diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index f4d18015a2..fa1eb017fc 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from an AWS S3 Bucket --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/aws-s3/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The AWS S3 integration has a special entity provider for discovering catalog diff --git a/docs/integrations/azure/discovery--old.md b/docs/integrations/azure/discovery--old.md deleted file mode 100644 index 27532fe6ee..0000000000 --- a/docs/integrations/azure/discovery--old.md +++ /dev/null @@ -1,186 +0,0 @@ ---- -id: discovery--old -title: Azure DevOps Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from repositories in an Azure DevOps organization ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Azure DevOps integration has a special entity provider for discovering -catalog entities within an Azure DevOps. The provider will crawl your Azure -DevOps organization and register entities matching the configured path. This can -be useful as an alternative to static locations or manually adding things to the -catalog. - -This guide explains how to install and configure the Azure DevOps Entity Provider (recommended) or the Azure DevOps Processor. - -## Dependencies - -### Code Search Feature - -Azure discovery is driven by the Code Search feature in Azure DevOps, this may not be enabled by default. For Azure -DevOps Services you can confirm this by looking at the installed extensions in your Organization Settings. For Azure -DevOps Server you'll find this information in your Collection Settings. - -If the Code Search extension is not listed then you can install it from the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=ms.vss-code-search&targetId=f9352dac-ba6e-434e-9241-a848a510ce3f&utm_source=vstsproduct&utm_medium=SearchExtStatus). - -### Azure Integration - -Setup [Azure integration](locations.md) with `host` and `token`. Host must be `dev.azure.com` for Cloud users, otherwise set this to your on-premise hostname. - -## Installation - -At your configuration, you add one or more provider configs: - -```yaml title="app-config.yaml" -catalog: - providers: - azureDevOps: - yourProviderId: # identifies your dataset / provider independent of config changes - organization: myorg - project: myproject - repository: service-* # this will match all repos starting with service-* - path: /catalog-info.yaml - schedule: # optional; same options as in SchedulerServiceTaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } - yourSecondProviderId: # identifies your dataset / provider independent of config changes - organization: myorg - project: '*' # this will match all projects - repository: '*' # this will match all repos - path: /catalog-info.yaml - anotherProviderId: # another identifier - organization: myorg - project: myproject - repository: '*' # this will match all repos - path: /src/*/catalog-info.yaml # this will search for files deep inside the /src folder - yetAnotherProviderId: # guess, what? Another one :) - host: selfhostedazure.yourcompany.com - organization: myorg - project: myproject - branch: development -``` - -The parameters available are: - -- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host. -- **`organization:`** Your Organization slug (or Collection for on-premise users). Required. -- **`project:`** _(required)_ Your project slug. Wildcards are supported as shown on the examples above. Using '\*' will search all projects. For a project name containing spaces, use both single and double quotes as in `project: '"My Project Name"'`. -- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched. -- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml. -- **`branch:`** _(optional)_ The branch name to use. -- **`schedule`**: - - **`frequency`**: - How often you want the task to run. The system does its best to avoid overlapping invocations. - - **`timeout`**: - The maximum amount of time that a single task invocation can take. - - **`initialDelay`** _(optional)_: - The amount of time that should pass before the first invocation happens. - - **`scope`** _(optional)_: - `'global'` or `'local'`. Sets the scope of concurrency control. - -_Note:_ - -- The path parameter follows the same rules as the search on Azure DevOps web interface. For more details visit the [official search documentation](https://docs.microsoft.com/en-us/azure/devops/project/search/get-started-search?view=azure-devops). -- To use branch parameters, it is necessary that the desired branch be added to the "Searchable branches" list within Azure DevOps Repositories. To do this, follow the instructions below: - -1. Access your Azure DevOps and open the repository in which you want to add the branch. -2. Click on "Settings" in the lower-left corner of the screen. -3. Select the "Options" option in the left navigation bar. -4. In the "Searchable branches" section, click on the "Add" button to add a new branch. -5. In the window that appears, enter the name of the branch you want to add and click "Add". -6. The added branch will now appear in the "Searchable branches" list. - -It may take some time before the branch is indexed and searchable. - -As this provider is not one of the default providers, you will first need to install -the Azure catalog plugin: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-azure -``` - -Once you've done that, you'll also need to add the segment below to `packages/backend/src/plugins/catalog.ts`: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { AzureDevOpsEntityProvider } from '@backstage/plugin-catalog-backend-module-azure'; - -const builder = await CatalogBuilder.create(env); -/** ... other processors and/or providers ... */ -/* highlight-add-start */ -builder.addEntityProvider( - AzureDevOpsEntityProvider.fromConfig(env.config, { - logger: env.logger, - // optional: alternatively, use scheduler with schedule defined in app-config.yaml - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), - // optional: alternatively, use schedule - scheduler: env.scheduler, - }), -); -/* highlight-add-end */ -``` - -## Alternative Processor - -As an alternative to the entity provider `AzureDevOpsEntityProvider`, you can still use the `AzureDevopsDiscoveryProcessor`. - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { AzureDevOpsDiscoveryProcessor } from '@backstage/plugin-catalog-backend-module-azure'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-next-line */ - builder.addProcessor( - AzureDevOpsDiscoveryProcessor.fromConfig(env.config, { - logger: env.logger, - }), - ); - - // .. -} -``` - -```yaml -catalog: - locations: - # Scan all repositories for a catalog-info.yaml in the root of the default branch - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject - # Or use a custom pattern for a subset of all repositories with default repository - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject/_git/service-* - # Or use a custom file format and location - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject/_git/*?path=/src/*/catalog-info.yaml - # And optionally provide a specific branch name using the version parameter - - type: azure-discovery - target: https://dev.azure.com/myorg/myproject/_git/*?path=/catalog-info.yaml&version=GBtopic/catalog-info -``` - -Note the `azure-discovery` type, as this is not a regular `url` processor. - -When using a custom pattern, the target is composed of these parts: - -- The base instance URL, `https://dev.azure.com` in this case -- The organization name which is required, `myorg` in this case -- The project name which is optional, `myproject` in this case. This defaults to \*, which scans all the projects where the token has access to. -- The repository blob to scan, which accepts \* wildcard tokens and must be - added after `_git/`. This can simply be `*` to scan all repositories in the - project. -- The path within each repository to find the catalog YAML file. This will - usually be `/catalog-info.yaml`, `/src/*/catalog-info.yaml` or a similar - variation for catalog files stored in the root directory of each repository. -- The repository branch to scan which is optional, `topic/catalog-info` in this case. If omitted, the repo's default branch will be scanned. The `GB` prefix is required, as this is how Azure DevOps identifies the version as a branch. diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 99feb9a806..b749f8e9ed 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from repositories in an --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/azure/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Azure DevOps integration has a special entity provider for discovering diff --git a/docs/integrations/azure/org--old.md b/docs/integrations/azure/org--old.md deleted file mode 100644 index 899120e88a..0000000000 --- a/docs/integrations/azure/org--old.md +++ /dev/null @@ -1,273 +0,0 @@ ---- -id: org--old -title: Microsoft Entra Tenant Data -sidebar_label: Org Data -# prettier-ignore -description: Importing users and groups from Microsoft Entra ID into Backstage ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage catalog can be set up to ingest organizational data - users and -teams - directly from a tenant in Microsoft Entra ID via the -Microsoft Graph API. - -## Installation - -The package is not installed by default, therefore you have to add `@backstage/plugin-catalog-backend-module-msgraph` to your backend package. - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-msgraph -``` - -Next add the basic configuration to `app-config.yaml` - -```yaml title="app-config.yaml" -catalog: - providers: - microsoftGraphOrg: - default: - tenantId: ${AZURE_TENANT_ID} - user: - filter: accountEnabled eq true and userType eq 'member' - group: - filter: > - securityEnabled eq false - and mailEnabled eq true - and groupTypes/any(c:c+eq+'Unified') - schedule: - frequency: PT1H - timeout: PT50M -``` - -Finally, register the plugin in `catalog.ts`. -For large organizations, this plugin can take a long time, so be careful setting low frequency / timeouts and importing a large amount of users / groups for the first try. - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { MicrosoftGraphOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-msgraph'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - builder.addEntityProvider( - MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Authenticating with Microsoft Graph - -### Local Development - -For a local dev environment, it's recommended you have the Azure CLI or Azure PowerShell installed, and are logged in to those. -Alternatively you can use VSCode with the Azure extension if you install `@azure/identity-vscode`. -When these are set up, the plugin will authenticate with the Microsoft Graph API without you needing to configure any credentials, or granting any special permissions. -If you can't do this, you'll have to create an App Registration. - -### App Registration - -If none of the other authentication methods work, you can create an app registration in the azure portal. -By default the graph plugin requires the following Application permissions (not Delegated) for Microsoft Graph: - -- `GroupMember.Read.All` -- `User.Read.All` - -If your organization required Admin Consent for these permissions, that will need to be granted. - -When authenticating with a ClientId/ClientSecret, you can either set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_SECRET` environment variables, or specify the values in configuration - -```yaml -microsoftGraphOrg: - default: - ##... - clientId: 9ef1aac6-b454-4e69-9cf5-7199df049281 - clientSecret: REDACTED -``` - -To authenticate with a certificate rather than a client secret, you can set the `AZURE_TENANT_ID`, `AZURE_CLIENT_ID` and `AZURE_CLIENT_CERTIFICATE_PATH` environments - -### Managed Identity - -If deploying to resources that supports Managed Identity, and has identities configured (e.g. Azure App Services, Azure Container Apps), Managed Identity should be picked up without any additional configuration. -If your app has multiple managed identities, you may need to set the `AZURE_CLIENT_ID` environment variable to tell Azure Identity which identity to use. - -To grant the managed identity the same permissions as mentioned in _App Registration_ above, [please follow this guide](https://docs.microsoft.com/en-us/azure/app-service/tutorial-connect-app-access-microsoft-graph-as-app-javascript?tabs=azure-powershell) - -## Filtering imported Users and Groups - -By default, the plugin will import all users and groups from your directory. -This can be customized through [filters](https://learn.microsoft.com/en-us/graph/filter-query-parameter) and [search](https://learn.microsoft.com/en-us/graph/search-query-parameter) queries. Keep in mind that if you omit filters and search queries for the user or group properties, the plugin will automatically import all available users or groups. - -### Groups - -A smaller set of groups can be obtained by configuring a search query or a filter. -If both `filter` and `search` are provided, then groups must match both to be ingested. - -```yaml -microsoftGraphOrg: - providerId: - group: - filter: securityEnabled eq false and mailEnabled eq true and groupTypes/any(c:c+eq+'Unified') - search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")' -``` - -In addition to these groups, one additional group will be created for your organization. -All imported groups will be a child of this group. - -### Users - -There are two modes for importing users - You can import all user objects matching a `filter`. - -```yaml -microsoftGraphOrg: - providerId: - user: - filter: accountEnabled eq true and userType eq 'member' -``` - -Alternatively you can import users that are members of specific groups. -For each group matching the `search` and `filter` query, each group member will be imported. -Only direct group members will be imported, not transient users. - -```yaml -microsoftGraphOrg: - providerId: - userGroupMember: - filter: "displayName eq 'Backstage Users'" - search: '"description:One" AND ("displayName:Video" OR "displayName:Drive")' -``` - -### User photos - -By default, the photos of users will be fetched and added to each user entity. For huge organizations this may be unfeasible, as it will take a _very_ long time, and can be disabled by setting `loadPhotos` to `false`: - -```yaml -microsoftGraphOrg: - providerId: - user: - filter: ... - loadPhotos: false -``` - -## Customizing Transformation - -Ingested entities can be customized by providing custom transformers. -These can be used to completely replace the built in logic, or used to tweak it by using the default transformers (`defaultGroupTransformer`, `defaultUserTransformer` and `defaultOrganizationTransformer` -Entities can also be excluded from backstage by returning `undefined`. - -These Transformers are be registered when configuring `MicrosoftGraphOrgEntityProvider` - -```ts -builder.addEntityProvider( - MicrosoftGraphOrgEntityProvider.fromConfig(env.config, { - // ... - /* highlight-add-start */ - groupTransformer: myGroupTransformer, - userTransformer: myUserTransformer, - organizationTransformer: myOrganizationTransformer, - /* highlight-add-end */ - }), -); -``` - -When using custom transformers, you may want to customize the data returned. -Several configuration options can be provided to tweak the Microsoft Graph query to get the data you need - -```yaml -microsoftGraphOrg: - providerId: - user: - expand: manager - group: - expand: member - select: ['id', 'displayName', 'description'] -``` - -The following provides an example of each kind of transformer - -```ts -import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; -import { - defaultGroupTransformer, - defaultUserTransformer, - defaultOrganizationTransformer, -} from '@backstage/plugin-catalog-backend-module-msgraph'; -import { GroupEntity, UserEntity } from '@backstage/catalog-model'; - -// This group transformer completely replaces the built in logic with custom logic. -export async function myGroupTransformer( - group: MicrosoftGraph.Group, - groupPhoto?: string, -): Promise { - return { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Group', - metadata: { - name: group.id!, - annotations: {}, - }, - spec: { - type: 'Microsoft Entra ID', - children: [], - }, - }; -} - -// This user transformer makes use of the built in logic, but also sets the description field -export async function myUserTransformer( - graphUser: MicrosoftGraph.User, - userPhoto?: string, -): Promise { - const backstageUser = await defaultUserTransformer(graphUser, userPhoto); - - if (backstageUser) { - backstageUser.metadata.description = 'Loaded from Microsoft Entra ID'; - } - - return backstageUser; -} - -// Example organization transformer that removes the organization group completely -export async function myOrganizationTransformer( - graphOrganization: MicrosoftGraph.Organization, -): Promise { - return undefined; -} -``` - -## Troubleshooting - -### No data - -First check your logs for the message `Reading msgraph users and groups`. -If you don't see this, check you've registered the provider, and that the schedule is valid - -If you see a log entry `Read 0 msgraph users and 0 msgraph groups`, check your search and filter arguments. - -If you see the start message (`Reading msgraph users and groups`) but no end message (`Read X msgraph users and Y msgraph groups`), then it is likely the job is taking a long time due to a large volume of data. -The default behavior is to import all users and groups, which is often more data than needed. -Try importing a smaller set of data (e.g. `filter: displayName eq 'John Smith'`). - -### Authentication / Token Errors - -See [Troubleshooting Azure Identity Authentication Issues](https://aka.ms/azsdk/js/identity/troubleshoot) - -### Error while reading users from Microsoft Graph: Authorization_RequestDenied - Insufficient privileges to complete the operation - -- Make sure you've granted all the required permissions to your application registration or managed identity -- Make sure the permissions are `Application` permissions rather than `Delegated` -- If your organization has configured "Admin consent" to be required, make sure this has been granted for your application permissions -- If your group queries are returning Microsoft Teams groups, you may need to grant addition permissions (e.g. `Team.ReadBasic.All`, `TeamMember.Read.All`) -- If you've added additional `select` or `expand` fields, those may need additional permissions granted diff --git a/docs/integrations/azure/org.md b/docs/integrations/azure/org.md index 3765275e1e..58c6c9f889 100644 --- a/docs/integrations/azure/org.md +++ b/docs/integrations/azure/org.md @@ -7,7 +7,7 @@ description: Importing users and groups from Microsoft Entra ID into Backstage --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/azure/org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Backstage catalog can be set up to ingest organizational data - users and diff --git a/docs/integrations/bitbucketServer/discovery--old.md b/docs/integrations/bitbucketServer/discovery--old.md deleted file mode 100644 index 83fe70da0d..0000000000 --- a/docs/integrations/bitbucketServer/discovery--old.md +++ /dev/null @@ -1,123 +0,0 @@ ---- -id: discovery--old -title: Bitbucket Server Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from repositories in Bitbucket Server ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Bitbucket Server integration has a special entity provider for discovering -catalog files located in Bitbucket Server. -The provider will search your Bitbucket Server account and register catalog files matching the configured path -as Location entity and via following processing steps add all contained catalog entities. -This can be useful as an alternative to static locations or manually adding things to the catalog. - -## Installation - -You will have to add the entity provider in the catalog initialization code of your -backend. The provider is not installed by default, therefore you have to add a -dependency to `@backstage/plugin-catalog-backend-module-bitbucket-server` to your backend -package. - -```bash -# From your Backstage root directory -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-bitbucket-server -``` - -And then add the entity provider to your catalog builder: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { BitbucketServerEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-server'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - builder.addEntityProvider( - BitbucketServerEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Configuration - -To use the entity provider, you'll need a [Bitbucket Server integration set up](locations.md). - -Additionally, you need to configure your entity provider instance(s): - -```yaml title="app-config.yaml" -catalog: - providers: - bitbucketServer: - yourProviderId: # identifies your ingested dataset - host: 'bitbucket.mycompany.com' - catalogPath: /catalog-info.yaml # default value - filters: # optional - projectKey: '^apis-.*$' # optional; RegExp - repoSlug: '^service-.*$' # optional; RegExp - skipArchivedRepos: true # optional; boolean - schedule: # same options as in TaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } -``` - -- **`host`**: - The host of the Bitbucket Server instance, **note**: the host needs to registered as an integration as well, see [location](locations.md). -- **`catalogPath`** _(optional)_: - Default: `/catalog-info.yaml`. - Path where to look for `catalog-info.yaml` files. - When started with `/`, it is an absolute path from the repo root. -- **`filters`** _(optional)_: - - **`projectKey`** _(optional)_: - Regular expression used to filter results based on the project key. - - **`repoSlug`** _(optional)_: - Regular expression used to filter results based on the repo slug. - - **`skipArchivedRepos`** _(optional)_: - Boolean flag to filter out archived repositories. -- **`schedule`**: - - **`frequency`**: - How often you want the task to run. The system does its best to avoid overlapping invocations. - - **`timeout`**: - The maximum amount of time that a single task invocation can take. - - **`initialDelay`** _(optional)_: - The amount of time that should pass before the first invocation happens. - - **`scope`** _(optional)_: - `'global'` or `'local'`. Sets the scope of concurrency control. - -## Custom location processing - -The Bitbucket Server Entity Provider will by default emit a location for each -matching repository. However, it is possible to override this functionality and take full control of how each -matching repository is processed. - -`BitbucketServerEntityProvider.fromConfig` takes an optional parameter -`options.parser` where you can set your own parser to be used for each matched -repository. - -```typescript -const provider = BitbucketServerEntityProvider.fromConfig(env.config, { - logger: env.logger, - schedule: env.scheduler, - parser: async function* customLocationParser(options: { - location: LocationSpec; - client: BitbucketServerClient; - }) { - // Custom logic for interpreting the matching repository - // See defaultBitbucketServerLocationParser for an example - }, -}); -``` diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 7947913182..36282b1f48 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from repositories in Bit --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/bitbucketServer/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Bitbucket Server integration has a special entity provider for discovering diff --git a/docs/integrations/gerrit/discovery--old.md b/docs/integrations/gerrit/discovery--old.md deleted file mode 100644 index 250de80bdf..0000000000 --- a/docs/integrations/gerrit/discovery--old.md +++ /dev/null @@ -1,73 +0,0 @@ ---- -id: discovery--old -title: Gerrit Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from Gerrit repositories ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Gerrit integration has a special entity provider for discovering catalog entities -from Gerrit repositories. The provider uses the "List Projects" API in Gerrit to get -a list of repositories and will automatically ingest all `catalog-info.yaml` files -stored in the root of the matching projects. - -## Installation - -As this provider is not one of the default providers, you will first need to install -the Gerrit provider plugin: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-gerrit -``` - -Then add the plugin to the plugin catalog `packages/backend/src/plugins/catalog.ts`: - -```ts -/* packages/backend/src/plugins/catalog.ts */ -import { GerritEntityProvider } from '@backstage/plugin-catalog-backend-module-gerrit'; -const builder = await CatalogBuilder.create(env); -/** ... other processors and/or providers ... */ -builder.addEntityProvider( - GerritEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), -); -``` - -## Configuration - -To use the discovery processor, you'll need a Gerrit integration -[set up](locations.md). Then you can add any number of providers. - -```yaml -# app-config.yaml -catalog: - providers: - gerrit: - yourProviderId: # identifies your dataset / provider independent of config changes - host: gerrit-your-company.com - branch: master # Optional - query: 'state=ACTIVE&prefix=webapps' - schedule: - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } - backend: - host: gerrit-your-company.com - branch: master # Optional - query: 'state=ACTIVE&prefix=backend' -``` - -The provider configuration is composed of three parts: - -- **`host`**: the host of the Gerrit integration to use. -- **`branch`** _(optional)_: the branch where we will look for catalog entities (defaults to "master"). -- **`query`**: this string is directly used as the argument to the "List Project" API. - Typically, you will want to have some filter here to exclude projects that will - never contain any catalog files. diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md index 137a716f35..dbbc7ba7a4 100644 --- a/docs/integrations/gerrit/discovery.md +++ b/docs/integrations/gerrit/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from Gerrit repositories --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/gerrit/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Gerrit integration has a special entity provider for discovering catalog entities diff --git a/docs/integrations/gerrit/locations.md b/docs/integrations/gerrit/locations.md index 3f237fc9ba..b69aee9016 100644 --- a/docs/integrations/gerrit/locations.md +++ b/docs/integrations/gerrit/locations.md @@ -11,6 +11,8 @@ or registered with the [catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) plugin. +Gerrit 3.9+ supports inline editing via URL. The integration enables this by default, following Gerrit's [official URL pattern](https://gerrit-review.googlesource.com/Documentation/user-inline-edit.html#create_from_url) for inline edits. + ## Configuration To use this integration, add at least one Gerrit configuration to your root `app-config.yaml`: @@ -22,6 +24,7 @@ integrations: gitilesBaseUrl: https://gerrit.company.com/gitiles baseUrl: https://gerrit.company.com/gerrit cloneUrl: https://gerrit.company.com/clone + disableEditUrl: false username: ${GERRIT_USERNAME} password: ${GERRIT_PASSWORD} ``` @@ -37,6 +40,7 @@ a structure with up to six elements: address here. This is the address that you would open in a browser. - `cloneUrl` (optional): The base URL for HTTP clones. Will default to `baseUrl` if not set. The address used to clone a repo is the `cloneUrl` plus the repo name. +- `disableEditUrl` (optional): Disable the edit mode. - `username` (optional): The Gerrit username to use in API requests. If neither a username nor password are supplied, anonymous access will be used. - `password` (optional): The password or http token for the Gerrit user. diff --git a/docs/integrations/github/discovery--old.md b/docs/integrations/github/discovery--old.md deleted file mode 100644 index 901462b1cb..0000000000 --- a/docs/integrations/github/discovery--old.md +++ /dev/null @@ -1,375 +0,0 @@ ---- -id: discovery--old -title: GitHub Discovery -sidebar_label: Discovery -# prettier-ignore -description: Automatically discovering catalog entities from repositories in a GitHub organization ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./discovery.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -## GitHub Provider - -The GitHub integration has a discovery provider for discovering catalog -entities within a GitHub organization. The provider will crawl the GitHub -organization and register entities matching the configured path. This can be -useful as an alternative to static locations or manually adding things to the -catalog. This is the preferred method for ingesting entities into the catalog. - -## Installation without Events Support - -You will have to add the provider in the catalog initialization code of your -backend. They are not installed by default, therefore you have to add a -dependency on `@backstage/plugin-catalog-backend-module-github` to your backend -package. - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github -``` - -And then add the entity provider to your catalog builder: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - builder.addEntityProvider( - GithubEntityProvider.fromConfig(env.config, { - logger: env.logger, - scheduler: env.scheduler, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Installation with Events Support - -_For the legacy backend system, please read the sub-section below._ - -The catalog module for GitHub comes with events support enabled. -This will make it subscribe to its relevant topics (`github.push`) -and expects these events to be published via the `EventsService`. - -Additionally, you should install the -[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md) -which will route received events from the generic topic `github` to more specific ones -based on the event type (e.g., `github.push`). - -In order to receive Webhook events by GitHub, you have to decide how you want them -to be ingested into Backstage and published to its `EventsService`. -You can decide between the following options (extensible): - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -### Legacy Backend System - -Please follow the installation instructions at - -- -- - -Additionally, you need to decide how you want to receive events from external sources like - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -Set up your provider - -```ts title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); - /* highlight-add-start */ - const githubProvider = GithubEntityProvider.fromConfig(env.config, { - events: env.events, - logger: env.logger, - scheduler: env.scheduler, - }); - builder.addEntityProvider(githubProvider); - /* highlight-add-end */ - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} -``` - -You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). The webhook will need to be configured to forward `push` events. - -## Configuration - -To use the discovery provider, you'll need a GitHub integration -[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md). For Personal Access Tokens you should pay attention to the [required scopes](https://backstage.io/docs/integrations/github/locations/#token-scopes), where you will need at least the `repo` scope for reading components. For GitHub Apps you will need to grant it the [required permissions](https://backstage.io/docs/integrations/github/github-apps#app-permissions) instead, where you will need at least the `Contents: Read-only` permissions for reading components. - -Then you can add a `github` config to the catalog providers configuration: - -```yaml -catalog: - providers: - github: - # the provider ID can be any camelCase string - providerId: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - branch: 'main' # string - repository: '.*' # Regex - schedule: # same options as in SchedulerServiceTaskScheduleDefinition - # supports cron, ISO duration, "human duration" as used in code - frequency: { minutes: 30 } - # supports ISO duration, "human duration" as used in code - timeout: { minutes: 3 } - customProviderId: - organization: 'new-org' # string - catalogPath: '/custom/path/catalog-info.yaml' # string - filters: # optional filters - branch: 'develop' # optional string - repository: '.*' # optional Regex - wildcardProviderId: - organization: 'new-org' # string - catalogPath: '/groups/**/*.yaml' # this will search all folders for files that end in .yaml - filters: # optional filters - branch: 'develop' # optional string - repository: '.*' # optional Regex - topicProviderId: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - branch: 'main' # string - repository: '.*' # Regex - topic: 'backstage-exclude' # optional string - topicFilterProviderId: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - branch: 'main' # string - repository: '.*' # Regex - topic: - include: ['backstage-include'] # optional array of strings - exclude: ['experiments'] # optional array of strings - validateLocationsExist: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - branch: 'main' # string - repository: '.*' # Regex - validateLocationsExist: true # optional boolean - visibilityProviderId: - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string - filters: - visibility: - - public - - internal - enterpriseProviderId: - host: ghe.example.net - organization: 'backstage' # string - catalogPath: '/catalog-info.yaml' # string -``` - -This provider supports multiple organizations via unique provider IDs. - -> **Note:** It is possible but certainly not recommended to skip the provider ID level. -> If you do so, `default` will be used as provider ID. - -- **`catalogPath`** _(optional)_: - Default: `/catalog-info.yaml`. - Path where to look for `catalog-info.yaml` files. - You can use wildcards - `*` or `**` - to search the path and/or the filename. - Wildcards cannot be used if the `validateLocationsExist` option is set to `true`. -- **`filters`** _(optional)_: - - **`branch`** _(optional)_: - String used to filter results based on the branch name. - Defaults to the default Branch of the repository. - - **`repository`** _(optional)_: - Regular expression used to filter results based on the repository name. - - **`topic`** _(optional)_: - Both of the filters below may be used at the same time but the exclusion filter has the highest priority. - In the example above, a repository with the `backstage-include` topic would still be excluded - if it were also carrying the `experiments` topic. - - **`include`** _(optional)_: - An array of strings used to filter in results based on their associated GitHub topics. - If configured, only repositories with one (or more) topic(s) present in the inclusion filter will be ingested - - **`exclude`** _(optional)_: - An array of strings used to filter out results based on their associated GitHub topics. - If configured, all repositories _except_ those with one (or more) topics(s) present in the exclusion filter will be ingested. - - **`visibility`** _(optional)_: - An array of strings used to filter results based on their visibility. Available options are `private`, `internal`, `public`. If configured (non empty), only repositories with visibility present in the filter will be ingested -- **`host`** _(optional)_: - The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). -- **`organization`**: - Name of your organization account/workspace. - If you want to add multiple organizations, you need to add one provider config each. -- **`validateLocationsExist`** _(optional)_: - Whether to validate locations that exist before emitting them. - This option avoids generating locations for catalog info files that do not exist in the source repository. - Defaults to `false`. - Due to limitations in the GitHub API's ability to query for repository objects, this option cannot be used in - conjunction with wildcards in the `catalogPath`. -- **`schedule`**: - - **`frequency`**: - How often you want the task to run. The system does its best to avoid overlapping invocations. - - **`timeout`**: - The maximum amount of time that a single task invocation can take. - - **`initialDelay`** _(optional)_: - The amount of time that should pass before the first invocation happens. - - **`scope`** _(optional)_: - `'global'` or `'local'`. Sets the scope of concurrency control. - -## GitHub API Rate Limits - -GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise -accounts). The snippet below refreshes the Backstage catalog data every 35 minutes, which issues an API request for each discovered location. - -If your requests are too frequent then you may get throttled by -rate limiting. You can change the refresh frequency of the catalog in your `app-config.yaml` file by controlling the `schedule`. - -```yaml -schedule: - frequency: { minutes: 35 } - timeout: { minutes: 3 } -``` - -More information about scheduling can be found on the [SchedulerServiceTaskScheduleDefinition](https://backstage.io/docs/reference/backend-plugin-api.schedulerservicetaskscheduledefinition) page. - -Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication -which carries a much higher rate limit at GitHub. - -This is true for any method of adding GitHub entities to the catalog, but -especially easy to hit with automatic discovery. - -## GitHub Processor (To Be Deprecated) - -The GitHub integration has a special discovery processor for discovering catalog -entities within a GitHub organization. The processor will crawl the GitHub -organization and register entities matching the configured path. This can be -useful as an alternative to static locations or manually adding things to the -catalog. - -## Installation - -You will have to add the processors in the catalog initialization code of your -backend. They are not installed by default, therefore you have to add a -dependency on `@backstage/plugin-catalog-backend-module-github` to your backend -package, plus `@backstage/integration` for the basic credentials management: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/integration @backstage/plugin-catalog-backend-module-github -``` - -And then add the processors to your catalog builder: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-start */ -import { - GithubDiscoveryProcessor, - GithubOrgReaderProcessor, -} from '@backstage/plugin-catalog-backend-module-github'; -import { - ScmIntegrations, - DefaultGithubCredentialsProvider, -} from '@backstage/integration'; -/* highlight-add-end */ - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-start */ - const integrations = ScmIntegrations.fromConfig(env.config); - const githubCredentialsProvider = - DefaultGithubCredentialsProvider.fromIntegrations(integrations); - builder.addProcessor( - GithubDiscoveryProcessor.fromConfig(env.config, { - logger: env.logger, - githubCredentialsProvider, - }), - GithubOrgReaderProcessor.fromConfig(env.config, { - logger: env.logger, - githubCredentialsProvider, - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Configuration - -To use the discovery processor, you'll need a GitHub integration -[set up](locations.md) with either a [Personal Access Token](../../getting-started/config/authentication.md) or [GitHub Apps](./github-apps.md). - -Then you can add a location target to the catalog configuration: - -```yaml -catalog: - locations: - # (since 0.13.5) Scan all repositories for a catalog-info.yaml in the root of the default branch - - type: github-discovery - target: https://github.com/myorg - # Or use a custom pattern for a subset of all repositories with default repository - - type: github-discovery - target: https://github.com/myorg/service-*/blob/-/catalog-info.yaml - # Or use a custom file format and location - - type: github-discovery - target: https://github.com/*/blob/-/docs/your-own-format.yaml - # Or use a specific branch-name - - type: github-discovery - target: https://github.com/*/blob/backstage-docs/catalog-info.yaml -``` - -Note the `github-discovery` type, as this is not a regular `url` processor. - -When using a custom pattern, the target is composed of three parts: - -- The base organization URL, `https://github.com/myorg` in this case -- The repository blob to scan, which accepts \* wildcard tokens. This can simply - be `*` to scan all repositories in the organization. This example only looks - for repositories prefixed with `service-`. -- The path within each repository to find the catalog YAML file. This will - usually be `/blob/main/catalog-info.yaml`, `/blob/master/catalog-info.yaml` or - a similar variation for catalog files stored in the root directory of each - repository. You could also use a dash (`-`) for referring to the default - branch. - -## GitHub API Rate Limits - -GitHub [rate limits](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) API requests to 5,000 per hour (or more for Enterprise -accounts). The default Backstage catalog backend refreshes data every 100 -seconds, which issues an API request for each discovered location. - -This means if you have more than ~140 catalog entities, you may get throttled by -rate limiting. You can change the refresh rate of the catalog in your `packages/backend/src/plugins/catalog.ts` file: - -```typescript -const builder = await CatalogBuilder.create(env); - -// For example, to refresh every 5 minutes (300 seconds). -builder.setProcessingIntervalSeconds(300); -``` - -Alternatively, or additionally, you can configure [github-apps](github-apps.md) authentication -which carries a much higher rate limit at GitHub. - -This is true for any method of adding GitHub entities to the catalog, but -especially easy to hit with automatic discovery. diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index e2513cdf21..354569cdb6 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -7,7 +7,7 @@ description: Automatically discovering catalog entities from repositories in a G --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/github/discovery--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: ## GitHub Provider diff --git a/docs/integrations/github/org--old.md b/docs/integrations/github/org--old.md deleted file mode 100644 index bf12cad036..0000000000 --- a/docs/integrations/github/org--old.md +++ /dev/null @@ -1,365 +0,0 @@ ---- -id: org--old -title: GitHub Organizational Data -sidebar_label: Org Data -# prettier-ignore -description: Importing users and groups from a GitHub organization into Backstage ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./org.md) instead.Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage catalog can be set up to ingest organizational data - users and -teams - directly from an organization in GitHub or GitHub Enterprise. The result -is a hierarchy of -[`User`](../../features/software-catalog/descriptor-format.md#kind-user) and -[`Group`](../../features/software-catalog/descriptor-format.md#kind-group) kind -entities that mirror your org setup. - -> Note: This adds `User` and `Group` entities to the catalog, but does not -> provide authentication. See the -> [GitHub auth provider](../../auth/github/provider.md) for that. - -## Installation without Events Support - -This guide will use the Entity Provider method. If you for some reason prefer -the Processor method (not recommended), it is described separately below. - -The provider is not installed by default, therefore you have to add a dependency -to `@backstage/plugin-catalog-backend-module-github` to your backend package. - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github -``` - -> Note: When configuring to use a Provider instead of a Processor you do not -> need to add a _location_ pointing to your GitHub server/organization - -Update the catalog plugin initialization in your backend to add the provider and -schedule it: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The org URL below needs to match a configured integrations.github entry - // specified in your app-config. - builder.addEntityProvider( - GithubOrgEntityProvider.fromConfig(env.config, { - id: 'production', - orgUrl: 'https://github.com/backstage', - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -Alternatively, if you wish to ingest data from multiple GitHub organizations you can use -the `GithubMultiOrgEntityProvider` instead. Note that by default, this provider will namespace -groups according to the org they originate from to avoid potential name duplicates: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The GitHub URL below needs to match a configured integrations.github entry - // specified in your app-config. - builder.addEntityProvider( - GithubMultiOrgEntityProvider.fromConfig(env.config, { - id: 'production', - githubUrl: 'https://github.com', - // Set the following to list the GitHub orgs you wish to ingest from. You can - // also omit this option to ingest all orgs accessible by your GitHub integration - orgs: ['org-a', 'org-b'], - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -## Installation with Events Support - -_For the legacy backend system, please read the subsection below._ - -The catalog module `github-org` comes with events support enabled for the `GithubMultiOrgEntityProvider`. -This will make it subscribe to its relevant topics and expects these events to be published via the `EventsService`. - -Topics: - -- `github.installation` -- `github.membership` -- `github.organization` -- `github.team` - -Additionally, you should install the -[event router by `events-backend-module-github`](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-github/README.md) -which will route received events from the generic topic `github` to more specific ones -based on the event type (e.g., `github.membership`). - -In order to receive Webhook events by GitHub, you have to decide how you want them -to be ingested into Backstage and published to its `EventsService`. -You can decide between the following options (extensible): - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -### Legacy Backend System - -Please follow the installation instructions at - -- -- - -Additionally, you need to decide how you want to receive events from external sources like - -- [via HTTP endpoint](https://github.com/backstage/backstage/tree/master/plugins/events-backend/README.md) -- [via an AWS SQS queue](https://github.com/backstage/backstage/tree/master/plugins/events-backend-module-aws-sqs/README.md) - -Set up your provider - -```ts title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { GithubOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; -import { ScaffolderEntitiesProcessor } from '@backstage/plugin-scaffolder-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - builder.addProcessor(new ScaffolderEntitiesProcessor()); - /* highlight-add-start */ - const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { - id: 'production', - orgUrl: 'https://github.com/backstage', - logger: env.logger, - events: env.events, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }); - builder.addEntityProvider(githubOrgProvider); - /* highlight-add-end */ - const { processingEngine, router } = await builder.build(); - await processingEngine.start(); - return router; -} -``` - -Or, alternatively, if using the `GithubMultiOrgEntityProvider`: - -```ts title="packages/backend/src/plugins/catalog.ts" -/* highlight-add-next-line */ -import { GithubMultiOrgEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - - /* highlight-add-start */ - // The GitHub URL below needs to match a configured integrations.github entry - // specified in your app-config. - builder.addEntityProvider( - GithubMultiOrgEntityProvider.fromConfig(env.config, { - id: 'production', - githubUrl: 'https://github.com', - // Set the following to list the GitHub orgs you wish to ingest from. You can - // also omit this option to ingest all orgs accessible by your GitHub integration - orgs: ['org-a', 'org-b'], - logger: env.logger, - events: env.events, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - }), - ); - /* highlight-add-end */ - - // .. -} -``` - -You can check the official docs to [configure your webhook](https://docs.github.com/en/developers/webhooks-and-events/webhooks/creating-webhooks) and to [secure your request](https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks). -The webhook will need to be configured to forward `organization`,`team` and `membership` events. - -## Configuration - -As mentioned above, you also must have some configuration in your app-config -that describes the targets that you want to import. This lets the entity -provider know what authorization to use, and what the API endpoints are. You may -or may not have such an entry already added since before: - -```yaml -integrations: - github: - # example for public github - - host: github.com - token: ${GITHUB_TOKEN} - # example for a private GitHub Enterprise instance - - host: ghe.example.net - apiBaseUrl: https://ghe.example.net/api/v3 - token: ${GHE_TOKEN} -``` - -These examples use `${}` placeholders to reference environment variables. This -is often suitable for production setups, but also means that you will have to -supply those variables to the backend as it starts up. If you want, for local -development in particular, you can experiment first by putting the actual tokens -in a mirrored config directly in your `app-config.local.yaml` as well. - -If Backstage is configured to use GitHub Apps authentication you must grant -`Read-Only` access for `Members` under `Organization` in order to ingest users -correctly. You can modify the app's permissions under the organization settings, -`https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions`. - -![permissions](../../assets/integrations/github/permissions.png) - -**Please note that when you change permissions, the app owner will get an email -that must be approved first before the changes are applied.** - -![email](../../assets/integrations/github/email.png) - -### Custom Transformers - -You can inject your own transformation logic to help map from GH API responses -into backstage entities. You can do this on the user and team requests to -enable you to do further processing or updates to the entities. - -To enable this you pass a function into the `GitHubOrgEntityProvider`. You can -pass a `UserTransformer`, `TeamTransformer` or both. The function is invoked -for each item (user or team) that is returned from the API. You can either -return an Entity (User or Group) or `undefined` if you do not want to import -that item. - -There is also a `defaultUserTransformer` and `defaultOrganizationTeamTransformer`. -You could use these and simply decorate the response from the default -transformation if you only need to change a few properties. - -### Resolving GitHub users via organization email - -When you authenticate users you should resolve them to an entity within the -catalog. Often the authentication you use could be a corporate SSO system that -provides you with email as a key. To enable you to find and resolve GitHub users -it's useful to also import the private domain verified emails into the User -entity in backstage. - -The integration attempts to return `organizationVerifiedDomainEmails` from the -GitHub API and makes this available as part of the object passed to -`UserTransformer`. The GitHub API will only return emails that use a domain -that's a verified domain for your GitHub Org. It also relies on the user having -configured such an email in their own account. The API will only return these -values when using GitHub App authentication and with the correct app permission -allowing access to emails. - -You can decorate the default `userTransformer` to replace the org email in the -returned identity. - -```ts title="packages/backend/src/plugins/catalog.ts" -const githubOrgProvider = GithubOrgEntityProvider.fromConfig(env.config, { - id: 'production', - orgUrl: 'https://github.com/backstage', - logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 60 }, - timeout: { minutes: 15 }, - }), - /* highlight-add-start */ - userTransformer: async (user, ctx) => { - const entity = await defaultUserTransformer(user, ctx); - if (entity && user.organizationVerifiedDomainEmails?.length) { - entity.spec.profile!.email = user.organizationVerifiedDomainEmails[0]; - } - return entity; - }, - /* highlight-add-end */ -}); -``` - -Once you have imported the emails you can resolve users in your [sign-in resolver](../../auth/github/provider.md) using the catalog entity search via email - -```typescript title="packages/backend/src/plugins/auth.ts" -ctx.signInWithCatalogUser({ - filter: { - kind: ['User'], - 'spec.profile.email': email as string, - }, -}); -``` - -## Using a Processor instead of a Provider - -An alternative to using the Provider for ingesting organizational entities is to -use a Processor. This is the old way that's based on registering locations with -the proper type and target, triggering the processor to run. - -The drawback of this method is that it will leave orphaned Group/User entities -whenever they are deleted on your GitHub server, and you cannot control the -frequency with which they are refreshed, separately from other processors. - -### Processor Installation - -The `GithubOrgReaderProcessor` is not registered by default, so you have to -install and register it in the catalog plugin: - -```bash title="From your Backstage root directory" -yarn --cwd packages/backend add @backstage/plugin-catalog-backend-module-github -``` - -```typescript title="packages/backend/src/plugins/catalog.ts" -import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend-module-github'; - -builder.addProcessor( - GithubOrgReaderProcessor.fromConfig(env.config, { logger: env.logger }), -); -``` - -### Processor Configuration - -The integration section of your app-config needs to be set up in the same way as -for the Entity Provider - see above. - -In addition to that, you typically want to add a few static locations to your -app-config, which reference your organizations to import. The following -configuration enables an import of the teams and users under the org -`https://github.com/my-org-name` on public GitHub. - -```yaml -catalog: - locations: - - type: github-org - target: https://github.com/my-org-name - rules: - - allow: [User, Group] -``` diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 7f0ee1a71f..3d0e0a4221 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -7,7 +7,7 @@ description: Importing users and groups from a GitHub organization into Backstag --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/integrations/github/org--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The Backstage catalog can be set up to ingest organizational data - users and diff --git a/docs/permissions/custom-rules--old.md b/docs/permissions/custom-rules--old.md deleted file mode 100644 index 7661e23327..0000000000 --- a/docs/permissions/custom-rules--old.md +++ /dev/null @@ -1,165 +0,0 @@ ---- -id: custom-rules--old -title: Defining custom permission rules -description: How to define custom permission rules for existing resources ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./custom-rules.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. - -## Define a custom rule - -Plugins should export a rule factory that provides type-safety that ensures compatibility with the plugin's backend. The catalog plugin exports `createCatalogPermissionRule` from `@backstage/plugin-catalog-backend/alpha` for this purpose. Note: the `/alpha` path segment is temporary until this API is marked as stable. For this example, we'll define the rule and create a condition in `packages/backend/src/plugins/permission.ts`. - -We use Zod in our example below. To install, run: - -```bash -yarn workspace backend add zod -``` - -```typescript title="packages/backend/src/plugins/permission.ts" -... - -import type { Entity } from '@backstage/catalog-model'; -import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; -import { createConditionFactory } from '@backstage/plugin-permission-node'; -import { z } from 'zod'; - -export const isInSystemRule = createCatalogPermissionRule({ - name: 'IS_IN_SYSTEM', - description: 'Checks if an entity is part of the system provided', - resourceType: 'catalog-entity', - paramsSchema: z.object({ - systemRef: z - .string() - .describe('SystemRef to check the resource is part of'), - }), - apply: (resource: Entity, { systemRef }) => { - if (!resource.relations) { - return false; - } - - return resource.relations - .filter(relation => relation.type === 'partOf') - .some(relation => relation.targetRef === systemRef); - }, - toQuery: ({ systemRef }) => ({ - key: 'relations.partOf', - values: [systemRef], - }), -}); - -const isInSystem = createConditionFactory(isInSystemRule); - -... -``` - -For a more detailed explanation on defining rules, refer to the [documentation for plugin authors](./plugin-authors/03-adding-a-resource-permission-check.md#adding-support-for-conditional-decisions). - -Still in the `packages/backend/src/plugins/permission.ts` file, let's use the condition we just created in our `TestPermissionPolicy`. - -```ts title="packages/backend/src/plugins/permission.ts" -... -/* highlight-remove-next-line */ -import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; -/* highlight-add-next-line */ -import { catalogConditions, createCatalogConditionalDecision, createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; -/* highlight-remove-next-line */ -import { createConditionFactory } from '@backstage/plugin-permission-node'; -/* highlight-add-next-line */ -import { PermissionPolicy, PolicyQuery, PolicyQueryUser, createConditionFactory } from '@backstage/plugin-permission-node'; -/* highlight-add-start */ -import { AuthorizeResult, PolicyDecision, isResourcePermission } from '@backstage/plugin-permission-common'; -/* highlight-add-end */ -... - -export const isInSystemRule = createCatalogPermissionRule({ - name: 'IS_IN_SYSTEM', - description: 'Checks if an entity is part of the system provided', - resourceType: 'catalog-entity', - paramsSchema: z.object({ - systemRef: z - .string() - .describe('SystemRef to check the resource is part of'), - }), - apply: (resource: Entity, { systemRef }) => { - if (!resource.relations) { - return false; - } - - return resource.relations - .filter(relation => relation.type === 'partOf') - .some(relation => relation.targetRef === systemRef); - }, - toQuery: ({ systemRef }) => ({ - key: 'relations.partOf', - values: [systemRef], - }), -}); - -const isInSystem = createConditionFactory(isInSystemRule); - -class TestPermissionPolicy implements PermissionPolicy { - async handle( - request: PolicyQuery, - user?: PolicyQueryUser, - ): Promise { - if (isResourcePermission(request.permission, 'catalog-entity')) { - return createCatalogConditionalDecision( - request.permission, - /* highlight-remove-start */ - catalogConditions.isEntityOwner({ - claims: user?.info.ownershipEntityRefs ?? [], - }), - /* highlight-remove-end */ - /* highlight-add-start */ - { - anyOf: [ - catalogConditions.isEntityOwner({ - claims: user?.info.ownershipEntityRefs ?? [], - }), - isInSystem({ systemRef: 'interviewing' }), - ], - }, - /* highlight-add-end */ - ); - } - - return { result: AuthorizeResult.ALLOW }; - } -} - -... -``` - -## Provide the rule during plugin setup - -Now that we have a custom rule defined and added to our policy, we need provide it to the catalog plugin. This step is important because the catalog plugin will use the rule's `toQuery` and `apply` methods while evaluating conditional authorize results. There's no guarantee that the catalog and permission backends are running on the same server, so we must explicitly link the rule to ensure that it's available at runtime. - -The api for providing custom rules may differ between plugins, but there should typically be some integration point during the creation of the backend router. For the catalog, this integration point is exposed via `CatalogBuilder.addPermissionRules`. - -```typescript title="packages/backend/src/plugins/catalog.ts" -import { CatalogBuilder } from '@backstage/plugin-catalog-backend'; -/* highlight-add-next-line */ -import { isInSystemRule } from './permission'; - -... - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - const builder = await CatalogBuilder.create(env); - /* highlight-add-next-line */ - builder.addPermissionRules(isInSystemRule); - ... - return router; -} -``` - -The updated policy will allow catalog entity resource permissions if any of the following are true: - -- User owns the target entity -- Target entity is part of the 'interviewing' system diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index bd35f5de8e..61b9a5326a 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -5,7 +5,7 @@ description: How to define custom permission rules for existing resources --- :::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./custom-rules--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/custom-rules--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: For some use cases, you may want to define custom [rules](../references/glossary.md#rule-permission-plugin) in addition to the ones provided by a plugin. In the [previous section](./writing-a-policy.md) we used the `isEntityOwner` rule to control access for catalog entities. Let's extend this policy with a custom rule that checks what [system](https://backstage.io/docs/features/software-catalog/system-model#system) an entity is part of. diff --git a/docs/permissions/getting-started--old.md b/docs/permissions/getting-started--old.md deleted file mode 100644 index 96dbfec92f..0000000000 --- a/docs/permissions/getting-started--old.md +++ /dev/null @@ -1,169 +0,0 @@ ---- -id: getting-started--old -title: Getting Started -description: How to get started with the permission framework as an integrator ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./getting-started.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -If you prefer to watch a video instead, you can start with this video introduction: - - - -:::note Note - -This video was recorded in the January 2022 Contributors Session using `@backstage/create-app@0.4.14`. Some aspects of the demo may have changed in later releases. - -::: - -Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. - -## Prerequisites - -The permissions framework depends on a few other Backstage systems, which must be set up before we can dive into writing a policy. - -### Upgrade to the latest version of Backstage - -The permissions framework itself is new to Backstage and still evolving quickly. To ensure your version of Backstage has all the latest permission-related functionality, it’s important to upgrade to the latest version. The [Backstage upgrade helper](https://backstage.github.io/upgrade-helper/) is a great tool to help ensure that you’ve made all the necessary changes during the upgrade! - -### Enable service-to-service authentication - -Service-to-service authentication allows Backstage backend code to verify that a given request originates from elsewhere in the Backstage backend. This is useful for tasks like collation of catalog entities in the search index. This type of request shouldn’t be permissioned, so it’s important to configure this feature before trying to use the permissions framework. - -To set up service-to-service authentication, follow the [service-to-service authentication docs](../auth/service-to-service-auth.md). - -### Supply an identity resolver to populate group membership on sign in - -**Note**: If you are working off of an existing Backstage instance, you likely already have some form of an identity resolver set up. - -Like many other parts of Backstage, the permissions framework relies on information about group membership. This simplifies authoring policies through the use of groups, rather than requiring each user to be listed in the configuration. Group membership is also often useful for conditional permissions, for example allowing permissions to act on an entity to be granted when a user is a member of a group that owns that entity. - -[The IdentityResolver docs](../auth/identity-resolver.md) describe the process for resolving group membership on sign in. - -## Optionally add cookie-based authentication - -Asset requests initiated by the browser will not include a token in the `Authorization` header. If these requests check authorization through the permission framework, as done in plugins like TechDocs, then you'll need to set up cookie-based authentication. Refer to the ["Authenticate API requests"](https://github.com/backstage/backstage/blob/master/contrib/docs/tutorials/authenticate-api-requests.md) tutorial for a demonstration on how to implement this behavior. - -## Integrating the permission framework with your Backstage instance - -### 1. Set up the permission backend - -The permissions framework uses a new `permission-backend` plugin to accept authorization requests from other plugins across your Backstage instance. The Backstage backend does not include this permission backend by default, so you will need to add it: - -1. Add `@backstage/plugin-permission-backend` as a dependency of your Backstage backend: - - ```bash title="From your Backstage root directory" - yarn --cwd packages/backend add @backstage/plugin-permission-backend - ``` - -2. Add the following to a new file, `packages/backend/src/plugins/permission.ts`. This adds the permission-backend router, and configures it with a policy which allows everything. - - ```typescript title="packages/backend/src/plugins/permission.ts" - import { createRouter } from '@backstage/plugin-permission-backend'; - import { - AuthorizeResult, - PolicyDecision, - } from '@backstage/plugin-permission-common'; - import { PermissionPolicy } from '@backstage/plugin-permission-node'; - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - class TestPermissionPolicy implements PermissionPolicy { - async handle(): Promise { - return { result: AuthorizeResult.ALLOW }; - } - } - - export default async function createPlugin( - env: PluginEnvironment, - ): Promise { - return await createRouter({ - config: env.config, - logger: env.logger, - discovery: env.discovery, - policy: new TestPermissionPolicy(), - identity: env.identity, - }); - } - ``` - -3. Wire up the permission policy in `packages/backend/src/index.ts`. [The index in the example backend](https://github.com/backstage/backstage/blob/master/packages/backend/src/index.ts) shows how to do this. You’ll need to import the module from the previous step, create a plugin environment, and add the router to the express app: - - ```ts title="packages/backend/src/index.ts" - import proxy from './plugins/proxy'; - import techdocs from './plugins/techdocs'; - import search from './plugins/search'; - /* highlight-add-next-line */ - import permission from './plugins/permission'; - - async function main() { - const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - /* highlight-add-next-line */ - const permissionEnv = useHotMemoize(module, () => createEnv('permission')); - // .. - - apiRouter.use('/techdocs', await techdocs(techdocsEnv)); - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/search', await search(searchEnv)); - /* highlight-add-next-line */ - apiRouter.use('/permission', await permission(permissionEnv)); - // .. - } - ``` - -### 2. Enable and test the permissions system - -Now that the permission backend is running, it’s time to enable the permissions framework and make sure it’s working properly. - -1. Set the property `permission.enabled` to `true` in `app-config.yaml`. - - ```yaml title="app-config.yaml" - permission: - enabled: true - ``` - -2. Update the PermissionPolicy in `packages/backend/src/plugins/permission.ts` to disable a permission that’s easy for us to test. This policy rejects any attempt to delete a catalog entity: - - ```ts title="packages/backend/src/plugins/permission.ts" - import { createRouter } from '@backstage/plugin-permission-backend'; - import { - AuthorizeResult, - PolicyDecision, - } from '@backstage/plugin-permission-common'; - /* highlight-remove-next-line */ - import { PermissionPolicy } from '@backstage/plugin-permission-node'; - /* highlight-add-start */ - import { - PermissionPolicy, - PolicyQuery, - } from '@backstage/plugin-permission-node'; - /* highlight-add-end */ - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - class TestPermissionPolicy implements PermissionPolicy { - /* highlight-remove-next-line */ - async handle(): Promise { - /* highlight-add-start */ - async handle(request: PolicyQuery): Promise { - if (request.permission.name === 'catalog.entity.delete') { - return { - result: AuthorizeResult.DENY, - }; - } - /* highlight-add-end */ - - return { result: AuthorizeResult.ALLOW }; - } - } - ``` - -3. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. - -![Entity detail page showing disabled unregister entity context menu entry](../assets/permissions/disabled-unregister-entity.png) - -Now that the framework is fully configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! diff --git a/docs/permissions/getting-started.md b/docs/permissions/getting-started.md index cb0d29ef82..f5b9092ec5 100644 --- a/docs/permissions/getting-started.md +++ b/docs/permissions/getting-started.md @@ -5,7 +5,7 @@ description: How to get started with the permission framework as an integrator --- :::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./getting-started--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/getting-started--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: Backstage integrators control permissions by writing a policy. In general terms, a policy is simply an async function which receives a request to authorize a specific action for a user and (optional) resource, and returns a decision on whether to authorize that permission. Integrators can implement their own policies from scratch, or adopt reusable policies written by others. diff --git a/docs/permissions/plugin-authors/01-setup--old.md b/docs/permissions/plugin-authors/01-setup--old.md deleted file mode 100644 index 34802d004f..0000000000 --- a/docs/permissions/plugin-authors/01-setup--old.md +++ /dev/null @@ -1,134 +0,0 @@ ---- -id: 01-setup--old -title: 1. Tutorial setup -description: How to get started with the permission framework as a plugin author ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./01-setup.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins! - -The rest of this page is focused on adding the `todo-list` and `todo-list-backend` plugins to your Backstage instance. If you want to add support for permissions to your own plugin instead, feel free to skip to the [next section](./02-adding-a-basic-permission-check.md). - -## Setup for the Tutorial - -We will use a "Todo list" feature, composed of the `todo-list` and `todo-list-backend` plugins, as well as their dependency, `todo-list-common`. - -The source code is available here: - -- [todo-list](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list) -- [todo-list-backend](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-backend) -- [todo-list-common](https://github.com/backstage/backstage/blob/master/plugins/example-todo-list-common) - -1. Copy-paste the three folders into the plugins folder of your backstage application repository (removing the `example-` prefix from each folder) or run the following script from the root of your backstage application: - - ```bash - $ cd $(mktemp -d) - git clone --depth 1 --quiet --no-checkout --filter=blob:none https://github.com/backstage/backstage.git . - git checkout master -- plugins/example-todo-list/ - git checkout master -- plugins/example-todo-list-backend/ - git checkout master -- plugins/example-todo-list-common/ - sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list/package.json - sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-backend/package.json - sed -i '' 's/workspace:\^/\*/g' plugins/example-todo-list-common/package.json - for file in plugins/*; do mv "$file" "$OLDPWD/${file/example-todo/todo}"; done - cd - - ``` - - The `plugins` directory of your project should now include `todo-list`, `todo-list-backend`, and `todo-list-common`. - - **Important**: if you are on **Windows**, make sure you have WSL and git installed on your machine before executing the script above. - -2. Add these packages as dependencies for your Backstage app: - - ```sh title="From your Backstage root directory" - yarn --cwd packages/backend add @internal/plugin-todo-list-backend @internal/plugin-todo-list-common - yarn --cwd packages/app add @internal/plugin-todo-list - ``` - -3. Include the backend and frontend plugin in your application: - - Create a new `packages/backend/src/plugins/todolist.ts` with the following content: - - ```typescript title="packages/backend/src/plugins/todolist.ts" - import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; - import { createRouter } from '@internal/plugin-todo-list-backend'; - import { Router } from 'express'; - import { PluginEnvironment } from '../types'; - - export default async function createPlugin({ - logger, - discovery, - }: PluginEnvironment): Promise { - return await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - }); - } - ``` - - Apply the following changes to `packages/backend/src/index.ts`: - - ```ts title="packages/backend/src/index.ts" - import techdocs from './plugins/techdocs'; - /* highlight-add-next-line */ - import todoList from './plugins/todolist'; - import search from './plugins/search'; - - async function main() { - const searchEnv = useHotMemoize(module, () => createEnv('search')); - const appEnv = useHotMemoize(module, () => createEnv('app')); - /* highlight-add-next-line */ - const todoListEnv = useHotMemoize(module, () => createEnv('todolist')); - // .. - - apiRouter.use('/proxy', await proxy(proxyEnv)); - apiRouter.use('/search', await search(searchEnv)); - apiRouter.use('/permission', await permission(permissionEnv)); - /* highlight-add-next-line */ - apiRouter.use('/todolist', await todoList(todoListEnv)); - // Add backends ABOVE this line; this 404 handler is the catch-all fallback - apiRouter.use(notFoundHandler()); - // .. - } - ``` - - Apply the following changes to `packages/app/src/App.tsx`: - - ```tsx title="packages/app/src/App.tsx" - /* highlight-add-next-line */ - import { TodoListPage } from '@internal/plugin-todo-list'; - - const routes = ( - - }> - {searchPage} - - } /> - {/* highlight-add-next-line */} - } /> - {/* ... */} - - ); - ``` - -Now if you start your application you should be able to reach the `/todo-list` page: - -![Todo List plugin page](../../assets/permissions/permission-todo-list-page.png) - ---- - -## Integrate the new plugin - -If you play with the UI, you will notice that it is possible to perform a few actions: - -- create a new todo item (`POST /todos`) -- view todo items (`GET /todos`) -- edit an existing todo item (`PUT /todos`) - -Let's try to bring authorization on top of each one of them. diff --git a/docs/permissions/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md index 6ece0b0b69..743c5c9718 100644 --- a/docs/permissions/plugin-authors/01-setup.md +++ b/docs/permissions/plugin-authors/01-setup.md @@ -5,7 +5,7 @@ description: How to get started with the permission framework as a plugin author --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./01-setup--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/01-setup--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: The following tutorial is designed to help plugin authors add support for permissions to their plugins. We'll add support for permissions to example `todo-list` and `todo-list-backend` plugins, but the process should be similar for other plugins! diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md deleted file mode 100644 index 7c8dad3d87..0000000000 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md +++ /dev/null @@ -1,387 +0,0 @@ ---- -id: 02-adding-a-basic-permission-check--old -title: 2. Adding a basic permission check -description: Explains how to add a basic permission check to a Backstage plugin ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./02-adding-a-basic-permission-check.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it. - -For this tutorial, we'll use a basic permission check to authorize the `create` endpoint in our todo-backend. This will allow Backstage integrators to control whether each of their users is authorized to create todos by adjusting their [permission policy](../../references/glossary.md#policy-permission-plugin). - -We'll start by creating a new permission, and then we'll use the permission api to call `authorize` with it during todo creation. - -## Creating a new permission - -Let's navigate to the file `plugins/todo-list-common/src/permissions.ts` and add our first permission: - -```ts title="plugins/todo-list-common/src/permissions.ts" -import { createPermission } from '@backstage/plugin-permission-common'; - -/* highlight-remove-start */ -export const tempExamplePermission = createPermission({ - name: 'temp.example.noop', - attributes: {}, -/* highlight-remove-end */ -/* highlight-add-start */ -export const todoListCreatePermission = createPermission({ - name: 'todo.list.create', - attributes: { action: 'create' }, -/* highlight-add-end */ -}); - -/* highlight-remove-next-line */ -export const todoListPermissions = [tempExamplePermission]; -/* highlight-add-next-line */ -export const todoListPermissions = [todoListCreatePermission]; -``` - -For this tutorial, we've automatically exported all permissions from this file (see `plugins/todo-list-common/src/index.ts`). - -:::note Note - -We use a separate `todo-list-common` package since all permissions authorized by your plugin should be exported from a ["common-library" package](https://backstage.io/docs/tooling/cli/build-system#package-roles). This allows Backstage integrators to reference them in frontend components as well as permission policies. - -::: - -## Authorizing using the new permission - -Install the following module: - -``` -$ yarn workspace @internal/plugin-todo-list-backend \ - add @backstage/plugin-permission-common @backstage/plugin-permission-node @internal/plugin-todo-list-common -``` - -Edit `plugins/todo-list-backend/src/service/router.ts`: - -```ts title="plugins/todo-list-backend/src/service/router.ts" -/* highlight-remove-start */ -import { InputError } from '@backstage/errors'; -import { IdentityApi } from '@backstage/plugin-auth-node'; -/* highlight-remove-end */ -/* highlight-add-start */ -import { InputError, NotAllowedError } from '@backstage/errors'; -import { getBearerTokenFromAuthorizationHeader, IdentityApi } from '@backstage/plugin-auth-node'; -import { PermissionEvaluator, AuthorizeResult } from '@backstage/plugin-permission-common'; -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; -import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; -/* highlight-add-end */ - -export interface RouterOptions { - logger: Logger; - identity: IdentityApi; - /* highlight-add-next-line */ - permissions: PermissionEvaluator; -} - -export async function createRouter( - options: RouterOptions, -): Promise { - /* highlight-remove-next-line */ - const { logger, identity } = options; - /* highlight-add-next-line */ - const { logger, identity, permissions } = options; - - /* highlight-add-start */ - const permissionIntegrationRouter = createPermissionIntegrationRouter({ - permissions: [todoListCreatePermission], - }); - /* highlight-add-end */ - - const router = Router(); - router.use(express.json()); - - router.get('/health', (_, response) => { - logger.info('PONG!'); - response.json({ status: 'ok' }); - }); - - /* highlight-add-next-line */ - router.use(permissionIntegrationRouter); - - router.get('/todos', async (_req, res) => { - res.json(getAll()); - }); - - router.post('/todos', async (req, res) => { - let author: string | undefined = undefined; - - const user = await identity.getIdentity({ request: req }); - author = user?.identity.userEntityRef; - /* highlight-add-start */ - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); - const decision = ( - await permissions.authorize([{ permission: todoListCreatePermission }], { - token, - }) - )[0]; - - if (decision.result === AuthorizeResult.DENY) { - throw new NotAllowedError('Unauthorized'); - } - /* highlight-add-end */ - - if (!isTodoCreateRequest(req.body)) { - throw new InputError('Invalid payload'); - } - - const todo = add({ title: req.body.title, author }); - res.json(todo); - }); - - // ... -``` - -Pass the `permissions` object to the plugin in `packages/backend/src/plugins/todolist.ts`: - -```ts title="packages/backend/src/plugins/todolist.ts" -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -import { createRouter } from '@internal/plugin-todo-list-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; - -export default async function createPlugin({ - logger, - discovery, - /* highlight-add-next-line */ - permissions, -}: PluginEnvironment): Promise { - return await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - /* highlight-add-next-line */ - permissions, - }); -} -``` - -That's it! Now your plugin is fully configured. Let's try to test the logic by denying the permission. - -## Test the authorized create endpoint - -Before running this step, please make sure you followed the steps described in [Getting started](../getting-started.md) section. - -In order to test the logic above, the integrators of your backstage instance need to change their permission policy to return `DENY` for our newly-created permission: - -```ts title="packages/backend/src/plugins/permission.ts" -import { - PermissionPolicy, - /* highlight-add-start */ - PolicyQuery, - PolicyQueryUser, - /* highlight-add-end */ -} from '@backstage/plugin-permission-node'; -/* highlight-add-start */ -import { isPermission } from '@backstage/plugin-permission-common'; -import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; -/* highlight-add-end */ - -class TestPermissionPolicy implements PermissionPolicy { - /* highlight-remove-next-line */ - async handle(): Promise { - /* highlight-add-start */ - async handle( - request: PolicyQuery, - _user?: PolicyQueryUser, - ): Promise { - if (isPermission(request.permission, todoListCreatePermission)) { - return { - result: AuthorizeResult.DENY, - }; - } - /* highlight-add-end */ - - return { - result: AuthorizeResult.ALLOW, - }; -} -``` - -Now the frontend should show an error whenever you try to create a new Todo item. - -Let's flip the result back to `ALLOW` before moving on. - -```ts -if (isPermission(request.permission, todoListCreatePermission)) { - return { - /* highlight-remove-next-line */ - result: AuthorizeResult.DENY, - /* highlight-add-next-line */ - result: AuthorizeResult.ALLOW, - }; -} -``` - -At this point everything is working but if you run `yarn tsc` you'll get some errors, let's fix those up. - -First we'll clean up the `plugins/todo-list-backend/src/service/router.test.ts`: - -```ts title="plugins/todo-list-backend/src/service/router.test.ts" -import { getVoidLogger } from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -/* highlight-add-next-line */ -import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import express from 'express'; -import request from 'supertest'; - -import { createRouter } from './router'; - -/* highlight-add-start */ -const mockedAuthorize: jest.MockedFunction = - jest.fn(); -const mockedPermissionQuery: jest.MockedFunction< - PermissionEvaluator['authorizeConditional'] -> = jest.fn(); - -const permissionEvaluator: PermissionEvaluator = { - authorize: mockedAuthorize, - authorizeConditional: mockedPermissionQuery, -}; -/* highlight-add-end */ - -describe('createRouter', () => { - let app: express.Express; - - beforeAll(async () => { - const router = await createRouter({ - logger: getVoidLogger(), - identity: {} as DefaultIdentityClient, - /* highlight-add-next-line */ - permissions: permissionEvaluator, - }); - app = express().use(router); - }); - - beforeEach(() => { - jest.resetAllMocks(); - }); - - describe('GET /health', () => { - it('returns ok', async () => { - const response = await request(app).get('/health'); - - expect(response.status).toEqual(200); - expect(response.body).toEqual({ status: 'ok' }); - }); - }); -}); -``` - -Then we want to update the `plugins/todo-list-backend/src/service/standaloneServer.ts`: - -```ts title="plugins/todo-list-backend/src/service/standaloneServer.ts" -import { - createServiceBuilder, - loadBackendConfig, - SingleHostDiscovery, - /* highlight-add-next-line */ - ServerTokenManager, -} from '@backstage/backend-common'; -import { DefaultIdentityClient } from '@backstage/plugin-auth-node'; -/* highlight-add-next-line */ -import { ServerPermissionClient } from '@backstage/plugin-permission-node'; -import { Server } from 'http'; -import { Logger } from 'winston'; -import { createRouter } from './router'; - -export interface ServerOptions { - port: number; - enableCors: boolean; - logger: Logger; -} - -export async function startStandaloneServer( - options: ServerOptions, -): Promise { - const logger = options.logger.child({ service: 'todo-list-backend' }); - logger.debug('Starting application server...'); - const config = await loadBackendConfig({ logger, argv: process.argv }); - const discovery = SingleHostDiscovery.fromConfig(config); - /* highlight-add-start */ - const tokenManager = ServerTokenManager.fromConfig(config, { - logger, - }); - const permissions = ServerPermissionClient.fromConfig(config, { - discovery, - tokenManager, - }); - /* highlight-add-end */ - const router = await createRouter({ - logger, - identity: DefaultIdentityClient.create({ - discovery, - issuer: await discovery.getExternalBaseUrl('auth'), - }), - /* highlight-add-next-line */ - permissions, - }); - - let service = createServiceBuilder(module) - .setPort(options.port) - .addRouter('/todo-list', router); - if (options.enableCors) { - service = service.enableCors({ origin: 'http://localhost:3000' }); - } - - return await service.start().catch(err => { - logger.error(err); - process.exit(1); - }); -} - -module.hot?.accept(); -``` - -Finally, we need to update `plugins/todo-list-backend/src/plugin.ts`: - -```ts title="plugins/todo-list-backend/src/plugin.ts" -import { loggerToWinstonLogger } from '@backstage/backend-common'; -import { - coreServices, - createBackendPlugin, -} from '@backstage/backend-plugin-api'; -import { createRouter } from './service/router'; - -/** -* The example TODO list backend plugin. -* -* @public -*/ -export const exampleTodoListPlugin = createBackendPlugin({ - pluginId: 'exampleTodoList', - register(env) { - env.registerInit({ - deps: { - identity: coreServices.identity, - logger: coreServices.logger, - httpRouter: coreServices.httpRouter, - /* highlight-add-next-line */ - permissions: coreServices.permissions, - }, - /* highlight-remove-next-line */ - async init({ identity, logger, httpRouter }) { - /* highlight-add-next-line */ - async init({ identity, logger, httpRouter, permissions }) { - httpRouter.use( - await createRouter({ - identity, - logger: loggerToWinstonLogger(logger), - permissions, - }), - ); - }, - }); - }, -}); -``` - -Now when you run `yarn tsc` you should have no more errors. diff --git a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md index 4053a81f99..8ba3ac3152 100644 --- a/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md +++ b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md @@ -5,7 +5,7 @@ description: Explains how to add a basic permission check to a Backstage plugin --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./02-adding-a-basic-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/02-adding-a-basic-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: If the outcome of a permission check doesn't need to change for different [resources](../../references/glossary.md#resource-permission-plugin), you can use a _basic permission check_. For this kind of check, we simply need to define a permission, and call `authorize` with it. diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md deleted file mode 100644 index ee06629e71..0000000000 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md +++ /dev/null @@ -1,299 +0,0 @@ ---- -id: 03-adding-a-resource-permission-check--old -title: 3. Adding a resource permission check -description: Explains how to add a resource permission check to a Backstage plugin ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./03-adding-a-resource-permission-check.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise. - -## Creating the update permission - -Let's add a new permission to the file `plugins/todo-list-common/src/permissions.ts` from [the previous section](./02-adding-a-basic-permission-check.md). - -```ts title="plugins/todo-list-common/src/permissions.ts" -import { createPermission } from '@backstage/plugin-permission-common'; - -/* highlight-add-next-line */ -export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; - -export const todoListCreatePermission = createPermission({ - name: 'todo.list.create', - attributes: { action: 'create' }, -}); - -/* highlight-add-start */ -export const todoListUpdatePermission = createPermission({ - name: 'todo.list.update', - attributes: { action: 'update' }, - resourceType: TODO_LIST_RESOURCE_TYPE, -}); -/* highlight-add-end */ - -/* highlight-remove-next-line */ -export const todoListPermissions = [todoListCreatePermission]; -/* highlight-add-start */ -export const todoListPermissions = [ - todoListCreatePermission, - todoListUpdatePermission, -]; -/* highlight-add-end */ -``` - -Notice that unlike `todoListCreatePermission`, the `todoListUpdatePermission` permission contains a `resourceType` field. This field indicates to the permission framework that this permission is intended to be authorized in the context of a resource with type `'todo-item'`. You can use whatever string you like as the resource type, as long as you use the same value consistently for each type of resource. - -## Setting up authorization for the update permission - -To start, let's edit `plugins/todo-list-backend/src/service/router.ts` in the same manner as we did in the previous section: - -```ts title="plugins/todo-list-backend/src/service/router.ts" -/* highlight-remove-next-line */ -import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; -/* highlight-add-start */ -import { - todoListCreatePermission, - todoListUpdatePermission, -} from '@internal/plugin-todo-list-common'; -/* highlight-add-end */ - -// ... - -const permissionIntegrationRouter = createPermissionIntegrationRouter({ - /* highlight-remove-next-line */ - permissions: [todoListCreatePermission], - /* highlight-add-next-line */ - permissions: [todoListCreatePermission, todoListUpdatePermission], -}); - -// ... - -router.put('/todos', async (req, res) => { - /* highlight-add-start */ - const token = getBearerTokenFromAuthorizationHeader( - req.header('authorization'), - ); - /* highlight-add-end */ - - if (!isTodoUpdateRequest(req.body)) { - throw new InputError('Invalid payload'); - } - /* highlight-add-start */ - const decision = ( - await permissions.authorize( - [{ permission: todoListUpdatePermission, resourceRef: req.body.id }], - { - token, - }, - ) - )[0]; - - if (decision.result !== AuthorizeResult.ALLOW) { - throw new NotAllowedError('Unauthorized'); - } - /* highlight-add-end */ - - res.json(update(req.body)); -}); -``` - -**Important:** Notice that we are passing an extra `resourceRef` field, with the `id` of the todo item as the value. - -This enables decisions based on characteristics of the resource, but it's important to note that policy authors will not have access to the resource ref inside of their permission policies. Instead, the policies will return conditional decisions, which we need to now support in our plugin. - -## Adding support for conditional decisions - -Install the missing module: - -```bash -$ yarn workspace @internal/plugin-todo-list-backend add zod -``` - -Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: - -```typescript title="plugins/todo-list-backend/src/service/rules.ts" -import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; -import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; -import { z } from 'zod'; -import { Todo, TodoFilter } from './todos'; - -export const createTodoListPermissionRule = makeCreatePermissionRule< - Todo, - TodoFilter, - typeof TODO_LIST_RESOURCE_TYPE ->(); - -export const isOwner = createTodoListPermissionRule({ - name: 'IS_OWNER', - description: 'Should allow only if the todo belongs to the user', - resourceType: TODO_LIST_RESOURCE_TYPE, - paramsSchema: z.object({ - userId: z.string().describe('User ID to match on the resource'), - }), - apply: (resource: Todo, { userId }) => { - return resource.author === userId; - }, - toQuery: ({ userId }) => { - return { - property: 'author', - values: [userId], - }; - }, -}); - -export const rules = { isOwner }; -``` - -`makeCreatePermissionRule` is a helper used to ensure that rules created for this plugin use consistent types for the resource and query. - -:::note Note - -To support custom rules defined by Backstage integrators, you must export `createTodoListPermissionRule` from the backend package and provide some way for custom rules to be passed in before the backend starts, likely via `createRouter`. - -::: - -We have created a new `isOwner` rule, which is going to be automatically used by the permission framework whenever a conditional response is returned in response to an authorized request with an attached `resourceRef`. -Specifically, the `apply` function is used to understand whether the passed resource should be authorized or not. - -Let's skip the `toQuery` function for now, we'll come back to that in the next section. - -Now, let's create the new endpoint by editing `plugins/todo-list-backend/src/service/router.ts`. This uses the `createPermissionIntegrationRouter` helper to add the APIs needed by the permission framework to your plugin. You'll need to supply: - -- `getResources`: a function that accepts an array of `resourceRefs` in the same format you expect to be passed to `authorize`, and returns an array of the corresponding resources. -- `resourceType`: the same value used in the permission rule above. -- `permissions`: the list of permissions that your plugin accepts. -- `rules`: an array of all the permission rules you want to support in conditional decisions. - -```ts title="plugins/todo-list-backend/src/service/router.ts" -// ... -import { - /* highlight-add-next-line */ - TODO_LIST_RESOURCE_TYPE, - todoListCreatePermission, - todoListUpdatePermission, -} from '@internal/plugin-todo-list-common'; -/* highlight-remove-next-line */ -import { add, getAll, update } from './todos'; -/* highlight-add-start */ -import { add, getAll, getTodo, update } from './todos'; -import { rules } from './rules'; -/* highlight-add-end */ - -export async function createRouter( - options: RouterOptions, -): Promise { - const { logger, identity, permissions } = options; - - const permissionIntegrationRouter = createPermissionIntegrationRouter({ - permissions: [todoListCreatePermission, todoListUpdatePermission], - /* highlight-add-start */ - getResources: async resourceRefs => { - return resourceRefs.map(getTodo); - }, - resourceType: TODO_LIST_RESOURCE_TYPE, - rules: Object.values(rules), - /* highlight-add-end */ - }); - - const router = Router(); - router.use(express.json()); - - // ... -} -``` - -## Provide utilities for policy authors - -Now that we have a new resource type and a corresponding rule, we need to export some utilities for policy authors to reference them. - -Create a new `plugins/todo-list-backend/src/conditionExports.ts` file and add the following code: - -```typescript title="plugins/todo-list-backend/src/conditionExports.ts" -import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; -import { createConditionExports } from '@backstage/plugin-permission-node'; -import { rules } from './service/rules'; - -const { conditions, createConditionalDecision } = createConditionExports({ - pluginId: 'todolist', - resourceType: TODO_LIST_RESOURCE_TYPE, - rules, -}); - -export const todoListConditions = conditions; - -export const createTodoListConditionalDecision = createConditionalDecision; -``` - -Make sure `todoListConditions` and `createTodoListConditionalDecision` are exported from the `todo-list-backend` package by editing `plugins/todo-list-backend/src/index.ts`: - -```ts title="plugins/todo-list-backend/src/index.ts" -export * from './service/router'; -/* highlight-add-next-line */ -export * from './conditionExports'; -export { exampleTodoListPlugin } from './plugin'; -``` - -## Test the authorized update endpoint - -Let's go back to the permission policy's handle function and try to authorize our new permission with an `isOwner` condition. - -```ts title="packages/backend/src/plugins/permission.ts" -import { - IdentityClient -} from '@backstage/plugin-auth-node'; -import { - PermissionPolicy, - PolicyQuery, - PolicyQueryUser, -} from '@backstage/plugin-permission-node'; -import { isPermission } from '@backstage/plugin-permission-common'; -/* highlight-remove-next-line */ -import { todoListCreatePermission } from '@internal/plugin-todo-list-common'; -/* highlight-add-start */ -import { - todoListCreatePermission, - todoListUpdatePermission, -} from '@internal/plugin-todo-list-common'; -import { - todoListConditions, - createTodoListConditionalDecision, -} from '@internal/plugin-todo-list-backend'; -/* highlight-add-end */ - - -async handle( - request: PolicyQuery, - /* highlight-remove-next-line */ - _user?: PolicyQueryUser, - /* highlight-add-next-line */ - user?: PolicyQueryUser, -): Promise { - if (isPermission(request.permission, todoListCreatePermission)) { - return { - result: AuthorizeResult.ALLOW, - }; - } - /* highlight-add-start */ - if (isPermission(request.permission, todoListUpdatePermission)) { - return createTodoListConditionalDecision( - request.permission, - todoListConditions.isOwner({ - userId: user?.info.userEntityRef ?? '', - }), - ); - } - /* highlight-add-end */ - - return { - result: AuthorizeResult.ALLOW, - }; -} -``` - -For any incoming update requests, we now return a _Conditional Decision_. We are saying: - -> Hey permission framework, I can't make a decision alone. Please go to the plugin with id `todolist` and ask it to apply these conditions. - -To check that everything works as expected, you should now see an error in the UI whenever you try to edit an item that wasn’t created by you. Success! diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index dd8e88bb94..afa56307ce 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -5,7 +5,7 @@ description: Explains how to add a resource permission check to a Backstage plug --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./03-adding-a-resource-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/03-adding-a-resource-permission-check--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: When performing updates (or other operations) on specific [resources](../../references/glossary.md#resource-permission-plugin), the permissions framework allows for the decision to be based on characteristics of the resource itself. This means that it's possible to write policies that (for example) allow the operation for users that own a resource, and deny the operation otherwise. @@ -125,7 +125,10 @@ $ yarn workspace @internal/plugin-todo-list-backend add zod Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append the following code: ```typescript title="plugins/todo-list-backend/src/service/rules.ts" -import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; +import { + createPermissionResourceRef, + createPermissionRule, +} from '@backstage/plugin-permission-node'; import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; import { z } from 'zod'; import { Todo, TodoFilter } from './todos'; @@ -135,13 +138,13 @@ export const todoListPermissionResourceRef = createPermissionResourceRef< TodoFilter >().with({ pluginId: 'todolist', - type: TODO_LIST_RESOURCE_TYPE, + resourceType: TODO_LIST_RESOURCE_TYPE, }); export const isOwner = createPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the todo belongs to the user', - resourceType: todoListPermissionResourceRef, + resourceRef: todoListPermissionResourceRef, paramsSchema: z.object({ userId: z.string().describe('User ID to match on the resource'), }), diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md deleted file mode 100644 index cca6899ef6..0000000000 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md +++ /dev/null @@ -1,193 +0,0 @@ ---- -id: 04-authorizing-access-to-paginated-data--old -title: 4. Authorizing access to paginated data -description: Explains how to authorize access to paginated data in a Backstage plugin ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../../backend-system/index.md), being the default since Backstage [version 1.24](../../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./04-authorizing-access-to-paginated-data.md) instead. Otherwise, [consider migrating](../../backend-system/building-backends/08-migrating.md)! -::: - -Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint. - -One possible solution may leverage the batching functionality to authorize all of the todos, and then returning only the ones for which the decision was `ALLOW`: - -```ts -router.get('/todos', async (req, res) => { - /* highlight-add-next-line */ - const credentials = await httpAuth.credentials(req, { allow: ['user'] }); - - /* highlight-remove-next-line */ - res.json(getAll()); - /* highlight-add-start */ - const items = getAll(); - const decisions = await permissions.authorize( - items.map(({ id }) => ({ - permission: todoListReadPermission, - resourceRef: id, - })), - { credentials }, - ); - - const filteredItems = decisions.filter( - decision => decision.result === AuthorizeResult.ALLOW, - ); - res.json(filteredItems); - /* highlight-add-end */ -}); -``` - -This approach will work for simple cases, but it has a downside: it forces us to retrieve all the elements upfront and authorize them one by one. This forces the plugin implementation to handle concerns like pagination, which is currently handled by the data source. - -To avoid this situation, the permissions framework has support for filtering items in the data source itself. In this part of the tutorial, we'll describe the steps required to use that behavior. - -:::note Note - -In order to perform authorization filtering in this way, the data source must allow filters to be logically combined with AND, OR, and NOT operators. The conditional decisions returned by the permissions framework use a [nested object](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) to combine conditions. If you're implementing a filter API from scratch, we recommend using the same shape for ease of interoperability. If not, you'll need to implement a function which transforms the nested object into your own format. - -::: - -## Creating the read permission - -Let's add another permission to the plugin. - -```ts title="plugins/todo-list-backend/src/service/permissions.ts" -import { createPermission } from '@backstage/plugin-permission-common'; - -export const TODO_LIST_RESOURCE_TYPE = 'todo-item'; - -export const todoListCreatePermission = createPermission({ - name: 'todo.list.create', - attributes: { action: 'create' }, -}); - -export const todoListUpdatePermission = createPermission({ - name: 'todo.list.update', - attributes: { action: 'update' }, - resourceType: TODO_LIST_RESOURCE_TYPE, -}); - -/* highlight-add-start */ -export const todoListReadPermission = createPermission({ - name: 'todos.list.read', - attributes: { action: 'read' }, - resourceType: TODO_LIST_RESOURCE_TYPE, -}); -/* highlight-add-end */ - -export const todoListPermissions = [ - todoListCreatePermission, - todoListUpdatePermission, - /* highlight-add-start */ - todoListReadPermission, - /* highlight-add-end */ -]; -``` - -## Using conditional policy decisions - -So far we've only used the `PermissionsService.authorize` method, which will evaluate conditional decisions before returning a result. In this step, we want to evaluate conditional decisions within our plugin, so we'll use `PermissionsService.authorizeConditional` instead. - -```ts title="plugins/todo-list-backend/src/service/router.ts" -/* highlight-remove-next-line */ -import { createPermissionIntegrationRouter } from '@backstage/plugin-permission-node'; -/* highlight-add-start */ -import { - createPermissionIntegrationRouter, - createConditionTransformer, - ConditionTransformer, -} from '@backstage/plugin-permission-node'; -/* highlight-add-end */ -/* highlight-remove-next-line */ -import { add, getAll, getTodo, update } from './todos'; -/* highlight-add-next-line */ -import { add, getAll, getTodo, TodoFilter, update } from './todos'; -import { - TODO_LIST_RESOURCE_TYPE, - todoListCreatePermission, - todoListUpdatePermission, - /* highlight-add-next-line */ - todoListReadPermission, -} from './permissions'; - -// ... - -const permissionIntegrationRouter = createPermissionIntegrationRouter({ - /* highlight-remove-next-line */ - permissions: [todoListCreatePermission, todoListUpdatePermission], - /* highlight-add-next-line */ - permissions: [todoListCreatePermission, todoListUpdatePermission, todoListReadPermission], - getResources: async resourceRefs => { - return resourceRefs.map(getTodo); - }, - resourceType: TODO_LIST_RESOURCE_TYPE, - rules: Object.values(rules), -}); - -// ... - -/* highlight-add-next-line */ -const transformConditions: ConditionTransformer = createConditionTransformer(Object.values(rules)); - -/* highlight-remove-next-line */ -router.get('/todos', async (_req, res) => { -/* highlight-add-start */ -router.get('/todos', async (req, res) => { - const credentials = await httpAuth.credentials(req, { allow: ['user'] }); - - const decision = ( - await permissions.authorizeConditional([{ permission: todoListReadPermission }], { - credentials, - }) - )[0]; - - if (decision.result === AuthorizeResult.DENY) { - throw new NotAllowedError('Unauthorized'); - } - - if (decision.result === AuthorizeResult.CONDITIONAL) { - const filter = transformConditions(decision.conditions); - res.json(getAll(filter)); - } else { - res.json(getAll()); - } -/* highlight-add-end */ - /* highlight-remove-next-line */ - res.json(getAll()); -}); -``` - -To make the process of handling conditional decisions easier, the permission framework provides a `createConditionTransformer` helper. This function accepts an array of permission rules, and returns a transformer function which converts the conditions to the format needed by the plugin using the `toQuery` method defined on each rule. - -Since `TodoFilter` used in our plugin matches the structure of the conditions object, we can directly pass the output of our condition transformer. If the filters were structured differently, we'd need to transform it further before passing it to the api. - -## Test the authorized read endpoint - -Let's update our permission policy to return a conditional result whenever a `todoListReadPermission` permission is received. In this case, we can reuse the decision returned for the `todosListCreate` permission. - -```ts title="packages/backend/src/plugins/permission.ts" -import { - todoListCreatePermission, - todoListUpdatePermission, - /* highlight-add-next-line */ - todoListReadPermission, -} from '@internal/plugin-todo-list-common'; - -/* highlight-remove-next-line */ -if (isPermission(request.permission, todoListUpdatePermission)) { -/* highlight-add-start */ -if ( - isPermission(request.permission, todoListUpdatePermission) || - isPermission(request.permission, todoListReadPermission) -) { -/* highlight-add-end */ - return createTodoListConditionalDecision( - request.permission, - todoListConditions.isOwner({ - userId: user?.identity.userEntityRef - }), - ); -} -``` - -Once the changes to the permission policy are saved, the UI should show only the todo items you've created. diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index c03183bbf0..b81382ee16 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -5,7 +5,7 @@ description: Explains how to authorize access to paginated data in a Backstage p --- :::info -This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./04-authorizing-access-to-paginated-data--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../../backend-system/index.md) which is the default since Backstage [version 1.24](../../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data--old.md) instead, and [consider migrating](../../backend-system/building-backends/08-migrating.md)! ::: Authorizing `GET /todos` is similar to the update endpoint, in that it should be possible to authorize access based on the characteristics of each resource. However, we'll need to authorize a list of resources for this endpoint. diff --git a/docs/permissions/writing-a-policy--old.md b/docs/permissions/writing-a-policy--old.md deleted file mode 100644 index d95fa7ffb3..0000000000 --- a/docs/permissions/writing-a-policy--old.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -id: writing-a-policy--old -title: Writing a permission policy -description: How to write your own permission policy as a Backstage integrator ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./writing-a-policy.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly. - -That policy looked like this: - -```typescript title="packages/backend/src/plugins/permission.ts" -class TestPermissionPolicy implements PermissionPolicy { - async handle( - request: PolicyQuery, - _user?: PolicyQueryUser, - ): Promise { - if (request.permission.name === 'catalog.entity.delete') { - return { - result: AuthorizeResult.DENY, - }; - } - - return { result: AuthorizeResult.ALLOW }; - } -} -``` - -## What's in a policy? - -Let's break this down a bit further. The request object of type [PolicyQuery](https://backstage.io/docs/reference/plugin-permission-node.policyquery) is a simple wrapper around [the Permission object](https://backstage.io/docs/reference/plugin-permission-common.permission). This permission object encapsulates information about the action that the user is attempting to perform (See [the Concepts page](./concepts.md) for more details). - -In the policy above, we are checking to see if the provided action is a catalog entity delete action, which is the permission that the catalog plugin authors have created to represent the action of unregistering a catalog entity. If this is the case, we return a [Definitive Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.definitivepolicydecision) of DENY. In all other cases, we return ALLOW (resulting in an allow-by-default behavior). - -As we confirmed in the previous section, we know that this now prevents us from unregistering catalog components. Hooray! But you may notice that this prevents _anyone_ from unregistering a component, which is not a very realistic policy. Let's improve this policy by disabling the unregister action _unless you are the owner of this component_. - -## Conditional decisions - -Let's change the policy to the following: - -```ts -import { - AuthorizeResult, - PolicyDecision, - /* highlight-add-next-line */ - isPermission, -} from '@backstage/plugin-permission-common'; -/* highlight-add-start */ -import { - catalogConditions, - createCatalogConditionalDecision, -} from '@backstage/plugin-catalog-backend/alpha'; -import { - catalogEntityDeletePermission, -} from '@backstage/plugin-catalog-common/alpha'; -/* highlight-add-end */ - -class TestPermissionPolicy implements PermissionPolicy { - /* highlight-remove-next-line */ - async handle(request: PolicyQuery): Promise { - /* highlight-add-start */ - async handle( - request: PolicyQuery, - user?: PolicyQueryUser, - ): Promise { - /* highlight-add-end */ - /* highlight-remove-next-line */ - if (request.permission.name === 'catalog.entity.delete') { - /* highlight-add-next-line */ - if (isPermission(request.permission, catalogEntityDeletePermission)) { - /* highlight-remove-start */ - return { - result: AuthorizeResult.DENY, - }; - /* highlight-remove-end */ - /* highlight-add-start */ - return createCatalogConditionalDecision( - request.permission, - catalogConditions.isEntityOwner({ - claims: user?.info.ownershipEntityRefs ?? [], - }), - ); - /* highlight-add-end */ - } - return { result: AuthorizeResult.ALLOW }; - } -} -``` - -Let's walk through the new code that we just added. - -Instead of returning an Definitive Policy Decision, we use factory methods to construct a [Conditional Policy Decision](https://backstage.io/docs/reference/plugin-permission-common.conditionalpolicydecision) (See the [Concepts page](./concepts.md) for more details). Since the policy doesn't have enough information to determine if `user` is the entity owner, this criteria is encapsulated within the conditional decision. However, `createCatalogConditionalDecision` will not compile unless `request.permission` is a catalog entity [`ResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.resourcepermission). This type constraint ensures that policies return conditional decisions that are compatible with the requested permission. To address this, we use [`isPermission`](https://backstage.io/docs/reference/plugin-permission-common.ispermission) to ["narrow"](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) the type of `request.permission` to `ResourcePermission<'catalog-entity'>`. This matches the runtime behavior that was in place before, but you'll notice that the type of `request.permission` has changed within the scope of that `if` statement. - -The `catalogConditions` object contains all of the rules defined by the catalog plugin. These rules can be combined to form a [`PermissionCriteria`](https://backstage.io/docs/reference/plugin-permission-common.permissioncriteria) object, but for this case we only need to use the `isEntityOwner` rule. This rule accepts a list of entity refs that represent User identity and Group membership used to determine ownership. The second argument to `PermissionPolicy#handle` provides us with a `PolicyQueryUser` object, from which we can grab the user's `ownershipEntityRefs`. We provide an empty array as a fallback since the user may be anonymous. - -You should now be able to see in your Backstage app that the unregister entity button is enabled for entities that you own, but disabled for all other entities! - -## Resource types - -Now let's say we want to prevent all actions on catalog entities unless performed by the owner. One way to achieve this may be to simply update the `if` statement and check for each permission. If you choose to write your policy this way, it will certainly work! However, it may be difficult to maintain as the policy grows, and it may not be obvious if certain permissions are left out. We can author this same policy in a more scalable way by checking the resource type of the requested permission. - -```ts -import { - AuthorizeResult, - PolicyDecision, - /* highlight-remove-next-line */ - isPermission, - isResourcePermission, - /* highlight-add-next-line */ -} from '@backstage/plugin-permission-common'; -import { - catalogConditions, - createCatalogConditionalDecision, -} from '@backstage/plugin-catalog-backend/alpha'; -/* highlight-remove-start */ -import { - catalogEntityDeletePermission, -} from '@backstage/plugin-catalog-common/alpha'; -/* highlight-remove-end */ - -class TestPermissionPolicy implements PermissionPolicy { - async handle( - request: PolicyQuery, - user?: PolicyQueryUser, - ): Promise { - /* highlight-remove-next-line */ - if (isPermission(request.permission, catalogEntityDeletePermission)) { - /* highlight-add-next-line */ - if (isResourcePermission(request.permission, 'catalog-entity')) { - return createCatalogConditionalDecision( - request.permission, - catalogConditions.isEntityOwner({ - claims: user?.info.ownershipEntityRefs ?? [], - }), - ); - } - - return { result: AuthorizeResult.ALLOW }; - } -} -``` - -In this example, we use [`isResourcePermission`](https://backstage.io/docs/reference/plugin-permission-common.isresourcepermission) to match all permissions with a resource type of `catalog-entity`. Just like `isPermission`, this helper will "narrow" the type of `request.permission` and enable the use of `createCatalogConditionalDecision`. In addition to the behavior you observed before, you should also see that catalog entities are no longer visible unless you are the owner - success! - -_Note:_ Some catalog permissions do not have the `'catalog-entity'` resource type, such as [`catalogEntityCreatePermission`](https://github.com/backstage/backstage/blob/1e5e9fb9de9856a49e60fc70c38a4e4e94c69570/plugins/catalog-common/src/permissions.ts#L49). In those cases, a definitive decision is required because conditions can't be applied to an entity that does not exist yet. diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md index ce662b6e1a..a479684287 100644 --- a/docs/permissions/writing-a-policy.md +++ b/docs/permissions/writing-a-policy.md @@ -5,7 +5,7 @@ description: How to write your own permission policy as a Backstage integrator --- :::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./writing-a-policy--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/permissions/writing-a-policy--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: In the [previous section](./getting-started.md), we were able to set up the permission framework and make a simple change to our `TestPermissionPolicy` to confirm that policy is indeed wired up correctly. diff --git a/docs/plugins/integrating-search-into-plugins--old.md b/docs/plugins/integrating-search-into-plugins--old.md deleted file mode 100644 index 2729dd3d21..0000000000 --- a/docs/plugins/integrating-search-into-plugins--old.md +++ /dev/null @@ -1,421 +0,0 @@ ---- -id: integrating-search-into-plugins--old -title: Integrating Search into a plugin -description: How to integrate Search into a Backstage plugin ---- - -:::info -This documentation is written for the old backend which has been replaced by [the new backend system](../backend-system/index.md), being the default since Backstage [version 1.24](../releases/v1.24.0.md). If have migrated to the new backend system, you may want to read [its own article](./integrating-search-into-plugins.md) instead. Otherwise, [consider migrating](../backend-system/building-backends/08-migrating.md)! -::: - -The Backstage Search Platform was designed to give plugin developers the APIs -and interfaces needed to offer search experiences within their plugins, while -abstracting away (and instead empowering application integrators to choose) the -specific underlying search technologies. - -On this page, you'll find concepts and tutorials for leveraging the Backstage -Search Platform in your plugin. - -## Providing data to the search platform - -### Create a collator - -> Knowing what a [collator](../features/search/concepts.md#collators) is will help you as you build it out. - -Imagine you have a plugin that is responsible for storing FAQ snippets in a database. You want other engineers to be able to easily find your questions and answers. So that means you want them to be indexed by the search platform. Lets say the FAQ snippets can be viewed at a URL like `backstage.example.biz/faq-snippets`. - -The search platform provides an interface (`DocumentCollatorFactory` from package `@backstage/plugin-search-common`) that allows you to do exactly that. It works by registering each of your entries as a "document" that later represents one search result each. - -> You can always look at a working example, e.g. [StackOverflowQuestionsCollatorFactory](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-stack-overflow-collator/src/collators/StackOverflowQuestionsCollatorFactory.ts), if you are unsure or want to follow best practices. - -#### 1. Install collator interface dependencies - -We will need the interface `DocumentCollatorFactory` from package `@backstage/plugin-search-common`, so let's add it to your plugins dependencies: - -```sh -# navigate to the plugin directory -# (for this tutorial our plugin lives in the backstage repo, if your plugin lives in a separate repo you need to clone that first) -cd plugins/faq-snippets - -# Create a new branch using Git command-line -git checkout -b tutorials/new-faq-snippets-collator - -# Install the package containing the interface -yarn add @backstage/plugin-search-common -``` - -#### 2. Define your document type - -Before we can start generating documents from our FAQ entries, we first have to define a document type containing all necessary information we need to later display our entry as search result. The package `@backstage/plugin-search-common` we installed earlier contains a type `IndexableDocument` that we can extend. - -Create a new file `plugins/faq-snippets/src/search/collators/FaqSnippetDocument.ts` and paste the following below: - -```ts -import { IndexableDocument } from '@backstage/plugin-search-common'; - -export interface FaqSnippetDocument extends IndexableDocument { - answered_by: string; -} -``` - -#### 3. Use Backstage App configuration - -Your new collator could benefit from using configuration directly from the Backstage `app-config.yaml` file which is located on the project's root folder: - -```yaml -faq: - baseUrl: https://backstage.example.biz/faq-snippets -``` - -#### 4. Implement your collator - -Imagine your FAQs can be retrieved at the URL `https://backstage.example.biz/faq-snippets` with following JSON response format: - -```json -{ - "items": [ - { - "id": 42, - "question": "What is The Answer to the Ultimate Question of Life, the Universe, and Everything?", - "answer": "Forty-two", - "user": "Deep Thought" - } - ] -} -``` - -Below we provide an example implementation of how the FAQ collator factory could look like using our new document type, placed in the `plugins/faq-snippets/src/search/collators/FaqCollatorFactory.ts` file: - -```ts -import fetch from 'cross-fetch'; -import { Logger } from 'winston'; -import { Config } from '@backstage/config'; -import { Readable } from 'stream'; -import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; - -import { FaqDocument } from './FaqDocument'; - -export type FaqCollatorFactoryOptions = { - baseUrl?: string; - logger: Logger; -}; - -export class FaqCollatorFactory implements DocumentCollatorFactory { - private readonly baseUrl: string | undefined; - private readonly logger: Logger; - public readonly type: string = 'faq-snippets'; - - private constructor(options: FaqCollatorFactoryOptions) { - this.baseUrl = options.baseUrl; - this.logger = options.logger; - } - - static fromConfig(config: Config, options: FaqCollatorFactoryOptions) { - const baseUrl = - config.getOptionalString('faq.baseUrl') || - 'https://backstage.example.biz/faq-snippets'; - return new FaqCollatorFactory({ ...options, baseUrl }); - } - - async getCollator() { - return Readable.from(this.execute()); - } - - async *execute(): AsyncGenerator { - if (!this.baseUrl) { - this.logger.error(`No faq.baseUrl configured in your app-config.yaml`); - return; - } - - const response = await fetch(this.baseUrl); - const data = await response.json(); - - for (const faq of data.items) { - yield { - title: faq.question, - location: `/faq-snippets/${faq.id}`, - text: faq.answer, - answered_by: faq.user, - }; - } - } -} -``` - -#### 5. Test your collator - -To verify your implementation works as expected make sure to add tests for it. For your convenience, there is the [`TestPipeline`](https://backstage.io/docs/reference/plugin-search-backend-node.testpipeline) utility that emulates a pipeline into which you can integrate your custom collator. - -Look at [DefaultTechDocsCollatorFactory test](https://github.com/backstage/backstage/blob/de294ce5c410c9eb56da6870a1fab795268f60e3/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.test.ts), for an example. - -#### 6. Make your plugins collator discoverable for others - -If you want to make your collator discoverable for other adopters, add it to the list of [plugins integrated to search](https://backstage.io/docs/features/search/#plugins-integrated-with-backstage-search). - -## Building a search experience into your plugin - -While the core Search plugin offers components and extensions that empower app -integrators to compose a global search experience, you may find that you want a -narrower search experience just within your plugin. This could be as literal as -an autocomplete-style search bar focused on documents provided by your plugin -(for example, the [TechDocsSearch](https://github.com/backstage/backstage/blob/master/plugins/techdocs/src/search/components/TechDocsSearch.tsx) -component), or as abstract as a widget that presents a list of links that -are contextually related to something else on the page. - -### Search Experience Concepts - -Knowing these high-level concepts will help you as you craft your in-plugin -search experience. - -- All search experiences must be wrapped in a ``, which - is provided by `@backstage/plugin-search-react`. This context keeps track - of state necessary to perform search queries and display any results. As - inputs to the query are updated (e.g. a `term` or `filter` values), the - updated query is executed and `results` are refreshed. Check out the - [SearchContextValue](https://backstage.io/docs/reference/plugin-search-react.searchcontextvalue) - for details. -- The aforementioned state can be modified and/or consumed via the - `useSearch()` hook, also exported by `@backstage/plugin-search-react`. -- For more literal search experiences, reusable components are available - to import and compose into a cohesive experience in your plugin (e.g. - `` or ``). You can see all such - components in [Backstage's storybook](https://backstage.io/storybook/?path=/story/plugins-search-searchbar--default). - -### Search Experience Tutorials - -The following tutorials make use of packages and plugins that you may not yet -have as dependencies for your plugin; be sure to add them before you use them! - -- [`@backstage/plugin-search-react`](https://www.npmjs.com/package/@backstage/plugin-search-react) - A - package containing components, hooks, and types that are shared across all - frontend plugins, including plugins like yours! -- [`@backstage/plugin-search`](https://www.npmjs.com/package/@backstage/plugin-search) - The - main search plugin, used by app integrators to compose global search - experiences. -- [`@backstage/core-components`](https://www.npmjs.com/package/@backstage/core-components) - A - package containing generic components useful for a variety of experiences - built in Backstage. - -#### Improved "404" page experience - -Imagine you have a plugin that allows users to manage _widgets_. Perhaps they -can be viewed at a URL like `backstage.example.biz/widgets/{widgetName}`. -At some point, a widget is renamed, and links to that widget's page from -chat systems, wikis, or browser bookmarks become stale, resulting in errors or -404s. - -What if instead of showing a broken page or the generic "looks like someone -dropped the mic" 404 page, you showed a list of possibly related widgets? - -```javascript -import { Link } from '@backstage/core-components'; -import { SearchResult } from '@backstage/plugin-search'; -import { SearchContextProvider } from '@backstage/plugin-search-react'; - -export const Widget404Page = ({ widgetName }) => { - // Supplying this to runs a pre-filtered search with - // the given widgetName as the search term, focused on search result of type - // "widget" with no other filters. - const preFiltered = { - term: widgetName, - types: ['widget'], - filters: {}, - }; - - return ( - - {/* The component allows us to iterate through results and - display them in whatever way fits best! */} - - {({ results }) => ( - {results.map(({ document }) => ( - - {document.title} - - ))} - )} - - - ); -); -``` - -Not all search experiences require user input! As you can see, it's possible to -leverage the Backstage Search Platform's frontend framework without necessarily -giving users input controls. - -#### Simple search page - -Of course, it's also possible to provide a more fully featured search -experience in your plugin. The simplest way is to leverage reusable components -provided by the `@backstage/plugin-search` package, like this: - -```javascript -import { useProfile } from '@internal/api'; -import { - Content, - ContentHeader, - PageWithHeader, -} from '@backstage/core-components'; -import { SearchBar, SearchResult } from '@backstage/plugin-search'; -import { SearchContextProvider } from '@backstage/plugin-search-react'; - -export const ManageMyWidgets = () => { - const { primaryTeam } = useProfile(); - // In this example, note how we are pre-filtering results down to a specific - // owner field value (the currently logged-in user's team), but allowing the - // search term to be controlled by the user via the component. - const preFiltered = { - types: ['widget'], - term: '', - filters: { - owner: primaryTeam, - }, - }; - - return ( - - - - - - - {/* Render results here, just like above */} - - - - - ); -}; -``` - -#### Custom search control surfaces - -If the reusable search components provided by `@backstage/plugin-search` aren't -adequate, no problem! There's an API in place that you can use to author your -own components to control the various parts of the search context. - -```javascript -import { useSearch } from '@backstage/plugin-search-react'; -import ChipInput from 'material-ui-chip-input'; - -export const CustomChipFilter = ({ name }) => { - const { filters, setFilters } = useSearch(); - const chipValues = filters[name] || []; - - // When a chip value is changed, update the filters value by calling the - // setFilters function from the search context. - const handleChipChange = (chip, index) => { - // There may be filters set for other fields. Be sure to maintain them. - setFilters(prevState => { - const { [name]: filter = [], ...others } = prevState; - - if (index === undefined) { - filter.push(chip); - } else { - filter.splice(index, 1); - } - - return { ...others, [name]: filter }; - }); - }; - - return ( - - ); -}; -``` - -Check out the [SearchContextValue type](https://github.com/backstage/backstage/blob/master/plugins/search-react/src/context/SearchContext.tsx) -for more details on what methods and values are available for manipulating and -reading the search context. - -If you produce something generic and reusable, consider contributing your -component upstream so that all users of the Backstage Search Platform can -benefit. Issues and pull requests welcome. - -#### Custom search results - -Search results throughout Backstage are rendered as lists so that list items can easily be customized; although a [default result list item](https://backstage.io/storybook/?path=/story/plugins-search-defaultresultlistitem--default) is available, plugins are in the best position to provide custom result list items that surface relevant information only known to the plugin. - -The example below imagines `YourCustomSearchResult` as a type of search result that contains associated `tags` which could be rendered as chips below the title/text. - -```tsx -import { Link } from '@backstage/core-components'; -import { useAnalytics } from '@backstage/core-plugin-api'; -import { ResultHighlight } from '@backstage/plugin-search-common'; -import { HighlightedSearchResultText } from '@backstage/plugin-search-react'; - -type CustomSearchResultListItemProps = { - result: YourCustomSearchResult; - rank?: number; - highlight?: ResultHighlight; -}; - -export const CustomSearchResultListItem = ( - props: CustomSearchResultListItemProps, -) => { - const { title, text, location, tags } = props.result; - - const analytics = useAnalytics(); - const handleClick = () => { - analytics.captureEvent('discover', title, { - attributes: { to: location }, - value: props.rank, - }); - }; - - return ( - - - - - ) : ( - title - ) - } - secondary={ - highlight?.fields?.text ? ( - - ) : ( - text - ) - } - /> - {tags && - tags.map((tag: string) => ( - - ))} - - - - - ); -}; -``` - -The optional use of the `` component makes it possible to highlight relevant parts of the result based on the user's search query. - -**Note on Analytics**: In order for app integrators to track and improve search experiences across Backstage, it's important for them to understand when and what users search for, as well as what they click on after searching. When providing a custom result component, it's your responsibility as a plugin developer to instrument it according to search analytics conventions. In particular: - -- You must use the `analytics.captureEvent` method, from the `useAnalytics()` hook (detailed [plugin analytics docs are here](./analytics.md)). -- You must ensure that the action of the event, representing a click on a search result item, is `discover`, and the subject is the `title` of the clicked result. In addition, the `to` attribute should be set to the result's `location`, and the `value` of the event must be set to the `rank` (passed in as a prop). -- You must ensure that the aforementioned `captureEvent` method is called when a user clicks the link; you should further ensure that the `noTrack` prop is added to the link (which disables default link click tracking, in favor of this custom instrumentation). - -For other examples and inspiration on custom result list items, check out the [``](https://github.com/backstage/backstage/blob/c981e83/plugins/stack-overflow/src/search/StackOverflowSearchResultListItem/StackOverflowSearchResultListItem.tsx) or [``](https://github.com/backstage/backstage/blob/c981e83/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx) components. diff --git a/docs/plugins/integrating-search-into-plugins.md b/docs/plugins/integrating-search-into-plugins.md index 374a8aeb8a..d6f2037529 100644 --- a/docs/plugins/integrating-search-into-plugins.md +++ b/docs/plugins/integrating-search-into-plugins.md @@ -5,7 +5,7 @@ description: How to integrate Search into a Backstage plugin --- :::info -This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](./integrating-search-into-plugins--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! +This documentation is written for [the new backend system](../backend-system/index.md) which is the default since Backstage [version 1.24](../releases/v1.24.0.md). If you are still on the old backend system, you may want to read [its own article](https://github.com/backstage/backstage/blob/v1.37.0/docs/plugins/integrating-search-into-plugins--old.md) instead, and [consider migrating](../backend-system/building-backends/08-migrating.md)! ::: The Backstage Search Platform was designed to give plugin developers the APIs diff --git a/docs/plugins/internationalization.md b/docs/plugins/internationalization.md index 1fbf54d94e..4eb5e864d1 100644 --- a/docs/plugins/internationalization.md +++ b/docs/plugins/internationalization.md @@ -147,6 +147,42 @@ export const myPluginTranslationRef = createTranslationRef({ }); ``` +#### JSX Elements + +The translation API supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. + +For example, you might define the following messages: + +```ts title="define the message" +export const myPluginTranslationRef = createTranslationRef({ + id: 'plugin.my-plugin', + messages: { + entityPage: { + redirect: { + message: 'The entity you are looking for has been moved to {{link}}.', + link: 'new location', + }, + }, + }, +}); +``` + +Which can be used within a component like this: + +```tsx title="use within a component" +const { t } = useTranslationRef(myPluginTranslationRef); + +return ( +
+ {t('entityPage.redirect.message', { + link: {t('entityPage.redirect.link')}, + })} +
+); +``` + +The return type of the outer `t` function will be a `JSX.Element`, with the underlying value being a React fragment of the different parts of the message. + ## For an application developer overwrite plugin messages Step 1: Create translation resources diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index 26329f8475..b32ed4fe12 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -41,6 +41,7 @@ Example: ```yaml # in app-config.yaml proxy: + reviveConsumedRequestBodies: true endpoints: /simple-example: http://simple.example.com:8080 '/larger-example/v1': @@ -121,6 +122,12 @@ third parties. The same logic applies to headers that are sent from the target back to the frontend. +### Passing POST-request body + +To fix the issue with missing request body passed by proxy to the target, set `proxy.reviveConsumedRequestBodies: true`, so the `fixRequestBody` handler of [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware?tab=readme-ov-file#intercept-and-manipulate-requests) will be used. + +In that case, mind setting the `Content-Type` header to either `application/json` or `application/x-www-form-urlencoded`. + ### Proxy Extension Endpoint The proxy plugin additionally supports a `proxyExtensionEndpoint` which a proxy diff --git a/docs/releases/v1.39.0-next.2-changelog.md b/docs/releases/v1.39.0-next.2-changelog.md new file mode 100644 index 0000000000..7a7b9dc4ed --- /dev/null +++ b/docs/releases/v1.39.0-next.2-changelog.md @@ -0,0 +1,1505 @@ +# Release v1.39.0-next.2 + +Upgrade Helper: [https://backstage.github.io/upgrade-helper/?to=1.39.0-next.2](https://backstage.github.io/upgrade-helper/?to=1.39.0-next.2) + +## @backstage/integration@1.17.0-next.2 + +### Minor Changes + +- f134cea: Implement Edit URL feature for Gerrit 3.9+. + + It's possible to disable the edit url by adding the `disableEditUrl: true` config in the Gerrit integration. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/plugin-catalog-import@0.13.0-next.2 + +### Minor Changes + +- e2fd549: **BREAKING**: `generateStepper` and `defaultGenerateStepper` now require a translation argument to be passed through for supporting translations. + +### Patch Changes + +- 66a1140: Add i18n support for `catalog-import` plugin. +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + +## @backstage/app-defaults@1.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + +## @backstage/backend-defaults@0.10.0-next.2 + +### Patch Changes + +- 0e7a640: The `GithubUrlReader` will now use the token from `options` when fetching repo details +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/backend-dynamic-feature-service@0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/plugin-events-backend@0.5.2-next.1 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/backend-test-utils@1.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/canon@0.4.0-next.2 + +### Patch Changes + +- 6189bfd: Added new icon and onClear props to the TextField to make it easier to accessorize inputs. +- 97b25a1: Pin version of @base-ui-components/react. +- 185d3a8: Use the Field component from Base UI within the TextField. + +## @backstage/cli@0.32.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/release-manifests@0.0.12 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.10 + - @backstage/types@1.2.1 + +## @backstage/core-app-api@1.16.2-next.0 + +### Patch Changes + +- 73f6cc3: Updated `I18nextTranslationApi` to support interpolation of JSX elements. +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-compat-api@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-components@0.17.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/version-bridge@1.0.11 + +## @backstage/core-plugin-api@1.10.7-next.0 + +### Patch Changes + +- 73f6cc3: The `TranslationApi` now supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/create-app@0.6.2-next.2 + +### Patch Changes + +- 8448948: Removed `lerna-debug.log*` pattern from `.gitignore` as Lerna was removed from the package in version `@backstage/create-app@0.5.19`. +- Updated dependencies + - @backstage/cli-common@0.1.15 + +## @backstage/dev-utils@1.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/core-components@0.17.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + +## @backstage/frontend-app-api@0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/frontend-defaults@0.2.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-defaults@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/errors@1.2.7 + +## @backstage/frontend-dynamic-feature-loader@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + +## @backstage/frontend-plugin-api@0.10.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/frontend-test-utils@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @backstage/integration-aws-node@0.1.16-next.0 + +### Patch Changes + +- db4630e: Fixed bug in DefaultAwsCredentialsManager where aws.mainAccount.region has no effect on the STS region used for account ID lookup during credential provider lookup when falling back to the main account, and it does not default to us-east-1 +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + +## @backstage/integration-react@1.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + +## @backstage/repo-tools@0.13.3-next.2 + +### Patch Changes + +- b229476: Support passing additional properties to OpenAPI server generator +- Updated dependencies + - @backstage/cli-node@0.2.13 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + +## @techdocs/cli@1.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + +## @backstage/test-utils@1.7.8-next.1 + +### Patch Changes + +- b573341: Added support for interpolating JSX elements with the `MockTranslationApi`. +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-api-docs@0.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + +## @backstage/plugin-app@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-app-visualizer@0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + +## @backstage/plugin-auth-react@0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + +## @backstage/plugin-catalog@1.29.1-next.2 + +### Patch Changes + +- bf85d37: Fix for missing `routeRef` when using `core-plugin-api` in a dialog context +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-catalog-backend@2.0.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.4.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.9.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-github-org@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend-module-github@0.9.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab-org@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-incremental-ingestion@0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.11.5-next.2 + +### Patch Changes + +- e253d1d: Improves error reporting for missing metadata.name in LDAP catalog provider. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + +## @backstage/plugin-catalog-backend-module-logs@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + +## @backstage/plugin-catalog-graph@0.4.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + +## @backstage/plugin-catalog-react@1.18.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/frontend-test-utils@0.3.2-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + +## @backstage/plugin-config-schema@0.1.68-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-devtools@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16-next.0 + +## @backstage/plugin-devtools-backend@0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-events-backend-module-github@0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + +## @backstage/plugin-home@0.8.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home-react@0.1.26-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + +## @backstage/plugin-home-react@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + +## @backstage/plugin-kubernetes@0.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-kubernetes-backend@0.19.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-kubernetes-node@0.3.0-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-kubernetes-react@0.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + +## @backstage/plugin-notifications@0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-notifications-backend-module-email@0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-notifications-backend-module-slack@0.1.1-next.2 + +### Patch Changes + +- 4f10768: Fix slack notification processor to handle a notification with an empty description +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-org@0.6.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + +## @backstage/plugin-org-react@0.1.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + +## @backstage/plugin-permission-react@0.4.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + +## @backstage/plugin-scaffolder@1.31.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-scaffolder-backend@1.33.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-confluence-to-markdown@0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gcp@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.2 + +### Patch Changes + +- b60253d: Change notification send scaffolder action to use native zod schemas +- Updated dependencies + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-notifications-common@0.0.8 + +## @backstage/plugin-scaffolder-backend-module-rails@0.5.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-backend-module-sentry@0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.2 + +## @backstage/plugin-scaffolder-node@0.8.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-test-utils@1.5.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + +## @backstage/plugin-scaffolder-react@1.16.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + +## @backstage/plugin-search@1.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend@2.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.7.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-backend-module-techdocs@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-search-react@1.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + +## @backstage/plugin-signals@0.0.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-signals-react@0.0.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/types@1.2.1 + +## @backstage/plugin-techdocs@1.12.6-next.2 + +### Patch Changes + +- 7d445da: Update keyboard focus on when clicking hash links. This fixes the issue where the "skip to content" link rendered by Material MkDocs isn't focused when used. +- 2ffd273: Add hover and focus styling to the "copy to clipboard" button within codeblocks in techdocs. Also added an aria-label to the button for accessibility. +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.48-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## @backstage/plugin-techdocs-backend@2.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + +## @backstage/plugin-techdocs-node@1.13.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-search-common@1.2.18-next.0 + - @backstage/plugin-techdocs-common@0.1.0 + +## @backstage/plugin-techdocs-react@1.2.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/version-bridge@1.0.11 + +## @backstage/plugin-user-settings@0.8.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## @backstage/plugin-user-settings-backend@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-signals-node@0.1.20-next.1 + - @backstage/plugin-user-settings-common@0.0.1 + +## example-app@0.2.109-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/canon@0.4.0-next.2 + - @backstage/plugin-catalog-import@0.13.0-next.2 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-api-docs@0.12.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home@0.8.8-next.2 + - @backstage/plugin-kubernetes@0.12.7-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + - @backstage/plugin-notifications@0.5.5-next.2 + - @backstage/plugin-org@0.6.39-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/plugin-search@1.4.26-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/plugin-user-settings@0.8.22-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + - @backstage/plugin-devtools@0.1.27-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## example-app-next@0.0.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/canon@0.4.0-next.2 + - @backstage/plugin-catalog-import@0.13.0-next.2 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-defaults@0.2.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-api-docs@0.12.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home@0.8.8-next.2 + - @backstage/plugin-kubernetes@0.12.7-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + - @backstage/plugin-notifications@0.5.5-next.2 + - @backstage/plugin-org@0.6.39-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/plugin-search@1.4.26-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/plugin-user-settings@0.8.22-next.2 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/plugin-app-visualizer@0.1.19-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + +## app-next-example-plugin@0.0.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + +## example-backend@0.0.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/plugin-events-backend@0.5.2-next.1 + - @backstage/plugin-search-backend@2.0.2-next.2 + - @backstage/plugin-kubernetes-backend@0.19.6-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend@1.33.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + - @backstage/plugin-techdocs-backend@2.0.2-next.2 + - @backstage/plugin-app-backend@0.5.2-next.1 + - @backstage/plugin-auth-backend@0.25.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-devtools-backend@0.5.5-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.1 + - @backstage/plugin-notifications-backend@0.5.6-next.1 + - @backstage/plugin-permission-backend@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-proxy-backend@0.6.2-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/plugin-signals-backend@0.3.4-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.4-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.2-next.1 + +## e2e-test@0.2.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.6.2-next.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + +## @internal/frontend@0.0.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + +## @internal/scaffolder@0.0.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + +## techdocs-cli-embedded-app@0.2.108-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + +## @internal/plugin-todo-list@1.0.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 diff --git a/microsite/sidebars.ts b/microsite/sidebars.ts index eb61c32daa..dd52f9a801 100644 --- a/microsite/sidebars.ts +++ b/microsite/sidebars.ts @@ -228,6 +228,7 @@ export default { 'features/software-templates/migrating-from-v1beta2-to-v1beta3', 'features/software-templates/dry-run-testing', 'features/software-templates/experimental', + 'features/software-templates/template-extensions', ], }, { diff --git a/package.json b/package.json index 15036733a7..b5d88d947b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "root", - "version": "1.39.0-next.1", + "version": "1.39.0-next.2", "backstage": { "cli": { "new": { @@ -102,13 +102,13 @@ "@material-ui/pickers@^3.3.10": "patch:@material-ui/pickers@npm%3A3.3.11#./.yarn/patches/@material-ui-pickers-npm-3.3.11-1c8f68ea20.patch", "@types/react": "^18.0.0", "@types/react-dom": "^18.0.0", + "@yarnpkg/plugin-npm@npm:^3.1.0": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch", "ast-types@0.14.2": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch", "ast-types@^0.14.1": "patch:ast-types@npm%3A0.14.2#./.yarn/patches/ast-types-npm-0.14.2-43c4ac4b0d.patch", "csstype@npm:^3.0.2": "3.0.9", "csstype@npm:^3.1.2": "3.0.9", "csstype@npm:^3.1.3": "3.0.9", - "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch", - "@yarnpkg/plugin-npm@npm:^3.1.0": "patch:@yarnpkg/plugin-npm@npm%3A3.1.0#~/.yarn/patches/@yarnpkg-plugin-npm-npm-3.1.0-6533d0f5a1.patch" + "jest-haste-map@^29.7.0": "patch:jest-haste-map@npm%3A29.7.0#./.yarn/patches/jest-haste-map-npm-29.7.0-e3be419eff.patch" }, "dependencies": { "@backstage/errors": "workspace:^", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 43487efc73..d130df1b26 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/app-defaults +## 1.6.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + ## 1.6.2-next.0 ### Patch Changes diff --git a/packages/app-defaults/catalog-info.yaml b/packages/app-defaults/catalog-info.yaml index e8450be1ec..057505a89c 100644 --- a/packages/app-defaults/catalog-info.yaml +++ b/packages/app-defaults/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index a6220cbe18..b2cc639068 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/app-defaults", - "version": "1.6.2-next.0", + "version": "1.6.2-next.1", "description": "Provides the default wiring of a Backstage App", "backstage": { "role": "web-library" diff --git a/packages/app-next-example-plugin/CHANGELOG.md b/packages/app-next-example-plugin/CHANGELOG.md index 1cd3e791f0..f39080a253 100644 --- a/packages/app-next-example-plugin/CHANGELOG.md +++ b/packages/app-next-example-plugin/CHANGELOG.md @@ -1,5 +1,13 @@ # app-next-example-plugin +## 0.0.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + ## 0.0.23-next.0 ### Patch Changes diff --git a/packages/app-next-example-plugin/catalog-info.yaml b/packages/app-next-example-plugin/catalog-info.yaml index b89b43285a..53869a83a9 100644 --- a/packages/app-next-example-plugin/catalog-info.yaml +++ b/packages/app-next-example-plugin/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/packages/app-next-example-plugin/package.json b/packages/app-next-example-plugin/package.json index f6699ef0fa..e0fd8ffff7 100644 --- a/packages/app-next-example-plugin/package.json +++ b/packages/app-next-example-plugin/package.json @@ -1,6 +1,6 @@ { "name": "app-next-example-plugin", - "version": "0.0.23-next.0", + "version": "0.0.23-next.1", "description": "Backstage internal example plugin", "backstage": { "role": "frontend-plugin", diff --git a/packages/app-next/CHANGELOG.md b/packages/app-next/CHANGELOG.md index 0a34bdb8a8..5cb9e8581e 100644 --- a/packages/app-next/CHANGELOG.md +++ b/packages/app-next/CHANGELOG.md @@ -1,5 +1,51 @@ # example-app-next +## 0.0.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/canon@0.4.0-next.2 + - @backstage/plugin-catalog-import@0.13.0-next.2 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-defaults@0.2.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-api-docs@0.12.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home@0.8.8-next.2 + - @backstage/plugin-kubernetes@0.12.7-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + - @backstage/plugin-notifications@0.5.5-next.2 + - @backstage/plugin-org@0.6.39-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/plugin-search@1.4.26-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/plugin-user-settings@0.8.22-next.2 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/plugin-app-visualizer@0.1.19-next.1 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.0.23-next.1 ### Patch Changes diff --git a/packages/app-next/catalog-info.yaml b/packages/app-next/catalog-info.yaml index 85e3f0d70b..14e51ecd04 100644 --- a/packages/app-next/catalog-info.yaml +++ b/packages/app-next/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend - owner: maintainers + owner: framework-maintainers diff --git a/packages/app-next/package.json b/packages/app-next/package.json index 690a9d4f28..0f56c433dd 100644 --- a/packages/app-next/package.json +++ b/packages/app-next/package.json @@ -1,6 +1,6 @@ { "name": "example-app-next", - "version": "0.0.23-next.1", + "version": "0.0.23-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 8ebf850a7d..ff42b42cc8 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,47 @@ # example-app +## 0.2.109-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/canon@0.4.0-next.2 + - @backstage/plugin-catalog-import@0.13.0-next.2 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-api-docs@0.12.7-next.2 + - @backstage/plugin-auth-react@0.1.15-next.1 + - @backstage/plugin-catalog-graph@0.4.19-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home@0.8.8-next.2 + - @backstage/plugin-kubernetes@0.12.7-next.2 + - @backstage/plugin-kubernetes-cluster@0.0.25-next.2 + - @backstage/plugin-notifications@0.5.5-next.2 + - @backstage/plugin-org@0.6.39-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder@1.31.0-next.2 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/plugin-search@1.4.26-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/plugin-signals@0.0.19-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.1.24-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/plugin-user-settings@0.8.22-next.2 + - @backstage/plugin-catalog-unprocessed-entities@0.2.17-next.2 + - @backstage/plugin-devtools@0.1.27-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.2.109-next.1 ### Patch Changes diff --git a/packages/app/catalog-info.yaml b/packages/app/catalog-info.yaml index e0808307cc..5880de9597 100644 --- a/packages/app/catalog-info.yaml +++ b/packages/app/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend - owner: maintainers + owner: framework-maintainers diff --git a/packages/app/package.json b/packages/app/package.json index 2393a89feb..b988bf460d 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.109-next.1", + "version": "0.2.109-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/backend-app-api/catalog-info.yaml b/packages/backend-app-api/catalog-info.yaml index 051e7ab5c2..c181c0720a 100644 --- a/packages/backend-app-api/catalog-info.yaml +++ b/packages/backend-app-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 49a911c398..b66cdb5ac7 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/backend-defaults +## 0.10.0-next.2 + +### Patch Changes + +- 0e7a640: The `GithubUrlReader` will now use the token from `options` when fetching repo details +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-dev-utils@0.1.5 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.10.0-next.1 ### Minor Changes diff --git a/packages/backend-defaults/catalog-info.yaml b/packages/backend-defaults/catalog-info.yaml index d3d00cc3d2..77971441c0 100644 --- a/packages/backend-defaults/catalog-info.yaml +++ b/packages/backend-defaults/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index ec6486d2b3..093ebdc422 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-defaults", - "version": "0.10.0-next.1", + "version": "0.10.0-next.2", "description": "Backend defaults used by Backstage backend apps", "backstage": { "role": "node-library" diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts index 70b18df091..4e1c5a2d08 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.test.ts @@ -492,7 +492,7 @@ describe('GithubUrlReader', () => { }); it('should override the token when provided', async () => { - expect.assertions(1); + expect.assertions(2); const mockHeaders = { Authorization: 'bearer blah', @@ -503,6 +503,20 @@ describe('GithubUrlReader', () => { }); worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/commits/main/status', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + 'Bearer overridentoken', + ); + + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(commitStatusGheResponse), + ); + }, + ), rest.get( 'https://ghe.github.com/api/v3/repos/backstage/mock/tarball/etag123abc', (req, res, ctx) => { @@ -678,6 +692,21 @@ describe('GithubUrlReader', () => { }, ]; + const gheCommitsResponse = { + sha: 'etag123abc', + repository: { + id: 123, + full_name: 'backstage/mock', + default_branch: 'main', + branches_url: + 'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}', + archive_url: + 'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}', + trees_url: + 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees{/sha}', + }, + } as Partial; + // Tarballs beforeEach(() => { worker.use( @@ -773,21 +802,6 @@ describe('GithubUrlReader', () => { }, } as Partial; - const gheResponse = { - sha: 'etag123abc', - repository: { - id: 123, - full_name: 'backstage/mock', - default_branch: 'main', - branches_url: - 'https://ghe.github.com/api/v3/repos/backstage/mock/branches{/branch}', - archive_url: - 'https://ghe.github.com/api/v3/repos/backstage/mock/{archive_format}{/ref}', - trees_url: - 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees{/sha}', - }, - } as Partial; - worker.use( rest.get( 'https://api.github.com/repos/backstage/mock/commits/main/status', @@ -810,7 +824,7 @@ describe('GithubUrlReader', () => { return res( ctx.status(200), ctx.set('Content-Type', 'application/json'), - ctx.json(gheResponse), + ctx.json(gheCommitsResponse), ); } @@ -969,9 +983,23 @@ describe('GithubUrlReader', () => { }); it('passes through a token for the search request', async () => { - expect.assertions(1); + expect.assertions(2); worker.use( + rest.get( + 'https://ghe.github.com/api/v3/repos/backstage/mock/commits/main/status', + (req, res, ctx) => { + expect(req.headers.get('authorization')).toBe( + 'Bearer overridentoken', + ); + + return res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json(gheCommitsResponse), + ); + }, + ), rest.get( 'https://ghe.github.com/api/v3/repos/backstage/mock/git/trees/etag123abc', (req, res, ctx) => { diff --git a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts index d78475de0b..1dbade8477 100644 --- a/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts +++ b/packages/backend-defaults/src/entrypoints/urlReader/lib/GithubUrlReader.ts @@ -154,7 +154,7 @@ export class GithubUrlReader implements UrlReaderService { url: string, options?: UrlReaderServiceReadTreeOptions, ): Promise { - const repoDetails = await this.getRepoDetails(url); + const repoDetails = await this.getRepoDetails(url, options); const commitSha = repoDetails.commitSha; if (options?.etag && options.etag === commitSha) { @@ -212,7 +212,7 @@ export class GithubUrlReader implements UrlReaderService { } } - const repoDetails = await this.getRepoDetails(url); + const repoDetails = await this.getRepoDetails(url, options); const commitSha = repoDetails.commitSha; if (options?.etag && options.etag === commitSha) { @@ -320,7 +320,10 @@ export class GithubUrlReader implements UrlReaderService { })); } - private async getRepoDetails(url: string): Promise<{ + private async getRepoDetails( + url: string, + options?: { token?: string }, + ): Promise<{ commitSha: string; repo: { archive_url: string; @@ -330,9 +333,7 @@ export class GithubUrlReader implements UrlReaderService { const parsed = parseGitUrl(url); const { ref, full_name } = parsed; - const credentials = await this.deps.credentialsProvider.getCredentials({ - url, - }); + const credentials = await this.getCredentials(url, options); const { headers } = credentials; const commitStatus: GhCombinedCommitStatusResponse = await this.fetchJson( diff --git a/packages/backend-dev-utils/catalog-info.yaml b/packages/backend-dev-utils/catalog-info.yaml index ecbcb4e0bb..c53d388a51 100644 --- a/packages/backend-dev-utils/catalog-info.yaml +++ b/packages/backend-dev-utils/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-dynamic-feature-service/CHANGELOG.md b/packages/backend-dynamic-feature-service/CHANGELOG.md index e81fb162c6..8ad2f1e0cf 100644 --- a/packages/backend-dynamic-feature-service/CHANGELOG.md +++ b/packages/backend-dynamic-feature-service/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/backend-dynamic-feature-service +## 0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/plugin-events-backend@0.5.2-next.1 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-app-node@0.1.33-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.7.0-next.1 ### Patch Changes diff --git a/packages/backend-dynamic-feature-service/catalog-info.yaml b/packages/backend-dynamic-feature-service/catalog-info.yaml index 1269a5c406..a5ed85a20a 100644 --- a/packages/backend-dynamic-feature-service/catalog-info.yaml +++ b/packages/backend-dynamic-feature-service/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-dynamic-feature-service/package.json b/packages/backend-dynamic-feature-service/package.json index e8c2a38398..54ea6c8986 100644 --- a/packages/backend-dynamic-feature-service/package.json +++ b/packages/backend-dynamic-feature-service/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-dynamic-feature-service", - "version": "0.7.0-next.1", + "version": "0.7.0-next.2", "description": "Backstage dynamic feature service", "backstage": { "role": "node-library" diff --git a/packages/backend-plugin-api/catalog-info.yaml b/packages/backend-plugin-api/catalog-info.yaml index 35ca9850fa..28c7ea07b0 100644 --- a/packages/backend-plugin-api/catalog-info.yaml +++ b/packages/backend-plugin-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 7ea0d20986..5cf584fc9f 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/backend-test-utils +## 1.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-app-api@1.2.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 1.5.0-next.1 ### Minor Changes diff --git a/packages/backend-test-utils/catalog-info.yaml b/packages/backend-test-utils/catalog-info.yaml index 396f190ef4..94c889b089 100644 --- a/packages/backend-test-utils/catalog-info.yaml +++ b/packages/backend-test-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 799db1e589..bcd3ef014c 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/backend-test-utils", - "version": "1.5.0-next.1", + "version": "1.5.0-next.2", "description": "Test helpers library for Backstage backends", "backstage": { "role": "node-library" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 18f850b5da..b7d8283cd1 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,44 @@ # example-backend +## 0.0.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend-module-notifications@0.1.10-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/plugin-events-backend@0.5.2-next.1 + - @backstage/plugin-search-backend@2.0.2-next.2 + - @backstage/plugin-kubernetes-backend@0.19.6-next.2 + - @backstage/plugin-catalog-backend-module-openapi@0.2.10-next.2 + - @backstage/plugin-scaffolder-backend@1.33.0-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + - @backstage/plugin-techdocs-backend@2.0.2-next.2 + - @backstage/plugin-app-backend@0.5.2-next.1 + - @backstage/plugin-auth-backend@0.25.0-next.1 + - @backstage/plugin-auth-backend-module-github-provider@0.3.3-next.1 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-devtools-backend@0.5.5-next.2 + - @backstage/plugin-events-backend-module-google-pubsub@0.1.0-next.1 + - @backstage/plugin-notifications-backend@0.5.6-next.1 + - @backstage/plugin-permission-backend@0.7.0-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-proxy-backend@0.6.2-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/plugin-signals-backend@0.3.4-next.1 + - @backstage/plugin-search-backend-module-techdocs@0.4.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-auth-backend-module-guest-provider@0.2.8-next.1 + - @backstage/plugin-catalog-backend-module-backstage-openapi@0.5.2-next.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1 + - @backstage/plugin-catalog-backend-module-unprocessed@0.6.0-next.1 + - @backstage/plugin-permission-backend-module-allow-all-policy@0.2.8-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-backend-module-catalog@0.3.4-next.1 + - @backstage/plugin-search-backend-module-explore@0.3.2-next.1 + ## 0.0.38-next.1 ### Patch Changes diff --git a/packages/backend/catalog-info.yaml b/packages/backend/catalog-info.yaml index 0e95516a41..3fb9572dc6 100644 --- a/packages/backend/catalog-info.yaml +++ b/packages/backend/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend - owner: maintainers + owner: framework-maintainers diff --git a/packages/backend/package.json b/packages/backend/package.json index fa51d98d4a..70a47f794c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.0.38-next.1", + "version": "0.0.38-next.2", "backstage": { "role": "backend" }, diff --git a/packages/canon/CHANGELOG.md b/packages/canon/CHANGELOG.md index 7cb4492505..cca1758025 100644 --- a/packages/canon/CHANGELOG.md +++ b/packages/canon/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/canon +## 0.4.0-next.2 + +### Patch Changes + +- 6189bfd: Added new icon and onClear props to the TextField to make it easier to accessorize inputs. +- 97b25a1: Pin version of @base-ui-components/react. +- 185d3a8: Use the Field component from Base UI within the TextField. + ## 0.4.0-next.1 ### Minor Changes diff --git a/packages/canon/css/components.css b/packages/canon/css/components.css index 6496363868..6aae715633 100644 --- a/packages/canon/css/components.css +++ b/packages/canon/css/components.css @@ -544,6 +544,7 @@ color: var(--canon-fg-primary); margin-bottom: var(--canon-space-1_5); cursor: pointer; + margin-right: auto; } .canon-TextFieldLabel[data-disabled] { @@ -566,48 +567,100 @@ margin: 0; } -.canon-TextFieldInput { +.canon-TextFieldInputWrapper { + padding: 0 var(--canon-space-3); border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); - padding: 0 var(--canon-space-4); background-color: var(--canon-bg-surface-1); + align-items: center; + display: flex; +} + +.canon-TextFieldInputIcon { + padding-right: var(--canon-space-1); + width: 1.5rem; + height: 1.5rem; + color: var(--canon-fg-primary); + flex-shrink: 0; + display: block; +} + +.canon-TextFieldInput { font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); width: 100%; + height: 100%; + cursor: inherit; + background: none; + border: none; + padding: 0; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } +.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { + padding-right: 1.25rem; +} + +.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { + appearance: none; +} + +.canon-TextFieldClearButton { + margin-left: var(--canon-space-1); + vertical-align: middle; + color: var(--canon-fg-primary); + background: none; + border: none; + padding: 0; + display: none; +} + +.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { + display: inline-block; +} + +.canon-TextFieldClearButtonIcon { + display: block; +} + .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput:hover { - border-color: var(--canon-border-hover); -} - -.canon-TextFieldInput:focus-visible { +.canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); - border-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInput[data-invalid] { +.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { + border-color: var(--canon-border-hover); +} + +.canon-TextField[data-focused] .canon-TextFieldInputWrapper { + border-color: var(--canon-border-pressed); +} + +.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { border-color: var(--canon-fg-danger); } -.canon-TextFieldInput[data-disabled] { +.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldInput[data-size="small"] { +.canon-TextField[data-disabled] .canon-TextFieldClearButton { + cursor: inherit; +} + +.canon-TextFieldInputWrapper[data-size="small"] { height: 2rem; } -.canon-TextFieldInput[data-size="medium"] { +.canon-TextFieldInputWrapper[data-size="medium"] { height: 2.5rem; } diff --git a/packages/canon/css/styles.css b/packages/canon/css/styles.css index b2743220c7..5fb6a934cb 100644 --- a/packages/canon/css/styles.css +++ b/packages/canon/css/styles.css @@ -9768,6 +9768,7 @@ color: var(--canon-fg-primary); margin-bottom: var(--canon-space-1_5); cursor: pointer; + margin-right: auto; } .canon-TextFieldLabel[data-disabled] { @@ -9790,48 +9791,100 @@ margin: 0; } -.canon-TextFieldInput { +.canon-TextFieldInputWrapper { + padding: 0 var(--canon-space-3); border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); - padding: 0 var(--canon-space-4); background-color: var(--canon-bg-surface-1); + align-items: center; + display: flex; +} + +.canon-TextFieldInputIcon { + padding-right: var(--canon-space-1); + width: 1.5rem; + height: 1.5rem; + color: var(--canon-fg-primary); + flex-shrink: 0; + display: block; +} + +.canon-TextFieldInput { font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); width: 100%; + height: 100%; + cursor: inherit; + background: none; + border: none; + padding: 0; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } +.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { + padding-right: 1.25rem; +} + +.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { + appearance: none; +} + +.canon-TextFieldClearButton { + margin-left: var(--canon-space-1); + vertical-align: middle; + color: var(--canon-fg-primary); + background: none; + border: none; + padding: 0; + display: none; +} + +.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { + display: inline-block; +} + +.canon-TextFieldClearButtonIcon { + display: block; +} + .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput:hover { - border-color: var(--canon-border-hover); -} - -.canon-TextFieldInput:focus-visible { +.canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); - border-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInput[data-invalid] { +.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { + border-color: var(--canon-border-hover); +} + +.canon-TextField[data-focused] .canon-TextFieldInputWrapper { + border-color: var(--canon-border-pressed); +} + +.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { border-color: var(--canon-fg-danger); } -.canon-TextFieldInput[data-disabled] { +.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldInput[data-size="small"] { +.canon-TextField[data-disabled] .canon-TextFieldClearButton { + cursor: inherit; +} + +.canon-TextFieldInputWrapper[data-size="small"] { height: 2rem; } -.canon-TextFieldInput[data-size="medium"] { +.canon-TextFieldInputWrapper[data-size="medium"] { height: 2.5rem; } diff --git a/packages/canon/css/textfield.css b/packages/canon/css/textfield.css index 4caf7cb2e9..a2980d6beb 100644 --- a/packages/canon/css/textfield.css +++ b/packages/canon/css/textfield.css @@ -11,6 +11,7 @@ color: var(--canon-fg-primary); margin-bottom: var(--canon-space-1_5); cursor: pointer; + margin-right: auto; } .canon-TextFieldLabel[data-disabled] { @@ -33,48 +34,100 @@ margin: 0; } -.canon-TextFieldInput { +.canon-TextFieldInputWrapper { + padding: 0 var(--canon-space-3); border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); - padding: 0 var(--canon-space-4); background-color: var(--canon-bg-surface-1); + align-items: center; + display: flex; +} + +.canon-TextFieldInputIcon { + padding-right: var(--canon-space-1); + width: 1.5rem; + height: 1.5rem; + color: var(--canon-fg-primary); + flex-shrink: 0; + display: block; +} + +.canon-TextFieldInput { font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); width: 100%; + height: 100%; + cursor: inherit; + background: none; + border: none; + padding: 0; transition: border-color .2s ease-in-out, outline-color .2s ease-in-out; } +.canon-TextFieldInput:not([data-filled]):has( + .canon-TextFieldClearButton) { + padding-right: 1.25rem; +} + +.canon-TextFieldInput[type="search"]::-webkit-search-cancel-button, .canon-TextFieldInput[type="search"]::-webkit-search-decoration { + appearance: none; +} + +.canon-TextFieldClearButton { + margin-left: var(--canon-space-1); + vertical-align: middle; + color: var(--canon-fg-primary); + background: none; + border: none; + padding: 0; + display: none; +} + +.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { + display: inline-block; +} + +.canon-TextFieldClearButtonIcon { + display: block; +} + .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput:hover { - border-color: var(--canon-border-hover); -} - -.canon-TextFieldInput:focus-visible { +.canon-TextFieldInput[data-focused] { outline-color: var(--canon-border-pressed); - border-color: var(--canon-border-pressed); outline-width: 0; } -.canon-TextFieldInput[data-invalid] { +.canon-TextFieldInputWrapper:has( > .canon-TextFieldInput:hover) { + border-color: var(--canon-border-hover); +} + +.canon-TextField[data-focused] .canon-TextFieldInputWrapper { + border-color: var(--canon-border-pressed); +} + +.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { border-color: var(--canon-fg-danger); } -.canon-TextFieldInput[data-disabled] { +.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { opacity: .5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldInput[data-size="small"] { +.canon-TextField[data-disabled] .canon-TextFieldClearButton { + cursor: inherit; +} + +.canon-TextFieldInputWrapper[data-size="small"] { height: 2rem; } -.canon-TextFieldInput[data-size="medium"] { +.canon-TextFieldInputWrapper[data-size="medium"] { height: 2.5rem; } diff --git a/packages/canon/package.json b/packages/canon/package.json index 04f9bbdb8f..c63e307453 100644 --- a/packages/canon/package.json +++ b/packages/canon/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/canon", - "version": "0.4.0-next.1", + "version": "0.4.0-next.2", "backstage": { "role": "web-library" }, @@ -41,7 +41,7 @@ "test": "backstage-cli package test" }, "dependencies": { - "@base-ui-components/react": "^1.0.0-alpha.7", + "@base-ui-components/react": "1.0.0-alpha.7", "@remixicon/react": "^4.6.0", "@tanstack/react-table": "^8.21.3", "clsx": "^2.1.1" diff --git a/packages/canon/report.api.md b/packages/canon/report.api.md index 765a77148f..da1b5015b4 100644 --- a/packages/canon/report.api.md +++ b/packages/canon/report.api.md @@ -744,6 +744,7 @@ export type IconNames = | 'chevron-left' | 'chevron-right' | 'chevron-up' + | 'close' | 'cloud' | 'code' | 'discord' @@ -759,6 +760,7 @@ export type IconNames = | 'heart' | 'moon' | 'plus' + | 'search' | 'sidebar-fold' | 'sidebar-unfold' | 'sparkling' @@ -1218,8 +1220,10 @@ export interface TextFieldProps className?: string; description?: string; error?: string | null; + icon?: IconNames; label?: string; name: string; + onClear?: React.MouseEventHandler; size?: 'small' | 'medium' | Partial>; } diff --git a/packages/canon/src/components/Icon/icons.ts b/packages/canon/src/components/Icon/icons.ts index 615c918db7..e43cc2e7d6 100644 --- a/packages/canon/src/components/Icon/icons.ts +++ b/packages/canon/src/components/Icon/icons.ts @@ -72,6 +72,8 @@ import { RiGithubLine, RiDiscordLine, RiYoutubeLine, + RiCloseLine, + RiSearchLine, } from '@remixicon/react'; /** @public */ @@ -103,6 +105,7 @@ export const icons: IconMap = { 'chevron-left': RiArrowLeftSLine, 'chevron-right': RiArrowRightSLine, 'chevron-up': RiArrowUpSLine, + close: RiCloseLine, cloud: RiCloudLine, code: RiCodeLine, discord: RiDiscordLine, @@ -118,6 +121,7 @@ export const icons: IconMap = { heart: RiHeartLine, moon: RiMoonLine, plus: RiAddLine, + search: RiSearchLine, 'sidebar-fold': RiSidebarFoldLine, 'sidebar-unfold': RiSidebarUnfoldLine, sparkling: RiSparklingLine, diff --git a/packages/canon/src/components/Icon/types.ts b/packages/canon/src/components/Icon/types.ts index 4870a1afc4..e97ea9c454 100644 --- a/packages/canon/src/components/Icon/types.ts +++ b/packages/canon/src/components/Icon/types.ts @@ -46,6 +46,7 @@ export type IconNames = | 'chevron-left' | 'chevron-right' | 'chevron-up' + | 'close' | 'cloud' | 'code' | 'discord' @@ -61,6 +62,7 @@ export type IconNames = | 'heart' | 'moon' | 'plus' + | 'search' | 'sidebar-fold' | 'sidebar-unfold' | 'sparkling' diff --git a/packages/canon/src/components/TextField/TextField.stories.tsx b/packages/canon/src/components/TextField/TextField.stories.tsx index 8594a8c650..0615a157a1 100644 --- a/packages/canon/src/components/TextField/TextField.stories.tsx +++ b/packages/canon/src/components/TextField/TextField.stories.tsx @@ -109,3 +109,35 @@ export const WithErrorAndDescription: Story = { description: 'Description', }, }; + +export const WithIcon: Story = { + args: { + ...WithLabel.args, + placeholder: 'Search...', + icon: 'search', + }, +}; + +export const DisabledWithIcon: Story = { + args: { + ...WithIcon.args, + disabled: true, + }, +}; + +export const WithOnClear: Story = { + args: { + ...WithLabel.args, + placeholder: 'Search...', + type: 'search', + onClear: () => console.log('Cleared!'), + }, +}; + +export const DisabledWithOnClear: Story = { + args: { + ...WithOnClear.args, + defaultValue: 'Testing', + disabled: true, + }, +}; diff --git a/packages/canon/src/components/TextField/TextField.styles.css b/packages/canon/src/components/TextField/TextField.styles.css index 7ee0a937dc..ad3ccc931b 100644 --- a/packages/canon/src/components/TextField/TextField.styles.css +++ b/packages/canon/src/components/TextField/TextField.styles.css @@ -26,8 +26,10 @@ font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); margin-bottom: var(--canon-space-1_5); + margin-right: auto; cursor: pointer; } + .canon-TextFieldLabel[data-disabled] { cursor: default; } @@ -48,48 +50,101 @@ padding-top: var(--canon-space-1_5); } -.canon-TextFieldInput { +.canon-TextFieldInputWrapper { + display: flex; + align-items: center; + padding: 0 var(--canon-space-3); border-radius: var(--canon-radius-3); border: 1px solid var(--canon-border); - padding: 0 var(--canon-space-4); background-color: var(--canon-bg-surface-1); +} + +.canon-TextFieldInputIcon { + display: block; + padding-right: var(--canon-space-1); + width: 1.5rem; + height: 1.5rem; + color: var(--canon-fg-primary); + flex-shrink: 0; +} + +.canon-TextFieldInput { + border: none; + background: none; font-size: var(--canon-font-size-3); font-family: var(--canon-font-regular); font-weight: var(--canon-font-weight-regular); color: var(--canon-fg-primary); transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out; width: 100%; + height: 100%; + cursor: inherit; + padding: 0; +} + +.canon-TextFieldInput:not([data-filled]):has(+ .canon-TextFieldClearButton) { + padding-right: 1.25rem; +} + +.canon-TextFieldInput[type='search']::-webkit-search-cancel-button, +.canon-TextFieldInput[type='search']::-webkit-search-decoration { + appearance: none; +} + +.canon-TextFieldClearButton { + display: none; + padding: 0; + margin-left: var(--canon-space-1); + background: none; + border: none; + vertical-align: middle; + color: var(--canon-fg-primary); +} + +.canon-TextFieldInput[data-filled] + .canon-TextFieldClearButton { + display: inline-block; +} + +.canon-TextFieldClearButtonIcon { + display: block; } .canon-TextFieldInput::placeholder { color: var(--canon-fg-secondary); } -.canon-TextFieldInput:hover { +.canon-TextFieldInput[data-focused] { + outline-color: var(--canon-border-pressed); + outline-width: 0px; +} + +.canon-TextFieldInputWrapper:has(> .canon-TextFieldInput:hover) { border-color: var(--canon-border-hover); } -.canon-TextFieldInput:focus-visible { - outline-color: var(--canon-border-pressed); - outline-width: 0px; +.canon-TextField[data-focused] .canon-TextFieldInputWrapper { border-color: var(--canon-border-pressed); } -.canon-TextFieldInput[data-invalid] { +.canon-TextField[data-invalid] .canon-TextFieldInputWrapper { border-color: var(--canon-fg-danger); } -.canon-TextFieldInput[data-disabled] { +.canon-TextField[data-disabled] .canon-TextFieldInputWrapper { opacity: 0.5; cursor: not-allowed; border: 1px solid var(--canon-border-disabled); } -.canon-TextFieldInput[data-size='small'] { +.canon-TextField[data-disabled] .canon-TextFieldClearButton { + cursor: inherit; +} + +.canon-TextFieldInputWrapper[data-size='small'] { height: 2rem; } -.canon-TextFieldInput[data-size='medium'] { +.canon-TextFieldInputWrapper[data-size='medium'] { height: 2.5rem; } diff --git a/packages/canon/src/components/TextField/TextField.tsx b/packages/canon/src/components/TextField/TextField.tsx index db56463c3a..bb32033da4 100644 --- a/packages/canon/src/components/TextField/TextField.tsx +++ b/packages/canon/src/components/TextField/TextField.tsx @@ -14,12 +14,13 @@ * limitations under the License. */ -import { useId, forwardRef } from 'react'; -import { Input } from '@base-ui-components/react/input'; +import { Field } from '@base-ui-components/react/field'; +import { forwardRef } from 'react'; import { useResponsiveValue } from '../../hooks/useResponsiveValue'; import clsx from 'clsx'; import type { TextFieldProps } from './types'; +import { Icon } from '../Icon'; /** @public */ export const TextField = forwardRef( @@ -33,62 +34,60 @@ export const TextField = forwardRef( required, style, disabled, + icon, + onClear, ...rest } = props; // Get the responsive value for the variant const responsiveSize = useResponsiveValue(size); - // Generate unique IDs for accessibility - const inputId = useId(); - const descriptionId = useId(); - const errorId = useId(); - return ( -
{label && ( - + )} - +
+ {icon && } + + {onClear && ( + + )} +
{description && ( -

+ {description} -

+ )} {error && ( - + )} -
+ ); }, ); diff --git a/packages/canon/src/components/TextField/types.ts b/packages/canon/src/components/TextField/types.ts index 147d398c99..8111749845 100644 --- a/packages/canon/src/components/TextField/types.ts +++ b/packages/canon/src/components/TextField/types.ts @@ -15,6 +15,7 @@ */ import type { Breakpoint } from '../../types'; +import type { IconNames } from '../Icon'; /** @public */ export interface TextFieldProps @@ -49,4 +50,14 @@ export interface TextFieldProps * The error message of the text field */ error?: string | null; + + /** + * An icon to render before the input + */ + icon?: IconNames; + + /** + * Handler to call when the clear button is pressed + */ + onClear?: React.MouseEventHandler; } diff --git a/packages/catalog-client/catalog-info.yaml b/packages/catalog-client/catalog-info.yaml index 91a4cb5504..5dc3df2ec8 100644 --- a/packages/catalog-client/catalog-info.yaml +++ b/packages/catalog-client/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: catalog-maintainers diff --git a/packages/catalog-model/catalog-info.yaml b/packages/catalog-model/catalog-info.yaml index 7b2bce99b9..e314e43dc5 100644 --- a/packages/catalog-model/catalog-info.yaml +++ b/packages/catalog-model/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: catalog-maintainers diff --git a/packages/cli-common/catalog-info.yaml b/packages/cli-common/catalog-info.yaml index bc1f589fc1..e60377a809 100644 --- a/packages/cli-common/catalog-info.yaml +++ b/packages/cli-common/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: tooling-maintainers diff --git a/packages/cli-node/catalog-info.yaml b/packages/cli-node/catalog-info.yaml index 3497007bfa..10b59eb3e0 100644 --- a/packages/cli-node/catalog-info.yaml +++ b/packages/cli-node/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: tooling-maintainers diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 8c5fb14d96..faddf38926 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/cli +## 0.32.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/cli-node@0.2.13 + - @backstage/config@1.3.2 + - @backstage/release-manifests@0.0.12 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/eslint-plugin@0.1.10 + - @backstage/types@1.2.1 + ## 0.32.1-next.1 ### Patch Changes diff --git a/packages/cli/catalog-info.yaml b/packages/cli/catalog-info.yaml index 212ba472d4..cec4ea5c65 100644 --- a/packages/cli/catalog-info.yaml +++ b/packages/cli/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: tooling-maintainers diff --git a/packages/cli/package.json b/packages/cli/package.json index 1be77acd70..616556b5b6 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/cli", - "version": "0.32.1-next.1", + "version": "0.32.1-next.2", "description": "CLI for developing Backstage plugins and apps", "backstage": { "role": "cli" diff --git a/packages/cli/src/lib/versioning/packages.test.ts b/packages/cli/src/lib/versioning/packages.test.ts index 23ff927c48..78fb1d1f5e 100644 --- a/packages/cli/src/lib/versioning/packages.test.ts +++ b/packages/cli/src/lib/versioning/packages.test.ts @@ -15,7 +15,7 @@ */ import * as runObj from '../run'; -import * as yarn from '../yarn'; +import * as yarn from './yarn'; import { fetchPackageInfo, mapDependencies } from './packages'; import { NotFoundError } from '../errors'; import { createMockDirectory } from '@backstage/backend-test-utils'; @@ -27,7 +27,7 @@ jest.mock('../run', () => { }; }); -jest.mock('../yarn', () => { +jest.mock('./yarn', () => { return { detectYarnVersion: jest.fn(), }; diff --git a/packages/cli/src/lib/versioning/packages.ts b/packages/cli/src/lib/versioning/packages.ts index 0499775bf2..7a75189a5f 100644 --- a/packages/cli/src/lib/versioning/packages.ts +++ b/packages/cli/src/lib/versioning/packages.ts @@ -17,7 +17,7 @@ import { minimatch } from 'minimatch'; import { getPackages } from '@manypkg/get-packages'; import { NotFoundError } from '../errors'; -import { detectYarnVersion } from '../yarn'; +import { detectYarnVersion } from './yarn'; import { execFile } from '../run'; const DEP_TYPES = [ diff --git a/packages/cli/src/lib/yarn.ts b/packages/cli/src/lib/versioning/yarn.ts similarity index 100% rename from packages/cli/src/lib/yarn.ts rename to packages/cli/src/lib/versioning/yarn.ts diff --git a/packages/cli/src/modules/build/commands/package/build/command.ts b/packages/cli/src/modules/build/commands/package/build/command.ts index 77e48841cf..d6a71b6fc1 100644 --- a/packages/cli/src/modules/build/commands/package/build/command.ts +++ b/packages/cli/src/modules/build/commands/package/build/command.ts @@ -21,7 +21,7 @@ import { PackageGraph, PackageRoles } from '@backstage/cli-node'; import { paths } from '../../../../../lib/paths'; import { buildFrontend } from '../../../lib/buildFrontend'; import { buildBackend } from '../../../lib/buildBackend'; -import { isValidUrl } from '../../../../../lib/urls'; +import { isValidUrl } from '../../../lib/urls'; import chalk from 'chalk'; export async function command(opts: OptionValues): Promise { diff --git a/packages/cli/src/lib/urls.test.ts b/packages/cli/src/modules/build/lib/urls.test.ts similarity index 100% rename from packages/cli/src/lib/urls.test.ts rename to packages/cli/src/modules/build/lib/urls.test.ts diff --git a/packages/cli/src/lib/urls.ts b/packages/cli/src/modules/build/lib/urls.ts similarity index 100% rename from packages/cli/src/lib/urls.ts rename to packages/cli/src/modules/build/lib/urls.ts diff --git a/packages/cli/src/modules/maintenance/commands/package/pack.ts b/packages/cli/src/modules/maintenance/commands/package/pack.ts index 510401c6d1..cdd50517ae 100644 --- a/packages/cli/src/modules/maintenance/commands/package/pack.ts +++ b/packages/cli/src/modules/maintenance/commands/package/pack.ts @@ -20,7 +20,7 @@ import { } from '../../../../modules/build/lib/packager/productionPack'; import { paths } from '../../../../lib/paths'; import fs from 'fs-extra'; -import { publishPreflightCheck } from '../../../../lib/publishing'; +import { publishPreflightCheck } from '../../lib/publishing'; import { createTypeDistProject } from '../../../../lib/typeDistProject'; export const pre = async () => { diff --git a/packages/cli/src/modules/maintenance/commands/repo/fix.ts b/packages/cli/src/modules/maintenance/commands/repo/fix.ts index 83f4da6114..231c822483 100644 --- a/packages/cli/src/modules/maintenance/commands/repo/fix.ts +++ b/packages/cli/src/modules/maintenance/commands/repo/fix.ts @@ -25,7 +25,7 @@ import { OptionValues } from 'commander'; import fs from 'fs-extra'; import { resolve as resolvePath, posix, relative as relativePath } from 'path'; import { paths } from '../../../../lib/paths'; -import { publishPreflightCheck } from '../../../../lib/publishing'; +import { publishPreflightCheck } from '../../lib/publishing'; /** * A mutable object representing a package.json file with potential fixes. diff --git a/packages/cli/src/lib/publishing.ts b/packages/cli/src/modules/maintenance/lib/publishing.ts similarity index 100% rename from packages/cli/src/lib/publishing.ts rename to packages/cli/src/modules/maintenance/lib/publishing.ts diff --git a/packages/cli/src/lib/codeowners/codeowners.test.ts b/packages/cli/src/modules/new/lib/codeowners/codeowners.test.ts similarity index 100% rename from packages/cli/src/lib/codeowners/codeowners.test.ts rename to packages/cli/src/modules/new/lib/codeowners/codeowners.test.ts diff --git a/packages/cli/src/lib/codeowners/codeowners.ts b/packages/cli/src/modules/new/lib/codeowners/codeowners.ts similarity index 98% rename from packages/cli/src/lib/codeowners/codeowners.ts rename to packages/cli/src/modules/new/lib/codeowners/codeowners.ts index 5734be95b8..32a38a71c7 100644 --- a/packages/cli/src/lib/codeowners/codeowners.ts +++ b/packages/cli/src/modules/new/lib/codeowners/codeowners.ts @@ -16,7 +16,7 @@ import fs from 'fs-extra'; import path from 'path'; -import { paths } from '../paths'; +import { paths } from '../../../../lib/paths'; const TEAM_ID_RE = /^@[-\w]+\/[-\w]+$/; const USER_ID_RE = /^@[-\w]+$/; diff --git a/packages/cli/src/lib/codeowners/index.ts b/packages/cli/src/modules/new/lib/codeowners/index.ts similarity index 100% rename from packages/cli/src/lib/codeowners/index.ts rename to packages/cli/src/modules/new/lib/codeowners/index.ts diff --git a/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts b/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts index cb076f3aa0..6d34e35e4c 100644 --- a/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts +++ b/packages/cli/src/modules/new/lib/execution/executePortableTemplate.ts @@ -15,8 +15,8 @@ */ import { assertError } from '@backstage/errors'; -import { addCodeownersEntry } from '../../../../lib/codeowners'; -import { Task } from '../../../../lib/tasks'; +import { addCodeownersEntry } from '../codeowners'; +import { Task } from '../tasks'; import { PortableTemplate, PortableTemplateConfig, diff --git a/packages/cli/src/modules/new/lib/execution/installNewPackage.ts b/packages/cli/src/modules/new/lib/execution/installNewPackage.ts index 733a688bfb..0fe0284386 100644 --- a/packages/cli/src/modules/new/lib/execution/installNewPackage.ts +++ b/packages/cli/src/modules/new/lib/execution/installNewPackage.ts @@ -17,7 +17,7 @@ import fs from 'fs-extra'; import upperFirst from 'lodash/upperFirst'; import camelCase from 'lodash/camelCase'; import { paths } from '../../../../lib/paths'; -import { Task } from '../../../../lib/tasks'; +import { Task } from '../tasks'; import { PortableTemplateInput } from '../types'; export async function installNewPackage(input: PortableTemplateInput) { diff --git a/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts b/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts index 23a30721b0..38e8eaddba 100644 --- a/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts +++ b/packages/cli/src/modules/new/lib/preparation/collectPortableTemplateInput.ts @@ -15,10 +15,7 @@ */ import inquirer, { DistinctQuestion } from 'inquirer'; -import { - getCodeownersFilePath, - parseOwnerIds, -} from '../../../../lib/codeowners'; +import { getCodeownersFilePath, parseOwnerIds } from '../codeowners'; import { paths } from '../../../../lib/paths'; import { PortableTemplateConfig, diff --git a/packages/cli/src/lib/tasks.ts b/packages/cli/src/modules/new/lib/tasks.ts similarity index 100% rename from packages/cli/src/lib/tasks.ts rename to packages/cli/src/modules/new/lib/tasks.ts diff --git a/packages/cli/src/modules/start/commands/package/start/startBackend.ts b/packages/cli/src/modules/start/commands/package/start/startBackend.ts index 417398e9f9..a09a2cfe70 100644 --- a/packages/cli/src/modules/start/commands/package/start/startBackend.ts +++ b/packages/cli/src/modules/start/commands/package/start/startBackend.ts @@ -17,7 +17,7 @@ import fs from 'fs-extra'; import { resolve as resolvePath } from 'path'; import { paths } from '../../../../../lib/paths'; -import { runBackend } from '../../../../../lib/runner'; +import { runBackend } from '../../../lib/runner'; interface StartBackendOptions { targetDir: string; diff --git a/packages/cli/src/lib/ipc/IpcServer.ts b/packages/cli/src/modules/start/lib/ipc/IpcServer.ts similarity index 100% rename from packages/cli/src/lib/ipc/IpcServer.ts rename to packages/cli/src/modules/start/lib/ipc/IpcServer.ts diff --git a/packages/cli/src/lib/ipc/ServerDataStore.ts b/packages/cli/src/modules/start/lib/ipc/ServerDataStore.ts similarity index 100% rename from packages/cli/src/lib/ipc/ServerDataStore.ts rename to packages/cli/src/modules/start/lib/ipc/ServerDataStore.ts diff --git a/packages/cli/src/lib/ipc/index.ts b/packages/cli/src/modules/start/lib/ipc/index.ts similarity index 100% rename from packages/cli/src/lib/ipc/index.ts rename to packages/cli/src/modules/start/lib/ipc/index.ts diff --git a/packages/cli/src/lib/runner/index.ts b/packages/cli/src/modules/start/lib/runner/index.ts similarity index 100% rename from packages/cli/src/lib/runner/index.ts rename to packages/cli/src/modules/start/lib/runner/index.ts diff --git a/packages/cli/src/lib/runner/runBackend.ts b/packages/cli/src/modules/start/lib/runner/runBackend.ts similarity index 99% rename from packages/cli/src/lib/runner/runBackend.ts rename to packages/cli/src/modules/start/lib/runner/runBackend.ts index ebb1d535b3..49dccf804f 100644 --- a/packages/cli/src/lib/runner/runBackend.ts +++ b/packages/cli/src/modules/start/lib/runner/runBackend.ts @@ -21,7 +21,7 @@ import { IpcServer, ServerDataStore } from '../ipc'; import debounce from 'lodash/debounce'; import { fileURLToPath } from 'url'; import { isAbsolute as isAbsolutePath } from 'path'; -import { paths } from '../paths'; +import { paths } from '../../../../lib/paths'; import spawn from 'cross-spawn'; const loaderArgs = [ diff --git a/packages/codemods/catalog-info.yaml b/packages/codemods/catalog-info.yaml index 8e5739bd1a..3d745bb7f6 100644 --- a/packages/codemods/catalog-info.yaml +++ b/packages/codemods/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: framework-maintainers diff --git a/packages/config-loader/catalog-info.yaml b/packages/config-loader/catalog-info.yaml index 4dfe9c1e59..6930572b07 100644 --- a/packages/config-loader/catalog-info.yaml +++ b/packages/config-loader/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/config/catalog-info.yaml b/packages/config/catalog-info.yaml index c077d9d47b..363390d607 100644 --- a/packages/config/catalog-info.yaml +++ b/packages/config/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 34fb45655d..39a4f5b63b 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-app-api +## 1.16.2-next.0 + +### Patch Changes + +- 73f6cc3: Updated `I18nextTranslationApi` to support interpolation of JSX elements. +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 1.16.1 ### Patch Changes diff --git a/packages/core-app-api/catalog-info.yaml b/packages/core-app-api/catalog-info.yaml index 628c5a36d6..a0b76f2dbb 100644 --- a/packages/core-app-api/catalog-info.yaml +++ b/packages/core-app-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index d226dbccb0..be8f0592eb 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-app-api", - "version": "1.16.1", + "version": "1.16.2-next.0", "description": "Core app API used by Backstage apps", "backstage": { "role": "web-library" diff --git a/packages/core-app-api/report.api.md b/packages/core-app-api/report.api.md index 60f8cb8c1c..445d73392d 100644 --- a/packages/core-app-api/report.api.md +++ b/packages/core-app-api/report.api.md @@ -294,6 +294,28 @@ export type AuthApiCreateOptions = { configApi?: ConfigApi; }; +// @public +export type AuthConnector = { + createSession( + options: AuthConnectorCreateSessionOptions, + ): Promise; + refreshSession( + options?: AuthConnectorRefreshSessionOptions, + ): Promise; + removeSession(): Promise; +}; + +// @public (undocumented) +export type AuthConnectorCreateSessionOptions = { + scopes: Set; + instantPopup?: boolean; +}; + +// @public (undocumented) +export type AuthConnectorRefreshSessionOptions = { + scopes: Set; +}; + // @public export type BackstageApp = { getPlugins(): BackstagePlugin[]; @@ -523,7 +545,9 @@ export class OAuth2 SessionApi { // (undocumented) - static create(options: OAuth2CreateOptions): OAuth2; + static create( + options: OAuth2CreateOptions | OAuth2CreateOptionsWithAuthConnector, + ): OAuth2; // (undocumented) getAccessToken( scope?: string | string[], @@ -538,6 +562,13 @@ export class OAuth2 // (undocumented) getProfile(options?: AuthRequestOptions): Promise; // (undocumented) + static normalizeScopes( + scopes?: string | string[], + options?: { + scopeTransform: (scopes: string[]) => string[]; + }, + ): Set; + // (undocumented) sessionState$(): Observable; // (undocumented) signIn(): Promise; @@ -551,6 +582,13 @@ export type OAuth2CreateOptions = OAuthApiCreateOptions & { popupOptions?: PopupOptions; }; +// @public +export type OAuth2CreateOptionsWithAuthConnector = { + scopeTransform?: (scopes: string[]) => string[]; + defaultScopes?: string[]; + authConnector: AuthConnector; +}; + // @public export type OAuth2Session = { providerInfo: { @@ -600,6 +638,19 @@ export type OneLoginAuthCreateOptions = { provider?: AuthProviderInfo; }; +// @public +export function openLoginPopup( + options: OpenLoginPopupOptions, +): Promise; + +// @public +export type OpenLoginPopupOptions = { + url: string; + name: string; + width?: number; + height?: number; +}; + // @public export type PopupOptions = { size?: diff --git a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.ts b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx similarity index 91% rename from packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.ts rename to packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx index d5b812c5d7..b6e79b20ed 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.ts +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.test.tsx @@ -23,6 +23,7 @@ import { import { Observable } from '@backstage/types'; import { AppLanguageSelector } from '../AppLanguageApi'; import { I18nextTranslationApi } from './I18nextTranslationApi'; +import { render } from '@testing-library/react'; const plainRef = createTranslationRef({ id: 'plain', @@ -538,5 +539,67 @@ describe('I18nextTranslationApi', () => { expect(snapshot.t('derpWithCount', { count: 2 })).toBe('2 derps'); expect(snapshot.t('derpWithCount', { count: 0 })).toBe('0 derps'); }); + + it('should support jsx interpolation', () => { + const snapshot = snapshotWithMessages({ + jsx: '{{ hello }}, {{ world }}!', + jsxMultiple: '{{ hello }} | {{ hello }}', + jsxNested: '$t(foo), $t(bar)', + foo: 'foo={{ foo }}', + bar: 'bar={{ bar }}', + }); + + expect( + render( + snapshot.t('jsx', { + hello:

Hello

, + world:
World
, + }), + ).container.textContent, + ).toBe('Hello, World!'); + + expect( + render( + snapshot.t('jsx', { + hello:

world

, + world:
hello
, + }), + ).container.textContent, + ).toBe('world, hello!'); + + // Missing value + expect( + render( + snapshot.t('jsx', { + hello:

hello

, + } as any), + ).container.textContent, + ).toBe('hello, {{ world }}!'); + + expect( + render( + snapshot.t('jsxMultiple', { + hello:

hello

, + } as any), + ).container.textContent, + ).toBe('hello | hello'); + + expect( + render( + snapshot.t('jsxNested', { + foo: ( +
+ foo +
+ ), + bar: ( +
+ bar +
+ ), + }), + ).container.textContent, + ).toBe('foo=foo, bar=bar'); + }); }); }); diff --git a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts index d0ef35568f..de21990cb3 100644 --- a/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts +++ b/packages/core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi.ts @@ -23,7 +23,13 @@ import { TranslationResource, TranslationSnapshot, } from '@backstage/core-plugin-api/alpha'; -import { createInstance as createI18n, type i18n as I18n } from 'i18next'; +import { + createInstance as createI18n, + FormatFunction, + Interpolator, + TFunction, + type i18n as I18n, +} from 'i18next'; import ObservableImpl from 'zen-observable'; // Internal import to avoid code duplication, this will lead to duplication in build output @@ -39,6 +45,7 @@ import { } from '../../../../../core-plugin-api/src/translation/TranslationRef'; import { Observable } from '@backstage/types'; import { DEFAULT_LANGUAGE } from '../AppLanguageApi/AppLanguageSelector'; +import { createElement, Fragment, ReactNode, isValidElement } from 'react'; /** @alpha */ export interface I18nextTranslationApiOptions { @@ -137,6 +144,98 @@ class ResourceLoader { } } +/** + * A helper for implementing JSX interpolation + */ +export class JsxInterpolator { + readonly #setFormatHook: (hook: FormatFunction) => void; + readonly #marker: string; + readonly #pattern: RegExp; + + static fromI18n(i18n: I18n) { + const interpolator = i18n.services.interpolator as Interpolator & { + format: FormatFunction; + }; + const originalFormat = interpolator.format; + + let formatHook: FormatFunction | undefined; + + // This is the only way to override the format function of the interpolator + // without overriding the default formatters. See the behavior here: + // https://github.com/i18next/i18next/blob/c633121e57e2b6024080142d78027842bf2a6e5e/src/i18next.js#L120-L125 + interpolator.format = (value, format, lng, formatOpts) => { + if (format) { + return originalFormat(value, format, lng, formatOpts); + } + return formatHook?.(value, format, lng, formatOpts) ?? value; + }; + + return new JsxInterpolator( + // Using a random marker to ensure it can't be misused + Math.random().toString(36).substring(2, 8), + hook => { + formatHook = hook; + }, + ); + } + + private constructor( + marker: string, + setFormatHook: (hook: FormatFunction) => void, + ) { + this.#setFormatHook = setFormatHook; + this.#marker = marker; + this.#pattern = new RegExp(`\\$${marker}\\(([^)]+)\\)`); + } + + wrapT( + originalT: TFunction, + ): TranslationFunction { + return ((key, options) => { + let elementsMap: Map | undefined = undefined; + + // There's no way to override the format hook via the translation function + // options, event though types indicate that it might be possible. + // Instead, override the format function hook before every invocation and + // rely on synchronous execution. + this.#setFormatHook(value => { + if (isValidElement(value)) { + if (!elementsMap) { + elementsMap = new Map(); + } + const elementKey = elementsMap.size.toString(); + elementsMap.set(elementKey, value); + + return `$${this.#marker}(${elementKey})`; + } + return value; + }); + + // Overriding the return options is not allowed via TranslationFunction, + // so this will always be a string + const result = originalT(key, options as any) as unknown as string; + if (!elementsMap) { + return result; + } + + const split = result.split(this.#pattern); + + return createElement( + Fragment, + null, + ...split + .map((part, index) => { + if (index % 2 === 0) { + return part; + } + return elementsMap?.get(part); + }) + .filter(Boolean), + ); + }) as TranslationFunction; + } +} + /** @alpha */ export class I18nextTranslationApi implements TranslationApi { static create(options: I18nextTranslationApiOptions) { @@ -147,6 +246,8 @@ export class I18nextTranslationApi implements TranslationApi { supportedLngs: languages, interpolation: { escapeValue: false, + // Used for the JsxInterpolator format hook + alwaysFormat: true, }, ns: [], defaultNS: false, @@ -161,6 +262,8 @@ export class I18nextTranslationApi implements TranslationApi { throw new Error('i18next was unexpectedly not initialized'); } + const interpolator = JsxInterpolator.fromI18n(i18n); + const { language: initialLanguage } = options.languageApi.getLanguage(); if (initialLanguage !== DEFAULT_LANGUAGE) { i18n.changeLanguage(initialLanguage); @@ -198,6 +301,7 @@ export class I18nextTranslationApi implements TranslationApi { i18n, loader, options.languageApi.getLanguage().language, + interpolator, ); options.languageApi.language$().subscribe(({ language }) => { @@ -210,16 +314,23 @@ export class I18nextTranslationApi implements TranslationApi { #i18n: I18n; #loader: ResourceLoader; #language: string; + #jsxInterpolator: JsxInterpolator; /** Keep track of which refs we have registered default resources for */ #registeredRefs = new Set(); /** Notify observers when language changes */ #languageChangeListeners = new Set<() => void>(); - private constructor(i18n: I18n, loader: ResourceLoader, language: string) { + private constructor( + i18n: I18n, + loader: ResourceLoader, + language: string, + jsxInterpolator: JsxInterpolator, + ) { this.#i18n = i18n; this.#loader = loader; this.#language = language; + this.#jsxInterpolator = jsxInterpolator; } getTranslation( @@ -297,10 +408,8 @@ export class I18nextTranslationApi implements TranslationApi { return { ready: false }; } - const t = this.#i18n.getFixedT( - null, - internalRef.id, - ) as TranslationFunction; + const unwrappedT = this.#i18n.getFixedT(null, internalRef.id); + const t = this.#jsxInterpolator.wrapT(unwrappedT); return { ready: true, diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts index a34589d746..018f068a62 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts @@ -18,6 +18,13 @@ import OAuth2 from './OAuth2'; import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; import { UrlPatternDiscovery } from '../../DiscoveryApi'; import { mockApis } from '@backstage/test-utils'; +import { + OAuth2Session, + AuthConnector, + AuthConnectorRefreshSessionOptions, + openLoginPopup, + OAuth2CreateOptionsWithAuthConnector, +} from '../../../../index'; const theFuture = new Date(Date.now() + 3600000); const thePast = new Date(Date.now() - 10); @@ -37,6 +44,25 @@ jest.mock('../../../../lib/AuthSessionManager', () => ({ const configApi = mockApis.config(); +class CustomAuthConnector implements AuthConnector { + async createSession() { + const s: OAuth2Session = { + providerInfo: { + idToken: '', + accessToken: 'accessToken', + scopes: new Set(['myScope']), + }, + profile: {}, + }; + await openLoginPopup({ url: 'http://localhost', name: 'myPopup' }); + return Promise.resolve(s); + } + + async refreshSession(_?: AuthConnectorRefreshSessionOptions): Promise {} + + async removeSession(): Promise {} +} + describe('OAuth2', () => { it('should get refreshed access token', async () => { getSession = jest.fn().mockResolvedValue({ @@ -215,4 +241,25 @@ describe('OAuth2', () => { await expect(promise3).resolves.toBe('token2'); expect(getSession).toHaveBeenCalledTimes(4); // De-duping of session requests happens in client }); + it('should use provided auth provider', async () => { + getSession = jest.fn().mockResolvedValue({ + providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, + }); + + const customAuthConnector = new CustomAuthConnector(); + + const options: OAuth2CreateOptionsWithAuthConnector = { + scopeTransform, + defaultScopes: ['myScope'], + authConnector: customAuthConnector, + }; + const oauth2 = OAuth2.create(options); + + expect(await oauth2.getAccessToken('my-scope my-scope2')).toBe( + 'access-token', + ); + expect(getSession).toHaveBeenCalledWith( + expect.objectContaining({ scopes: new Set(['my-scope', 'my-scope2']) }), + ); + }); }); diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts index 4963330724..92747a1707 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts @@ -14,35 +14,32 @@ * limitations under the License. */ -import { - DefaultAuthConnector, - PopupOptions, -} from '../../../../lib/AuthConnector'; +import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { AuthRequestOptions, + BackstageIdentityApi, BackstageIdentityResponse, + BackstageUserIdentity, OAuthApi, OpenIdConnectApi, ProfileInfo, ProfileInfoApi, - SessionState, SessionApi, - BackstageIdentityApi, - BackstageUserIdentity, + SessionState, } from '@backstage/core-plugin-api'; import { Observable } from '@backstage/types'; -import { OAuth2Session } from './types'; -import { OAuthApiCreateOptions } from '../types'; +import { + OAuth2CreateOptions, + OAuth2CreateOptionsWithAuthConnector, + OAuth2Session, +} from './types'; -/** - * OAuth2 create options. - * @public - */ -export type OAuth2CreateOptions = OAuthApiCreateOptions & { - scopeTransform?: (scopes: string[]) => string[]; - popupOptions?: PopupOptions; +const DEFAULT_PROVIDER = { + id: 'oauth2', + title: 'Your Identity Provider', + icon: () => null, }; export type OAuth2Response = { @@ -60,12 +57,6 @@ export type OAuth2Response = { }; }; -const DEFAULT_PROVIDER = { - id: 'oauth2', - title: 'Your Identity Provider', - icon: () => null, -}; - /** * Implements a generic OAuth2 flow for auth. * @@ -79,19 +70,23 @@ export default class OAuth2 BackstageIdentityApi, SessionApi { - static create(options: OAuth2CreateOptions) { + private static createAuthConnector( + options: OAuth2CreateOptions | OAuth2CreateOptionsWithAuthConnector, + ) { + if ('authConnector' in options) { + return options.authConnector; + } const { + scopeTransform = x => x, configApi, discoveryApi, environment = 'development', provider = DEFAULT_PROVIDER, oauthRequestApi, - defaultScopes = [], - scopeTransform = x => x, popupOptions, } = options; - const connector = new DefaultAuthConnector({ + return new DefaultAuthConnector({ configApi, discoveryApi, environment, @@ -106,10 +101,9 @@ export default class OAuth2 providerInfo: { idToken: res.providerInfo.idToken, accessToken: res.providerInfo.accessToken, - scopes: OAuth2.normalizeScopes( + scopes: OAuth2.normalizeScopes(res.providerInfo.scope, { scopeTransform, - res.providerInfo.scope, - ), + }), expiresAt: res.providerInfo.expiresInSeconds ? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1000) : undefined, @@ -128,6 +122,14 @@ export default class OAuth2 }, popupOptions, }); + } + + static create( + options: OAuth2CreateOptions | OAuth2CreateOptionsWithAuthConnector, + ) { + const { defaultScopes = [], scopeTransform = x => x } = options; + + const connector = OAuth2.createAuthConnector(options); const sessionManager = new RefreshingAuthSessionManager({ connector, @@ -182,7 +184,9 @@ export default class OAuth2 scope?: string | string[], options?: AuthRequestOptions, ) { - const normalizedScopes = OAuth2.normalizeScopes(this.scopeTransform, scope); + const normalizedScopes = OAuth2.normalizeScopes(scope, { + scopeTransform: this.scopeTransform, + }); const session = await this.sessionManager.getSession({ ...options, scopes: normalizedScopes, @@ -210,9 +214,12 @@ export default class OAuth2 return session?.profile; } - private static normalizeScopes( - scopeTransform: (scopes: string[]) => string[], + /** + * @public + */ + public static normalizeScopes( scopes?: string | string[], + options?: { scopeTransform: (scopes: string[]) => string[] }, ): Set { if (!scopes) { return new Set(); @@ -222,6 +229,10 @@ export default class OAuth2 ? scopes : scopes.split(/[\s|,]/).filter(Boolean); - return new Set(scopeTransform(scopeList)); + const transformedScopes = options + ? options.scopeTransform(scopeList) + : scopeList; + + return new Set(transformedScopes); } } diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts new file mode 100644 index 0000000000..a3188c9a80 --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2CustomAuthConnector.test.ts @@ -0,0 +1,141 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import OAuth2 from './OAuth2'; +import { + OAuth2Session, + AuthConnector, + AuthConnectorRefreshSessionOptions, + openLoginPopup, + OAuth2CreateOptionsWithAuthConnector, +} from '../../../../index'; +import { waitFor } from '@testing-library/react'; +import { BackstageUserIdentity, ProfileInfo } from '@backstage/core-plugin-api'; + +const scopeTransform = (x: string[]) => x; + +type Options = { + /** + * Function used to transform an auth response into the session type. + */ + sessionTransform?(response: any): OAuth2Session | Promise; +}; + +/** + * A replica of private `OAuth2Response` from OAuth2.ts. + * `OAuth2Response` represents raw OAuth2 response from the `auth-backend` plugin. + * If custom auth connector calls `auth-backend` plugin, it will have to transform `OAuth2Response` into + * `OAuth2Session`. + */ +type OAuth2Response = { + providerInfo: { + accessToken: string; + idToken: string; + scope: string; + expiresInSeconds?: number; + }; + profile: ProfileInfo; + backstageIdentity: { + token: string; + expiresInSeconds?: number; + identity: BackstageUserIdentity; + }; +}; + +class CustomAuthConnector implements AuthConnector { + private readonly sessionTransform: (response: any) => Promise; + + constructor(options: Options) { + const { sessionTransform = id => id } = options; + + this.sessionTransform = sessionTransform; + } + + async createSession() { + return await this.sessionTransform( + await openLoginPopup({ url: 'http://my-origin', name: 'myPopup' }), + ); + } + + async refreshSession(_?: AuthConnectorRefreshSessionOptions): Promise {} + + async removeSession(): Promise {} +} + +describe('OAuth2CustomAuthConnector', () => { + it('should use custom auth connector', async () => { + const popupMock = { closed: false }; + + jest.spyOn(window, 'open').mockReturnValue(popupMock as Window); + + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + jest.spyOn(window, 'removeEventListener'); + + const customAuthConnector = new CustomAuthConnector({ + sessionTransform(res: OAuth2Response): OAuth2Session { + return { + ...res, + providerInfo: { + idToken: res.providerInfo.idToken, + accessToken: res.providerInfo.accessToken, + scopes: OAuth2.normalizeScopes(res.providerInfo.scope, { + scopeTransform, + }), + expiresAt: res.providerInfo.expiresInSeconds + ? new Date(Date.now() + res.providerInfo.expiresInSeconds * 1000) + : undefined, + }, + }; + }, + }); + + const options: OAuth2CreateOptionsWithAuthConnector = { + scopeTransform, + defaultScopes: ['myScope'], + authConnector: customAuthConnector, + }; + const oauth2 = OAuth2.create(options); + + const accessToken = oauth2.getAccessToken('myScope'); + + // wait until `openLoginPopup` has been called + await waitFor(() => expect(addEventListenerSpy).toHaveBeenCalled()); + + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + + const accessTokenValue = 'myAccessToken'; + const myResponse = { + providerInfo: { + accessToken: accessTokenValue, + scope: 'myScope', + expiresInSeconds: 900, + }, + profile: { displayName: 'John Doe' }, + }; + + // A valid sessions response + listener({ + source: popupMock, + origin: 'http://my-origin', + data: { + type: 'authorization_response', + response: myResponse, + }, + } as MessageEvent); + + return expect(accessToken).resolves.toBe(accessTokenValue); + }); +}); diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts index e51de63dbe..651113fd4d 100644 --- a/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts +++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/types.ts @@ -15,11 +15,12 @@ */ import { - ProfileInfo, BackstageIdentityResponse, + ProfileInfo, } from '@backstage/core-plugin-api'; +import { OAuthApiCreateOptions } from '../types.ts'; +import { AuthConnector, PopupOptions } from '../../../../lib'; -export type { OAuth2CreateOptions } from './OAuth2'; export type { PopupOptions } from '../../../../lib/AuthConnector'; /** * Session information for generic OAuth2 auth. @@ -36,3 +37,22 @@ export type OAuth2Session = { profile: ProfileInfo; backstageIdentity?: BackstageIdentityResponse; }; + +/** + * OAuth2 create options. + * @public + */ +export type OAuth2CreateOptions = OAuthApiCreateOptions & { + scopeTransform?: (scopes: string[]) => string[]; + popupOptions?: PopupOptions; +}; + +/** + * OAuth2 create options with custom auth connector. + * @public + */ +export type OAuth2CreateOptionsWithAuthConnector = { + scopeTransform?: (scopes: string[]) => string[]; + defaultScopes?: string[]; + authConnector: AuthConnector; +}; diff --git a/packages/core-app-api/src/index.ts b/packages/core-app-api/src/index.ts index a5e6b649e9..a963b0cca9 100644 --- a/packages/core-app-api/src/index.ts +++ b/packages/core-app-api/src/index.ts @@ -23,3 +23,10 @@ export * from './apis'; export * from './app'; export * from './routing'; +export type { + AuthConnector, + AuthConnectorCreateSessionOptions, + AuthConnectorRefreshSessionOptions, + OpenLoginPopupOptions, +} from './lib'; +export { openLoginPopup } from './lib'; diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts index 60c4a3b8d9..192b1b7ac0 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -26,7 +26,7 @@ import { ConfigApi } from '@backstage/core-plugin-api'; jest.mock('../loginPopup', () => { return { - showLoginPopup: jest.fn(), + openLoginPopup: jest.fn(), }; }); @@ -74,7 +74,9 @@ describe('DefaultAuthConnector', () => { ); const connector = new DefaultAuthConnector(defaultOptions); - const session = await connector.refreshSession(new Set(['a', 'b', 'c'])); + const session = await connector.refreshSession({ + scopes: new Set(['a', 'b', 'c']), + }); expect(session.idToken).toBe('mock-id-token'); expect(session.accessToken).toBe('mock-access-token'); expect(session.scopes).toEqual(new Set(['a', 'b', 'c'])); @@ -118,7 +120,7 @@ describe('DefaultAuthConnector', () => { it('should create a session', async () => { const mockOauth = new MockOAuthApi(); const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue({ idToken: 'my-id-token', accessToken: 'my-access-token', @@ -151,7 +153,7 @@ describe('DefaultAuthConnector', () => { it('should instantly show popup if option is set', async () => { const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue('my-session'); const connector = new DefaultAuthConnector({ ...defaultOptions, @@ -169,7 +171,6 @@ describe('DefaultAuthConnector', () => { expect(popupSpy).toHaveBeenCalledTimes(1); expect(popupSpy).toHaveBeenCalledWith({ name: 'My Provider Login', - origin: 'http://my-host', url: 'http://my-host/api/auth/my-provider/start?scope=&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', width: 450, height: 730, @@ -178,7 +179,7 @@ describe('DefaultAuthConnector', () => { it('should show popup fullscreen', async () => { const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue('my-session'); jest.spyOn(window.screen, 'width', 'get').mockReturnValue(1000); @@ -205,7 +206,6 @@ describe('DefaultAuthConnector', () => { expect(popupSpy).toHaveBeenCalledWith({ height: 1000, name: 'My Provider Login', - origin: 'http://my-host', url: 'http://my-host/api/auth/my-provider/start?scope=&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', width: 1000, }); @@ -213,7 +213,7 @@ describe('DefaultAuthConnector', () => { it('should show popup with special width and height', async () => { const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue('my-session'); const connector = new DefaultAuthConnector({ ...defaultOptions, @@ -236,7 +236,6 @@ describe('DefaultAuthConnector', () => { expect(popupSpy).toHaveBeenCalledWith({ name: 'My Provider Login', - origin: 'http://my-host', url: 'http://my-host/api/auth/my-provider/start?scope=&origin=http%3A%2F%2Flocalhost&flow=popup&env=production', width: 500, height: 1000, @@ -246,7 +245,7 @@ describe('DefaultAuthConnector', () => { it('should use join func to join scopes', async () => { const mockOauth = new MockOAuthApi(); const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') + .spyOn(loginPopup, 'openLoginPopup') .mockResolvedValue({ scopes: '' }); const connector = new DefaultAuthConnector({ ...defaultOptions, diff --git a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts index ad1b25619d..3671ddc6e6 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.ts @@ -20,8 +20,13 @@ import { OAuthRequestApi, OAuthRequester, } from '@backstage/core-plugin-api'; -import { showLoginPopup } from '../loginPopup'; -import { AuthConnector, CreateSessionOptions, PopupOptions } from './types'; +import { openLoginPopup } from '../loginPopup'; +import { + AuthConnector, + AuthConnectorCreateSessionOptions, + PopupOptions, + AuthConnectorRefreshSessionOptions, +} from './types'; let warned = false; @@ -123,7 +128,9 @@ export class DefaultAuthConnector this.popupOptions = popupOptions; } - async createSession(options: CreateSessionOptions): Promise { + async createSession( + options: AuthConnectorCreateSessionOptions, + ): Promise { if (options.instantPopup) { if (this.enableExperimentalRedirectFlow) { return this.executeRedirect(options.scopes); @@ -133,11 +140,13 @@ export class DefaultAuthConnector return this.authRequester(options.scopes); } - async refreshSession(scopes?: Set): Promise { + async refreshSession( + options?: AuthConnectorRefreshSessionOptions, + ): Promise { const res = await fetch( await this.buildUrl('/refresh', { optional: true, - ...(scopes && { scope: this.joinScopesFunc(scopes) }), + ...(options && { scope: this.joinScopesFunc(options.scopes) }), }), { headers: { @@ -203,10 +212,9 @@ export class DefaultAuthConnector ? window.screen.height : this.popupOptions?.size?.height || 730; - const payload = await showLoginPopup({ + const payload = await openLoginPopup({ url: popupUrl, name: `${this.provider.title} Login`, - origin: new URL(popupUrl).origin, width, height, }); diff --git a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts index 200ba755ac..a45bfa0af9 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DirectAuthConnector.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { AuthProviderInfo, DiscoveryApi } from '@backstage/core-plugin-api'; -import { showLoginPopup } from '../loginPopup'; +import { openLoginPopup } from '../loginPopup'; type Options = { discoveryApi: DiscoveryApi; @@ -36,13 +36,12 @@ export class DirectAuthConnector { async createSession(): Promise { const popupUrl = await this.buildUrl('/start'); - const payload = await showLoginPopup({ + const payload = (await openLoginPopup({ url: popupUrl, name: `${this.provider.title} Login`, - origin: new URL(popupUrl).origin, width: 450, height: 730, - }); + })) as any; return { ...payload, diff --git a/packages/core-app-api/src/lib/AuthConnector/types.ts b/packages/core-app-api/src/lib/AuthConnector/types.ts index 3c01bb254f..035760d0aa 100644 --- a/packages/core-app-api/src/lib/AuthConnector/types.ts +++ b/packages/core-app-api/src/lib/AuthConnector/types.ts @@ -14,18 +14,34 @@ * limitations under the License. */ -export type CreateSessionOptions = { +/** + * @public + */ +export type AuthConnectorCreateSessionOptions = { scopes: Set; instantPopup?: boolean; }; +/** + * @public + */ +export type AuthConnectorRefreshSessionOptions = { + scopes: Set; +}; + /** * An AuthConnector is responsible for realizing auth session actions * by for example communicating with a backend or interacting with the user. + * + * @public */ export type AuthConnector = { - createSession(options: CreateSessionOptions): Promise; - refreshSession(scopes?: Set): Promise; + createSession( + options: AuthConnectorCreateSessionOptions, + ): Promise; + refreshSession( + options?: AuthConnectorRefreshSessionOptions, + ): Promise; removeSession(): Promise; }; diff --git a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts index 88000638e6..9afc710bdd 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts @@ -16,6 +16,7 @@ import { RefreshingAuthSessionManager } from './RefreshingAuthSessionManager'; import { SessionState } from '@backstage/core-plugin-api'; +import { AuthConnectorRefreshSessionOptions } from '../AuthConnector'; const defaultOptions = { sessionScopes: (session: { scopes: Set }) => session.scopes, @@ -47,7 +48,7 @@ describe('RefreshingAuthSessionManager', () => { await manager.getSession({}); expect(createSession).toHaveBeenCalledTimes(1); - expect(refreshSession).toHaveBeenCalledWith(new Set()); + expect(refreshSession).toHaveBeenCalledWith({ scopes: new Set() }); expect(stateSubscriber.mock.calls).toEqual([ [SessionState.SignedOut], [SessionState.SignedIn], @@ -103,7 +104,7 @@ describe('RefreshingAuthSessionManager', () => { await manager.getSession({ scopes: new Set(['a']) }); expect(createSession).toHaveBeenCalledTimes(1); - expect(refreshSession).toHaveBeenCalledWith(new Set(['a'])); + expect(refreshSession).toHaveBeenCalledWith({ scopes: new Set(['a']) }); await manager.getSession({ scopes: new Set(['a']) }); expect(createSession).toHaveBeenCalledTimes(1); @@ -134,7 +135,7 @@ describe('RefreshingAuthSessionManager', () => { expect(await manager.getSession({ optional: true })).toBe(undefined); expect(createSession).toHaveBeenCalledTimes(0); - expect(refreshSession).toHaveBeenCalledWith(new Set()); + expect(refreshSession).toHaveBeenCalledWith({ scopes: new Set() }); }); it('should forward option to instantly show auth popup and not attempt refresh', async () => { @@ -168,10 +169,12 @@ describe('RefreshingAuthSessionManager', () => { it('should handle two simultaneous session refreshes with same scopes', async () => { const createSession = jest.fn(); - const refreshSession = jest.fn(async (scopes?: Set) => ({ - scopes: scopes ?? new Set(), - expired: false, - })); + const refreshSession = jest.fn( + async (options?: AuthConnectorRefreshSessionOptions) => ({ + scopes: options?.scopes ?? new Set(), + expired: false, + }), + ); const manager = new RefreshingAuthSessionManager({ connector: { createSession, refreshSession }, ...defaultOptions, @@ -192,10 +195,12 @@ describe('RefreshingAuthSessionManager', () => { it('should handle two simultaneous session refreshes with different scopes', async () => { const createSession = jest.fn(); - const refreshSession = jest.fn(async (scopes?: Set) => ({ - scopes: scopes ?? new Set(), - expired: false, - })); + const refreshSession = jest.fn( + async (options?: AuthConnectorRefreshSessionOptions) => ({ + scopes: options?.scopes ?? new Set(), + expired: false, + }), + ); const manager = new RefreshingAuthSessionManager({ connector: { createSession, refreshSession }, ...defaultOptions, @@ -216,10 +221,12 @@ describe('RefreshingAuthSessionManager', () => { it('should handle multiple simultaneous session refreshes with different scopes', async () => { const createSession = jest.fn(); - const refreshSession = jest.fn(async (scopes?: Set) => ({ - scopes: scopes ?? new Set(), - expired: false, - })); + const refreshSession = jest.fn( + async (options?: AuthConnectorRefreshSessionOptions) => ({ + scopes: options?.scopes ?? new Set(), + expired: false, + }), + ); const manager = new RefreshingAuthSessionManager({ connector: { createSession, refreshSession }, ...defaultOptions, diff --git a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts index 414743ba9d..216409a528 100644 --- a/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts +++ b/packages/core-app-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts @@ -137,9 +137,9 @@ export class RefreshingAuthSessionManager implements SessionManager { return this.refreshPromise; } - this.refreshPromise = this.connector.refreshSession( - this.helper.getExtendedScope(this.currentSession, scopes), - ); + this.refreshPromise = this.connector.refreshSession({ + scopes: this.helper.getExtendedScope(this.currentSession, scopes), + }); try { const session = await this.refreshPromise; diff --git a/packages/core-app-api/src/lib/loginPopup.test.ts b/packages/core-app-api/src/lib/loginPopup.test.ts index 50193cbdd6..448f4f727c 100644 --- a/packages/core-app-api/src/lib/loginPopup.test.ts +++ b/packages/core-app-api/src/lib/loginPopup.test.ts @@ -14,14 +14,14 @@ * limitations under the License. */ -import { showLoginPopup } from './loginPopup'; +import { openLoginPopup } from './loginPopup'; -describe('showLoginPopup', () => { +describe('openLoginPopup', () => { afterEach(() => { jest.resetAllMocks(); }); - it('should show an auth popup', async () => { + it('should open an auth popup', async () => { const popupMock = { closed: false }; const openSpy = jest .spyOn(window, 'open') @@ -29,15 +29,14 @@ describe('showLoginPopup', () => { const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const payloadPromise = showLoginPopup({ - url: 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', + const payloadPromise = openLoginPopup({ + url: 'http://my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', name: 'test-popup', - origin: 'my-origin', }); expect(openSpy).toHaveBeenCalledTimes(1); expect(openSpy.mock.calls[0][0]).toBe( - 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', + 'http://my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', ); expect(openSpy.mock.calls[0][1]).toBe('test-popup'); expect(addEventListenerSpy).toHaveBeenCalledTimes(1); @@ -57,16 +56,16 @@ describe('showLoginPopup', () => { // None of these should be accepted listener({ source: popupMock } as MessageEvent); - listener({ origin: 'my-origin' } as MessageEvent); + listener({ origin: 'http://my-origin' } as MessageEvent); listener({ data: { type: 'authorization_response' } } as MessageEvent); listener({ source: popupMock, - origin: 'my-origin', + origin: 'http://my-origin', data: {}, } as MessageEvent); listener({ source: popupMock, - origin: 'my-origin', + origin: 'http://my-origin', data: { type: 'not-auth-result', response: {} }, } as MessageEvent); @@ -79,7 +78,7 @@ describe('showLoginPopup', () => { // This should be accepted as a valid sessions response listener({ source: popupMock, - origin: 'my-origin', + origin: 'http://my-origin', data: { type: 'authorization_response', response: myResponse, @@ -101,10 +100,9 @@ describe('showLoginPopup', () => { const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const payloadPromise = showLoginPopup({ - url: 'url', + const payloadPromise = openLoginPopup({ + url: 'http://my-origin', name: 'name', - origin: 'my-origin', }); expect(openSpy).toHaveBeenCalledTimes(1); @@ -115,7 +113,7 @@ describe('showLoginPopup', () => { listener({ source: popupMock, - origin: 'my-origin', + origin: 'http://my-origin', data: { type: 'authorization_response', error: { @@ -145,10 +143,9 @@ describe('showLoginPopup', () => { openSpy.mockReturnValue(popupMock as Window); - const payloadPromise = showLoginPopup({ - url: 'url', + const payloadPromise = openLoginPopup({ + url: 'http://origin', name: 'name', - origin: 'origin', }); expect(openSpy).toHaveBeenCalledTimes(1); @@ -158,7 +155,7 @@ describe('showLoginPopup', () => { const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; listener({ source: popupMock, - origin: 'origin', + origin: 'http://origin', data: { type: 'config_info', targetOrigin: 'http://localhost', @@ -187,16 +184,15 @@ describe('showLoginPopup', () => { openSpy.mockReturnValue(popupMock as Window); - const payloadPromise = showLoginPopup({ - url: 'url', + const payloadPromise = openLoginPopup({ + url: 'http://origin', name: 'name', - origin: 'origin', }); const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; listener({ source: popupMock, - origin: 'origin', + origin: 'http://origin', data: { type: 'config_info', targetOrigin: 'http://differenthost', diff --git a/packages/core-app-api/src/lib/loginPopup.ts b/packages/core-app-api/src/lib/loginPopup.ts index b6c14d60c9..e7ed84b0cf 100644 --- a/packages/core-app-api/src/lib/loginPopup.ts +++ b/packages/core-app-api/src/lib/loginPopup.ts @@ -16,8 +16,10 @@ /** * Options used to open a login popup. + * + * @public */ -export type LoginPopupOptions = { +export type OpenLoginPopupOptions = { /** * The URL that the auth popup should point to */ @@ -28,11 +30,6 @@ export type LoginPopupOptions = { */ name: string; - /** - * The origin of the final popup page that will post a message to this window. - */ - origin: string; - /** * The width of the popup in pixels, defaults to 500 */ @@ -65,14 +62,20 @@ type AuthResult = * to the app window. The message posted to the app must match the AuthResult type. * * The returned promise resolves to the response of the message that was posted from the auth popup. + * + * @public */ -export function showLoginPopup(options: LoginPopupOptions): Promise { +export function openLoginPopup( + options: OpenLoginPopupOptions, +): Promise { return new Promise((resolve, reject) => { const width = options.width || 500; const height = options.height || 700; const left = window.screen.width / 2 - width / 2; const top = window.screen.height / 2 - height / 2; + const origin = new URL(options.url).origin; + const popup = window.open( options.url, options.name, @@ -92,7 +95,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { if (event.source !== popup) { return; } - if (event.origin !== options.origin) { + if (event.origin !== origin) { return; } const { data } = event; diff --git a/packages/core-compat-api/CHANGELOG.md b/packages/core-compat-api/CHANGELOG.md index 58d8e5df47..e06676f2e3 100644 --- a/packages/core-compat-api/CHANGELOG.md +++ b/packages/core-compat-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/core-compat-api +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/version-bridge@1.0.11 + ## 0.4.2-next.1 ### Patch Changes diff --git a/packages/core-compat-api/catalog-info.yaml b/packages/core-compat-api/catalog-info.yaml index f20f0543a0..c8e198bc53 100644 --- a/packages/core-compat-api/catalog-info.yaml +++ b/packages/core-compat-api/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/core-compat-api/package.json b/packages/core-compat-api/package.json index 51a48c130f..6f20d04c5f 100644 --- a/packages/core-compat-api/package.json +++ b/packages/core-compat-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-compat-api", - "version": "0.4.2-next.1", + "version": "0.4.2-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index ed75738a7a..55608d11bf 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-components +## 0.17.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/version-bridge@1.0.11 + ## 0.17.2-next.0 ### Patch Changes diff --git a/packages/core-components/catalog-info.yaml b/packages/core-components/catalog-info.yaml index ba189b8c39..429253364f 100644 --- a/packages/core-components/catalog-info.yaml +++ b/packages/core-components/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/core-components/package.json b/packages/core-components/package.json index d55c977c87..2fe5f03f28 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-components", - "version": "0.17.2-next.0", + "version": "0.17.2-next.1", "description": "Core components used by Backstage plugins and apps", "backstage": { "role": "web-library" diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 15575b2eb8..90dee1e81b 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-plugin-api +## 1.10.7-next.0 + +### Patch Changes + +- 73f6cc3: The `TranslationApi` now supports interpolation of JSX elements by passing them directly as values to the translation function. If any of the provided interpolation values are JSX elements, the translation function will return a JSX element instead of a string. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 1.10.6 ### Patch Changes diff --git a/packages/core-plugin-api/catalog-info.yaml b/packages/core-plugin-api/catalog-info.yaml index 2f0fa2d4eb..b64c0a80f9 100644 --- a/packages/core-plugin-api/catalog-info.yaml +++ b/packages/core-plugin-api/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 3c106675d0..7c9eaa3943 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/core-plugin-api", - "version": "1.10.6", + "version": "1.10.7-next.0", "description": "Core API used by Backstage plugins", "backstage": { "role": "web-library" diff --git a/packages/core-plugin-api/report-alpha.api.md b/packages/core-plugin-api/report-alpha.api.md index 888c0ffea0..6243c76cff 100644 --- a/packages/core-plugin-api/report-alpha.api.md +++ b/packages/core-plugin-api/report-alpha.api.md @@ -6,6 +6,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { Expand } from '@backstage/types'; import { ExpandRecursive } from '@backstage/types'; +import { JSX as JSX_2 } from 'react'; import { Observable } from '@backstage/types'; import { TranslationMessages as TranslationMessages_2 } from '@backstage/core-plugin-api/alpha'; import { TranslationRef as TranslationRef_2 } from '@backstage/core-plugin-api/alpha'; @@ -94,21 +95,34 @@ export type TranslationApi = { export const translationApiRef: ApiRef; // @alpha (undocumented) -export interface TranslationFunction< +export type TranslationFunction< TMessages extends { [key in string]: string; }, -> { - // (undocumented) - >( - key: TKey, - ...[args]: TranslationFunctionOptions< - NestedMessageKeys>, - PluralKeys, - CollapsedMessages - > - ): CollapsedMessages[TKey]; +> = CollapsedMessages extends infer IMessages extends { + [key in string]: string; } + ? { + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string + > + ): IMessages[TKey]; + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string | JSX_2.Element + > + ): JSX_2.Element; + } + : never; // @alpha export interface TranslationMessages< diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts b/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx similarity index 82% rename from packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts rename to packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx index ba5d43e896..f99001ecf6 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.ts +++ b/packages/core-plugin-api/src/apis/definitions/TranslationApi.test.tsx @@ -14,13 +14,17 @@ * limitations under the License. */ +import { JSX } from 'react'; import { TranslationFunction } from './TranslationApi'; -function unused(..._any: any[]) {} +// This is a weak assertion, don't reuse unless you know the drawbacks +function expectType(value: T) { + return value; +} describe('TranslationFunction', () => { it('should infer plurals', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ key_one: 'one'; key_other: 'other'; thingCount_one: '{{count}} thing'; @@ -29,11 +33,11 @@ describe('TranslationFunction', () => { }>; expect(f).toBeDefined(); - f('foo'); + expectType(f('foo')); // @ts-expect-error f('foo', { count: 1 }); - f('key', { count: 1 }); + expectType(f('key', { count: 1 })); // @ts-expect-error f('key'); // @ts-expect-error @@ -47,7 +51,7 @@ describe('TranslationFunction', () => { // @ts-expect-error f('key_other', { count: 6 }); - f('thingCount', { count: 1 }); + expectType(f('thingCount', { count: 1 })); // @ts-expect-error f('thingCount'); // @ts-expect-error @@ -61,14 +65,13 @@ describe('TranslationFunction', () => { // @ts-expect-error f('thingCount_other', { count: 6 }); - const x1: 'one' | 'other' = f('key', { count: 6 }); + expectType<'one' | 'other'>(f('key', { count: 6 })); // @ts-expect-error - const x2: 'one' = f('key', { count: 6 }); - unused(x1, x2); + expectType<'one'>(f('key', { count: 6 })); }); it('should infer interpolation params', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ none: '='; simple: '= {{bar}}'; multiple: '= {{bar }} {{ baz}}'; @@ -78,7 +81,11 @@ describe('TranslationFunction', () => { // @ts-expect-error f('none', { replace: { unknown: 1 } }); - f('simple', { bar: '' }); + expectType(f('simple', { bar: '' })); + // @ts-expect-error + expectType(f('simple', { bar:
})); + expectType(f('simple', { bar:
})); + expectType(f('simple', { replace: { bar:
} })); // @ts-expect-error f('simple'); // @ts-expect-error @@ -87,7 +94,10 @@ describe('TranslationFunction', () => { f('simple', { replace: {} }); // @ts-expect-error f('simple', { replace: { wrong: '' } }); - f('multiple', { bar: '', baz: '' }); + expectType(f('multiple', { bar: '', baz: '' })); + expectType(f('multiple', { bar:
, baz: '' })); + expectType(f('multiple', { bar: '', baz:
})); + expectType(f('multiple', { bar:
, baz:
})); // @ts-expect-error f('multiple', { bar: '' }); // @ts-expect-error @@ -98,7 +108,12 @@ describe('TranslationFunction', () => { f('multiple', {}); // @ts-expect-error f('multiple', { replace: {} }); - f('deep', { replace: { x: { y: '', z: '' }, a: { b: { c: '' } } } }); + expectType( + f('deep', { replace: { x: { y: '', z: '' }, a: { b: { c: '' } } } }), + ); + expectType( + f('deep', { replace: { x: { y: '', z: '' }, a: { b: { c:
} } } }), + ); // @ts-expect-error f('deep'); // @ts-expect-error @@ -114,7 +129,7 @@ describe('TranslationFunction', () => { }); it('should infer interpolation params with count', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ simple_one: '= {{bar}}'; simple_other: '= {{bar}}'; multiple_one: '= {{ bar}} {{baz }}'; @@ -163,7 +178,7 @@ describe('TranslationFunction', () => { }); it('should support formatting', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ none: '{{x}}'; number: '{{x, number}}'; numberOptions: '{{x, number(minimumFractionDigits: 2)}}'; @@ -211,7 +226,7 @@ describe('TranslationFunction', () => { }); it('should support nesting', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ simple: '$t(foo)'; nested: '$t(bar)'; nestedCount: '$t(qux)'; @@ -246,7 +261,7 @@ describe('TranslationFunction', () => { }); it('should limit nesting depth', () => { - const f = (() => {}) as TranslationFunction<{ + const f = (() => {}) as unknown as TranslationFunction<{ a: '$t(b) {{a}}'; b: '$t(c) {{b}}'; c: '$t(d) {{c}}'; diff --git a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts index dba8753828..7c3c3f8e83 100644 --- a/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/TranslationApi.ts @@ -17,6 +17,7 @@ import { ApiRef, createApiRef } from '@backstage/core-plugin-api'; import { Expand, ExpandRecursive, Observable } from '@backstage/types'; import { TranslationRef } from '../../translation'; +import { JSX } from 'react'; /** * Base translation options. @@ -168,12 +169,12 @@ type ReplaceFormatsFromMessage = * * @ignore */ -type ReplaceOptionsFromFormats = { +type ReplaceOptionsFromFormats = { [Key in keyof TFormats]: TFormats[Key] extends keyof I18nextFormatMap ? I18nextFormatMap[TFormats[Key]]['type'] : TFormats[Key] extends {} - ? Expand> - : string; + ? Expand> + : TValueType; }; /** @@ -259,14 +260,17 @@ type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( type CollectOptions< TCount extends { count?: number }, TFormats extends {}, + TValueType, > = TCount & // count is special, omit it from the replacements (keyof Omit extends never ? {} : ( - | Expand, 'count'>> + | Expand, 'count'>> | { - replace: Expand, 'count'>>; + replace: Expand< + Omit, 'count'> + >; } ) & { formatParams?: Expand>; @@ -278,8 +282,8 @@ type CollectOptions< * @ignore */ type OptionArgs = keyof TOptions extends never - ? [options?: BaseOptions] - : [options: BaseOptions & TOptions]; + ? [options?: Expand] + : [options: Expand]; /** * @ignore @@ -288,30 +292,53 @@ type TranslationFunctionOptions< TKeys extends keyof TMessages, // All normalized message keys to be considered, i.e. included nested ones TPluralKeys extends keyof TMessages, // All keys in the message map that are pluralized TMessages extends { [key in string]: string }, // Collapsed message map with normalized keys and union values + TValueType, > = OptionArgs< Expand< CollectOptions< TKeys & TPluralKeys extends never ? {} : { count: number }, ExpandRecursive< UnionToIntersection> - > + >, + TValueType > > >; /** @alpha */ -export interface TranslationFunction< - TMessages extends { [key in string]: string }, -> { - >( - key: TKey, - ...[args]: TranslationFunctionOptions< - NestedMessageKeys>, - PluralKeys, - CollapsedMessages - > - ): CollapsedMessages[TKey]; -} +export type TranslationFunction = + CollapsedMessages extends infer IMessages extends { + [key in string]: string; + } + ? { + /** + * A translation function that returns a string. + */ + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string + > + ): IMessages[TKey]; + /** + * A translation function where at least one JSX.Element has been + * provided as an interpolation value, and will therefore return a + * JSX.Element. + */ + ( + key: TKey, + ...[args]: TranslationFunctionOptions< + NestedMessageKeys, + PluralKeys, + IMessages, + string | JSX.Element + > + ): JSX.Element; + } + : never; /** @alpha */ export type TranslationSnapshot = diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 2587a880a8..004799767d 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/create-app +## 0.6.2-next.2 + +### Patch Changes + +- 8448948: Removed `lerna-debug.log*` pattern from `.gitignore` as Lerna was removed from the package in version `@backstage/create-app@0.5.19`. +- Updated dependencies + - @backstage/cli-common@0.1.15 + ## 0.6.2-next.1 ### Patch Changes diff --git a/packages/create-app/catalog-info.yaml b/packages/create-app/catalog-info.yaml index 7da321f091..5f3b468e5d 100644 --- a/packages/create-app/catalog-info.yaml +++ b/packages/create-app/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: framework-maintainers diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 015a84409f..55432ddc6b 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/create-app", - "version": "0.6.2-next.1", + "version": "0.6.2-next.2", "description": "A CLI that helps you create your own Backstage app", "backstage": { "role": "cli" diff --git a/packages/create-app/templates/default-app/.gitignore.hbs b/packages/create-app/templates/default-app/.gitignore.hbs index 77ad56d128..e506e00f92 100644 --- a/packages/create-app/templates/default-app/.gitignore.hbs +++ b/packages/create-app/templates/default-app/.gitignore.hbs @@ -7,7 +7,6 @@ logs npm-debug.log* yarn-debug.log* yarn-error.log* -lerna-debug.log* # Coverage directory generated when running tests with coverage coverage diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 5d3eeaf067..1481726448 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/dev-utils +## 1.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/core-components@0.17.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + ## 1.1.10-next.1 ### Patch Changes diff --git a/packages/dev-utils/catalog-info.yaml b/packages/dev-utils/catalog-info.yaml index c67eecec83..e1c5408ec0 100644 --- a/packages/dev-utils/catalog-info.yaml +++ b/packages/dev-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 9bdc8a25ac..8e68e50aed 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/dev-utils", - "version": "1.1.10-next.1", + "version": "1.1.10-next.2", "description": "Utilities for developing Backstage plugins.", "backstage": { "role": "web-library" diff --git a/packages/e2e-test-utils/catalog-info.yaml b/packages/e2e-test-utils/catalog-info.yaml index d1dea51dc5..b58dc2d88a 100644 --- a/packages/e2e-test-utils/catalog-info.yaml +++ b/packages/e2e-test-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/e2e-test/CHANGELOG.md b/packages/e2e-test/CHANGELOG.md index 5575b8f05b..487eb771f1 100644 --- a/packages/e2e-test/CHANGELOG.md +++ b/packages/e2e-test/CHANGELOG.md @@ -1,5 +1,14 @@ # e2e-test +## 0.2.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/create-app@0.6.2-next.2 + - @backstage/cli-common@0.1.15 + - @backstage/errors@1.2.7 + ## 0.2.28-next.1 ### Patch Changes diff --git a/packages/e2e-test/catalog-info.yaml b/packages/e2e-test/catalog-info.yaml index 55bce03e8b..3d795a5ef4 100644 --- a/packages/e2e-test/catalog-info.yaml +++ b/packages/e2e-test/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: operations-maintainers diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index a27bdcc4b3..7287232a01 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,6 +1,6 @@ { "name": "e2e-test", - "version": "0.2.28-next.1", + "version": "0.2.28-next.2", "description": "E2E test for verifying Backstage packages", "backstage": { "role": "cli" diff --git a/packages/errors/catalog-info.yaml b/packages/errors/catalog-info.yaml index ee97045b20..d39e32e171 100644 --- a/packages/errors/catalog-info.yaml +++ b/packages/errors/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-app-api/CHANGELOG.md b/packages/frontend-app-api/CHANGELOG.md index 95c44da351..3e119d7b2f 100644 --- a/packages/frontend-app-api/CHANGELOG.md +++ b/packages/frontend-app-api/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/frontend-app-api +## 0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/frontend-defaults@0.2.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.11.2-next.1 ### Patch Changes diff --git a/packages/frontend-app-api/catalog-info.yaml b/packages/frontend-app-api/catalog-info.yaml index 332650f083..4e44782440 100644 --- a/packages/frontend-app-api/catalog-info.yaml +++ b/packages/frontend-app-api/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-app-api/package.json b/packages/frontend-app-api/package.json index e64b6b8b2c..ea086e29ee 100644 --- a/packages/frontend-app-api/package.json +++ b/packages/frontend-app-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-app-api", - "version": "0.11.2-next.1", + "version": "0.11.2-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-defaults/CHANGELOG.md b/packages/frontend-defaults/CHANGELOG.md index 9dc4b7be1c..df3f24050b 100644 --- a/packages/frontend-defaults/CHANGELOG.md +++ b/packages/frontend-defaults/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/frontend-defaults +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/errors@1.2.7 + ## 0.2.2-next.1 ### Patch Changes diff --git a/packages/frontend-defaults/catalog-info.yaml b/packages/frontend-defaults/catalog-info.yaml index cd0ac9b79a..0a3f07fd29 100644 --- a/packages/frontend-defaults/catalog-info.yaml +++ b/packages/frontend-defaults/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-defaults/package.json b/packages/frontend-defaults/package.json index 15285842b6..b1b85f3eb1 100644 --- a/packages/frontend-defaults/package.json +++ b/packages/frontend-defaults/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-defaults", - "version": "0.2.2-next.1", + "version": "0.2.2-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-dynamic-feature-loader/CHANGELOG.md b/packages/frontend-dynamic-feature-loader/CHANGELOG.md index dbbda03911..1b377ed777 100644 --- a/packages/frontend-dynamic-feature-loader/CHANGELOG.md +++ b/packages/frontend-dynamic-feature-loader/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/frontend-dynamic-feature-loader +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + ## 0.1.1-next.0 ### Patch Changes diff --git a/packages/frontend-dynamic-feature-loader/catalog-info.yaml b/packages/frontend-dynamic-feature-loader/catalog-info.yaml index 3f9ef427ed..a44650174a 100644 --- a/packages/frontend-dynamic-feature-loader/catalog-info.yaml +++ b/packages/frontend-dynamic-feature-loader/catalog-info.yaml @@ -3,8 +3,9 @@ kind: Component metadata: name: backstage-frontend-dynamic-feature-loader title: '@backstage/frontend-dynamic-feature-loader' - description: Backstage frontend feature loader to load new frontend system plugins exposed as module federation remotes. + description: Backstage frontend feature loader to load new frontend system + plugins exposed as module federation remotes. spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-dynamic-feature-loader/package.json b/packages/frontend-dynamic-feature-loader/package.json index 3513ac9cc5..d3b05fdc12 100644 --- a/packages/frontend-dynamic-feature-loader/package.json +++ b/packages/frontend-dynamic-feature-loader/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-dynamic-feature-loader", - "version": "0.1.1-next.0", + "version": "0.1.1-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-internal/CHANGELOG.md b/packages/frontend-internal/CHANGELOG.md index 42e102807a..65ffbd826f 100644 --- a/packages/frontend-internal/CHANGELOG.md +++ b/packages/frontend-internal/CHANGELOG.md @@ -1,5 +1,14 @@ # @internal/frontend +## 0.0.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.0.9-next.0 ### Patch Changes diff --git a/packages/frontend-internal/catalog-info.yaml b/packages/frontend-internal/catalog-info.yaml index 630f53275a..20dcb6c970 100644 --- a/packages/frontend-internal/catalog-info.yaml +++ b/packages/frontend-internal/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-internal/package.json b/packages/frontend-internal/package.json index 70276bf9c6..7f1615c5f5 100644 --- a/packages/frontend-internal/package.json +++ b/packages/frontend-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/frontend", - "version": "0.0.9-next.0", + "version": "0.0.9-next.1", "backstage": { "role": "web-library", "inline": true diff --git a/packages/frontend-plugin-api/CHANGELOG.md b/packages/frontend-plugin-api/CHANGELOG.md index a43fcec1d8..ce9e73745f 100644 --- a/packages/frontend-plugin-api/CHANGELOG.md +++ b/packages/frontend-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/frontend-plugin-api +## 0.10.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.10.2-next.0 ### Patch Changes diff --git a/packages/frontend-plugin-api/catalog-info.yaml b/packages/frontend-plugin-api/catalog-info.yaml index 4531f6eca3..8ce7aacabd 100644 --- a/packages/frontend-plugin-api/catalog-info.yaml +++ b/packages/frontend-plugin-api/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-plugin-api/package.json b/packages/frontend-plugin-api/package.json index 5877a3800c..1e3161489c 100644 --- a/packages/frontend-plugin-api/package.json +++ b/packages/frontend-plugin-api/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-plugin-api", - "version": "0.10.2-next.0", + "version": "0.10.2-next.1", "backstage": { "role": "web-library" }, diff --git a/packages/frontend-test-utils/CHANGELOG.md b/packages/frontend-test-utils/CHANGELOG.md index 38048e3fd3..3534a15653 100644 --- a/packages/frontend-test-utils/CHANGELOG.md +++ b/packages/frontend-test-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/frontend-test-utils +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/config@1.3.2 + - @backstage/frontend-app-api@0.11.2-next.2 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-app@0.1.9-next.2 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + ## 0.3.2-next.1 ### Patch Changes diff --git a/packages/frontend-test-utils/catalog-info.yaml b/packages/frontend-test-utils/catalog-info.yaml index e2d2a57897..065a0b64e2 100644 --- a/packages/frontend-test-utils/catalog-info.yaml +++ b/packages/frontend-test-utils/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/frontend-test-utils/package.json b/packages/frontend-test-utils/package.json index 5bdefbbb49..b065490006 100644 --- a/packages/frontend-test-utils/package.json +++ b/packages/frontend-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/frontend-test-utils", - "version": "0.3.2-next.1", + "version": "0.3.2-next.2", "backstage": { "role": "web-library" }, diff --git a/packages/integration-aws-node/CHANGELOG.md b/packages/integration-aws-node/CHANGELOG.md index 67060c78c2..41d4eecb08 100644 --- a/packages/integration-aws-node/CHANGELOG.md +++ b/packages/integration-aws-node/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/integration-aws-node +## 0.1.16-next.0 + +### Patch Changes + +- db4630e: Fixed bug in DefaultAwsCredentialsManager where aws.mainAccount.region has no effect on the STS region used for account ID lookup during credential provider lookup when falling back to the main account, and it does not default to us-east-1 +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 0.1.15 ### Patch Changes diff --git a/packages/integration-aws-node/catalog-info.yaml b/packages/integration-aws-node/catalog-info.yaml index 4c8264d575..88b86deb84 100644 --- a/packages/integration-aws-node/catalog-info.yaml +++ b/packages/integration-aws-node/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/integration-aws-node/package.json b/packages/integration-aws-node/package.json index 28cf435fd7..65403727b6 100644 --- a/packages/integration-aws-node/package.json +++ b/packages/integration-aws-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-aws-node", - "version": "0.1.15", + "version": "0.1.16-next.0", "description": "Helpers for fetching AWS account credentials", "backstage": { "role": "node-library" diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts index d81f717a58..582b003724 100644 --- a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts +++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.test.ts @@ -91,6 +91,18 @@ describe('DefaultAwsCredentialsManager', () => { Account: '123456789012', }); + stsMock + .on(GetCallerIdentityCommand) + .callsFake(async (_input, getClient) => { + const client = getClient(); + const region = await client.config.region(); + if (!region) { + throw new Error('Region is missing'); + } + return { + Account: '123456789012', + }; + }); stsMock .on(AssumeRoleCommand, { RoleArn: 'arn:aws:iam::111111111111:role/hello', @@ -488,5 +500,21 @@ describe('DefaultAwsCredentialsManager', () => { }, }); }); + + it('passes mainAccount region to fillInAccountId for account ID lookup during fallback', async () => { + const region = 'us-west-2'; + const configWithRegion = new ConfigReader({ + aws: { + mainAccount: { + region, + }, + }, + }); + const provider = + DefaultAwsCredentialsManager.fromConfig(configWithRegion); + await provider.getCredentialProvider({ accountId: '123456789012' }); + + expect(await stsMock.call(0).thisValue.config.region()).toEqual(region); + }); }); }); diff --git a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts index 3048fdfe40..b54a4fda26 100644 --- a/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts +++ b/packages/integration-aws-node/src/DefaultAwsCredentialsManager.ts @@ -37,6 +37,7 @@ import { Config } from '@backstage/config'; /** * Retrieves the account ID for the given credential provider from STS. + * Include the region if present, otherwise use the default region. */ async function fillInAccountId(credProvider: AwsCredentialProvider) { if (credProvider.accountId) { @@ -44,7 +45,7 @@ async function fillInAccountId(credProvider: AwsCredentialProvider) { } const client = new STSClient({ - region: credProvider.stsRegion, + region: credProvider.stsRegion ?? 'us-east-1', customUserAgent: 'backstage-aws-credentials-manager', credentialDefaultProvider: () => credProvider.sdkCredentialProvider, }); @@ -174,6 +175,7 @@ export class DefaultAwsCredentialsManager implements AwsCredentialsManager { awsConfig.mainAccount, ); const mainAccountCredProvider: AwsCredentialProvider = { + stsRegion: awsConfig.mainAccount.region, sdkCredentialProvider: mainAccountSdkCredProvider, }; diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index a3440c8d2c..58fe14bfb1 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/integration-react +## 1.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + ## 1.2.7-next.1 ### Patch Changes diff --git a/packages/integration-react/catalog-info.yaml b/packages/integration-react/catalog-info.yaml index 8df5e52c95..1b6876ed99 100644 --- a/packages/integration-react/catalog-info.yaml +++ b/packages/integration-react/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index f49fc4dc69..bf99892817 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "1.2.7-next.1", + "version": "1.2.7-next.2", "description": "Frontend package for managing integrations towards external systems", "backstage": { "role": "web-library" diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 535d154f6e..6a67d797c8 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/integration +## 1.17.0-next.2 + +### Minor Changes + +- f134cea: Implement Edit URL feature for Gerrit 3.9+. + + It's possible to disable the edit url by adding the `disableEditUrl: true` config in the Gerrit integration. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/errors@1.2.7 + ## 1.16.4-next.1 ### Patch Changes diff --git a/packages/integration/catalog-info.yaml b/packages/integration/catalog-info.yaml index 7f602c42be..5f9e30681a 100644 --- a/packages/integration/catalog-info.yaml +++ b/packages/integration/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 9e47e9a0ff..3bfcf42203 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -182,6 +182,11 @@ export interface Config { * @visibility frontend */ cloneUrl?: string; + /** + * Disable the edit url feature. + * @visibility frontend + */ + disableEditUrl?: boolean; /** * The username to use for authenticated requests. * @visibility secret diff --git a/packages/integration/package.json b/packages/integration/package.json index 53a380b1c9..053a3e793a 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "1.16.4-next.1", + "version": "1.17.0-next.2", "description": "Helpers for managing integrations towards external systems", "backstage": { "role": "common-library" diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md index fc38853029..9c1ad3ae82 100644 --- a/packages/integration/report.api.md +++ b/packages/integration/report.api.md @@ -396,6 +396,7 @@ export type GerritIntegrationConfig = { host: string; baseUrl?: string; cloneUrl?: string; + disableEditUrl?: boolean; gitilesBaseUrl: string; username?: string; password?: string; diff --git a/packages/integration/src/gerrit/GerritIntegration.test.ts b/packages/integration/src/gerrit/GerritIntegration.test.ts index 2cfc748550..0d4058d47e 100644 --- a/packages/integration/src/gerrit/GerritIntegration.test.ts +++ b/packages/integration/src/gerrit/GerritIntegration.test.ts @@ -47,6 +47,7 @@ describe('GerritIntegration', () => { it('returns the basics', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', } as any); expect(integration.type).toBe('gerrit'); expect(integration.title).toBe('gerrit-review.example.com'); @@ -70,6 +71,7 @@ describe('GerritIntegration', () => { it('handles line numbers', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', } as any); expect( @@ -85,6 +87,7 @@ describe('GerritIntegration', () => { describe('resolves with a relative url', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', } as any); it('works for valid urls pointing to a branch', () => { expect( @@ -135,17 +138,37 @@ describe('GerritIntegration', () => { }); }); - it('resolve edit URL', () => { + it('resolve Url when editUrl is disabled', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', + disableEditUrl: true, } as any); // Resolve edit URLs is not applicable for gerrit. Return the input // url as is. expect( integration.resolveEditUrl( - 'https://gerrit-review.example.com/catalog-info.yaml', + 'https://gerrit-review.example.com/gitiles/backstage/backstage/+/refs/heads/master/catalog-info.yaml', ), - ).toBe('https://gerrit-review.example.com/catalog-info.yaml'); + ).toBe( + 'https://gerrit-review.example.com/gitiles/backstage/backstage/+/refs/heads/master/catalog-info.yaml', + ); + }); + + it('resolve edit URL with editUrl', () => { + const integration = new GerritIntegration({ + host: 'gerrit-review.example.com', + baseUrl: 'https://gerrit-review.example.com', + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', + disableEditUrl: false, + } as any); + + expect( + integration.resolveEditUrl( + 'https://gerrit-review.example.com/gitiles/backstage/backstage/+/refs/heads/master/catalog-info.yaml', + ), + ).toBe( + 'https://gerrit-review.example.com/admin/repos/edit/repo/backstage/backstage/branch/refs/heads/master/file/catalog-info.yaml', + ); }); }); diff --git a/packages/integration/src/gerrit/GerritIntegration.ts b/packages/integration/src/gerrit/GerritIntegration.ts index 7e2e757c6b..ba2153c817 100644 --- a/packages/integration/src/gerrit/GerritIntegration.ts +++ b/packages/integration/src/gerrit/GerritIntegration.ts @@ -20,7 +20,7 @@ import { GerritIntegrationConfig, readGerritIntegrationConfigs, } from './config'; -import { parseGitilesUrlRef } from './core'; +import { buildGerritEditUrl, parseGitilesUrlRef } from './core'; /** * A Gerrit based integration. @@ -75,7 +75,16 @@ export class GerritIntegration implements ScmIntegration { } resolveEditUrl(url: string): string { - // Not applicable for gerrit. - return url; + if (this.config.disableEditUrl) { + return url; + } + + const parsed = parseGitilesUrlRef(this.config, url); + return buildGerritEditUrl( + this.config, + parsed.project, + parsed.ref, + parsed.path, + ); } } diff --git a/packages/integration/src/gerrit/config.test.ts b/packages/integration/src/gerrit/config.test.ts index be31193f2e..7b6dc01e7e 100644 --- a/packages/integration/src/gerrit/config.test.ts +++ b/packages/integration/src/gerrit/config.test.ts @@ -57,6 +57,7 @@ describe('readGerritIntegrationConfig', () => { host: 'a.com', baseUrl: 'https://a.com/api', cloneUrl: 'https:a.com/clone', + disableEditUrl: true, gitilesBaseUrl: 'https://a.com/git', username: 'u', password: ' p ', @@ -66,12 +67,32 @@ describe('readGerritIntegrationConfig', () => { host: 'a.com', baseUrl: 'https://a.com/api', cloneUrl: 'https:a.com/clone', + disableEditUrl: true, gitilesBaseUrl: 'https://a.com/git', username: 'u', password: 'p', }); }); + it('activate Edit Url', () => { + const output = readGerritIntegrationConfig( + buildConfig({ + host: 'a.com', + disableEditUrl: false, + gitilesBaseUrl: 'https://a.com/gerrit/plugins/gitiles', + }), + ); + expect(output).toEqual({ + host: 'a.com', + baseUrl: 'https://a.com', + cloneUrl: 'https://a.com', + disableEditUrl: false, + gitilesBaseUrl: 'https://a.com/gerrit/plugins/gitiles', + username: undefined, + password: undefined, + }); + }); + it('can create a default value if the API base URL is missing', () => { const output = readGerritIntegrationConfig( buildConfig({ @@ -156,6 +177,7 @@ describe('readGerritIntegrationConfigs', () => { host: 'b.com', baseUrl: 'https://b.com/api', cloneUrl: 'https://b.com/api', + disableEditUrl: undefined, gitilesBaseUrl: 'https://b.com/gerrit/plugins/gitiles', username: undefined, password: undefined, diff --git a/packages/integration/src/gerrit/config.ts b/packages/integration/src/gerrit/config.ts index fe88750687..e3cedea9de 100644 --- a/packages/integration/src/gerrit/config.ts +++ b/packages/integration/src/gerrit/config.ts @@ -44,6 +44,11 @@ export type GerritIntegrationConfig = { */ cloneUrl?: string; + /** + * Disable the edit url feature for Gerrit version less than 3.9. + */ + disableEditUrl?: boolean; + /** * Base url for Gitiles. This is needed for creating a valid * user-friendly url that can be used for browsing the content of the @@ -80,6 +85,7 @@ export function readGerritIntegrationConfig( const host = config.getString('host'); let baseUrl = config.getOptionalString('baseUrl'); let cloneUrl = config.getOptionalString('cloneUrl'); + const disableEditUrl = config.getOptionalBoolean('disableEditUrl'); let gitilesBaseUrl = config.getString('gitilesBaseUrl'); const username = config.getOptionalString('username'); const password = config.getOptionalString('password')?.trim(); @@ -106,21 +112,19 @@ export function readGerritIntegrationConfig( } else { baseUrl = `https://${host}`; } - if (gitilesBaseUrl) { - gitilesBaseUrl = trimEnd(gitilesBaseUrl, '/'); - } else { - gitilesBaseUrl = `https://${host}`; - } if (cloneUrl) { cloneUrl = trimEnd(cloneUrl, '/'); } else { cloneUrl = baseUrl; } + gitilesBaseUrl = trimEnd(gitilesBaseUrl, '/'); + return { host, baseUrl, cloneUrl, + disableEditUrl, gitilesBaseUrl, username, password, diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts index 4e3aabfa9c..ae6a5baa10 100644 --- a/packages/integration/src/gerrit/core.ts +++ b/packages/integration/src/gerrit/core.ts @@ -208,6 +208,29 @@ export function buildGerritGitilesUrl( }/${project}/+/refs/heads/${branch}/${trimStart(filePath, '/')}`; } +/** + * Build a Gerrit Gitiles url that targets a specific path. + * + * @param config - A Gerrit provider config. + * @param project - The name of the git project + * @param branch - The branch we will target. + * @param filePath - The absolute file path. + * @public + */ +export function buildGerritEditUrl( + config: GerritIntegrationConfig, + project: string, + branch: string, + filePath: string, +): string { + return `${ + config.baseUrl + }/admin/repos/edit/repo/${project}/branch/refs/heads/${branch}/file/${trimStart( + filePath, + '/', + )}`; +} + /** * Build a Gerrit Gitiles archive url that targets a specific branch and path * diff --git a/packages/opaque-internal/catalog-info.yaml b/packages/opaque-internal/catalog-info.yaml index b1c5d01937..8fd8136d34 100644 --- a/packages/opaque-internal/catalog-info.yaml +++ b/packages/opaque-internal/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/release-manifests/catalog-info.yaml b/packages/release-manifests/catalog-info.yaml index 4564145a3d..dfef3b2a5f 100644 --- a/packages/release-manifests/catalog-info.yaml +++ b/packages/release-manifests/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-common-library - owner: maintainers + owner: operations-maintainers diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index 8dcd1341f7..917b364d2e 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/repo-tools +## 0.13.3-next.2 + +### Patch Changes + +- b229476: Support passing additional properties to OpenAPI server generator +- Updated dependencies + - @backstage/cli-node@0.2.13 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + ## 0.13.3-next.1 ### Patch Changes diff --git a/packages/repo-tools/catalog-info.yaml b/packages/repo-tools/catalog-info.yaml index 66b5ecc64b..379111cbca 100644 --- a/packages/repo-tools/catalog-info.yaml +++ b/packages/repo-tools/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-cli - owner: maintainers + owner: tooling-maintainers diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index a8481d81c9..1ec1d1a48f 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/repo-tools", - "version": "0.13.3-next.1", + "version": "0.13.3-next.2", "description": "CLI for Backstage repo tooling ", "backstage": { "role": "cli" diff --git a/packages/repo-tools/src/commands/index.ts b/packages/repo-tools/src/commands/index.ts index 7515feb8a4..01288056c7 100644 --- a/packages/repo-tools/src/commands/index.ts +++ b/packages/repo-tools/src/commands/index.ts @@ -56,6 +56,10 @@ function registerPackageCommand(program: Command) { .description( 'Additional properties that can be passed to @openapitools/openapi-generator-cli', ) + .option('--server-additional-properties [properties]') + .description( + 'Additional properties that can be passed to @openapitools/openapi-generator-cli', + ) .option('--watch') .description('Watch the OpenAPI spec for changes and regenerate on save.') .action(lazy(() => import('./package/schema/openapi/generate'), 'command')); diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts index 1c9fd586fb..bda80d2fb1 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/index.ts @@ -51,7 +51,7 @@ export async function command(opts: OptionValues) { ); } if (opts.server) { - promises.push(generateServer(options)); + promises.push(generateServer(options, opts.serverAdditionalProperties)); } await Promise.all(promises); }; diff --git a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts index 8c342b0a68..1a8dd36cab 100644 --- a/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts +++ b/packages/repo-tools/src/commands/package/schema/openapi/generate/server.ts @@ -30,6 +30,7 @@ import { resolvePackagePath } from '@backstage/backend-plugin-api'; import { getPathToCurrentOpenApiSpec, getRelativePathToFile, + toGeneratorAdditionalProperties, } from '../../../../../lib/openapi/helpers'; async function generateSpecFile() { @@ -70,7 +71,7 @@ export const createOpenApiRouter = async ( const indexFile = join(schemaDir, '..', 'index.ts'); await fs.writeFile( indexFile, - `// + `// export * from './generated';`, ); @@ -82,7 +83,10 @@ export const createOpenApiRouter = async ( } } -async function generate(abortSignal?: AbortController) { +async function generate( + serverAdditionalProperties?: string, + abortSignal?: AbortController, +) { const resolvedOpenapiPath = await getPathToCurrentOpenApiSpec(); const resolvedOutputDirectory = await getRelativePathToFile(OUTPUT_PATH); @@ -93,6 +97,10 @@ async function generate(abortSignal?: AbortController) { OPENAPI_IGNORE_FILES.join('\n'), ); + const additionalProperties = toGeneratorAdditionalProperties({ + initialValue: serverAdditionalProperties, + }); + await exec( 'node', [ @@ -111,6 +119,9 @@ async function generate(abortSignal?: AbortController) { ), '--generator-key', 'v3.0', + additionalProperties + ? `--additional-properties=${additionalProperties}` + : '', ], { maxBuffer: Number.MAX_VALUE, @@ -147,15 +158,18 @@ async function generate(abortSignal?: AbortController) { await generateSpecFile(); } -export async function command({ - abortSignal, - isWatch = false, -}: { - abortSignal?: AbortController; - isWatch?: boolean; -}): Promise { +export async function command( + { + abortSignal, + isWatch = false, + }: { + abortSignal?: AbortController; + isWatch?: boolean; + }, + serverAdditionalProperties?: string, +): Promise { try { - await generate(abortSignal); + await generate(serverAdditionalProperties, abortSignal); console.log(chalk.green('Generated server files.')); } catch (err) { if (err.name === 'AbortError') { diff --git a/packages/scaffolder-internal/CHANGELOG.md b/packages/scaffolder-internal/CHANGELOG.md index 78d4f59df4..9ba8836fff 100644 --- a/packages/scaffolder-internal/CHANGELOG.md +++ b/packages/scaffolder-internal/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/scaffolder +## 0.0.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + ## 0.0.9-next.1 ### Patch Changes diff --git a/packages/scaffolder-internal/catalog-info.yaml b/packages/scaffolder-internal/catalog-info.yaml index c2c2c013d3..a586883cca 100644 --- a/packages/scaffolder-internal/catalog-info.yaml +++ b/packages/scaffolder-internal/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/scaffolder-internal/package.json b/packages/scaffolder-internal/package.json index 44e409cb48..6dda7d0b41 100644 --- a/packages/scaffolder-internal/package.json +++ b/packages/scaffolder-internal/package.json @@ -1,6 +1,6 @@ { "name": "@internal/scaffolder", - "version": "0.0.9-next.1", + "version": "0.0.9-next.2", "backstage": { "role": "web-library", "inline": true diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 570bfbd446..8afd99d6fb 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,24 @@ # techdocs-cli-embedded-app +## 0.2.108-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/test-utils@1.7.8-next.1 + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/plugin-techdocs@1.12.6-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/app-defaults@1.6.2-next.1 + - @backstage/cli@0.32.1-next.2 + - @backstage/config@1.3.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-techdocs-react@1.2.17-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + ## 0.2.108-next.1 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 875c80a2b1..cd95f9e37e 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.108-next.1", + "version": "0.2.108-next.2", "backstage": { "role": "frontend" }, diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index a116a77b24..e9a719087e 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,16 @@ # @techdocs/cli +## 1.9.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/cli-common@0.1.15 + ## 1.9.3-next.1 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 3ff6938e9c..e44b6fb107 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,6 +1,6 @@ { "name": "@techdocs/cli", - "version": "1.9.3-next.1", + "version": "1.9.3-next.2", "description": "Utility CLI for managing TechDocs sites in Backstage.", "backstage": { "role": "cli" diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 42bfd5315e..2561b80f57 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/test-utils +## 1.7.8-next.1 + +### Patch Changes + +- b573341: Added support for interpolating JSX elements with the `MockTranslationApi`. +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 1.7.8-next.0 ### Patch Changes diff --git a/packages/test-utils/catalog-info.yaml b/packages/test-utils/catalog-info.yaml index 4b894f08b5..534241122d 100644 --- a/packages/test-utils/catalog-info.yaml +++ b/packages/test-utils/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index fae2069bbc..0839e44b69 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/test-utils", - "version": "1.7.8-next.0", + "version": "1.7.8-next.1", "description": "Utilities to test Backstage plugins and apps.", "backstage": { "role": "web-library" diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.tsx similarity index 84% rename from packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts rename to packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.tsx index 0cc755ecb5..4bee969a6d 100644 --- a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.ts +++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.test.tsx @@ -94,6 +94,52 @@ describe('MockTranslationApi', () => { expect(snapshot.t('foo', { qux: 'Deep' })).toBe('Foo Nested Baz Deep'); }); + it('should support jsx interpolation', () => { + const snapshot = snapshotWithMessages({ + empty: 'derp', + jsx: '={{ x }}', + jsxNested: '={{ x.y.z }}', + jsxDeep: '<$t(jsx)>', + }); + + expect(snapshot.t('jsx', { x:

hello

})).toMatchInlineSnapshot(` + + = +

+ hello +

+
+ `); + expect(snapshot.t('jsx', { replace: { x:

hello

} })) + .toMatchInlineSnapshot(` + + = +

+ hello +

+
+ `); + expect( + snapshot.t('jsxNested', { replace: { x: { y: { z:

hello

} } } }), + ).toMatchInlineSnapshot(` + + = +

+ hello +

+
+ `); + expect(snapshot.t('jsxDeep', { x:

hello

})).toMatchInlineSnapshot(` + + <= +

+ hello +

+ > +
+ `); + }); + it('should support formatting', () => { const snapshot = snapshotWithMessages({ plain: '= {{ x }}', diff --git a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts index 46c5bfd5d3..344679355a 100644 --- a/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts +++ b/packages/test-utils/src/testUtils/apis/TranslationApi/MockTranslationApi.ts @@ -16,7 +16,6 @@ import { TranslationApi, - TranslationFunction, TranslationRef, TranslationSnapshot, } from '@backstage/core-plugin-api/alpha'; @@ -27,6 +26,8 @@ import { Observable } from '@backstage/types'; // Internal import to avoid code duplication, this will lead to duplication in build output // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { toInternalTranslationRef } from '../../../../../core-plugin-api/src/translation/TranslationRef'; +// eslint-disable-next-line @backstage/no-relative-monorepo-imports +import { JsxInterpolator } from '../../../../../core-app-api/src/apis/implementations/TranslationApi/I18nextTranslationApi'; const DEFAULT_LANGUAGE = 'en'; @@ -41,6 +42,8 @@ export class MockTranslationApi implements TranslationApi { supportedLngs: [DEFAULT_LANGUAGE], interpolation: { escapeValue: false, + // Used for the JsxInterpolator format hook + alwaysFormat: true, }, ns: [], defaultNS: false, @@ -55,14 +58,18 @@ export class MockTranslationApi implements TranslationApi { throw new Error('i18next was unexpectedly not initialized'); } - return new MockTranslationApi(i18n); + const interpolator = JsxInterpolator.fromI18n(i18n); + + return new MockTranslationApi(i18n, interpolator); } #i18n: I18n; + #interpolator: JsxInterpolator; #registeredRefs = new Set(); - private constructor(i18n: I18n) { + private constructor(i18n: I18n, interpolator: JsxInterpolator) { this.#i18n = i18n; + this.#interpolator = interpolator; } getTranslation( @@ -81,10 +88,9 @@ export class MockTranslationApi implements TranslationApi { ); } - const t = this.#i18n.getFixedT( - null, - internalRef.id, - ) as TranslationFunction; + const t = this.#interpolator.wrapT( + this.#i18n.getFixedT(null, internalRef.id), + ); return { ready: true, diff --git a/packages/theme/catalog-info.yaml b/packages/theme/catalog-info.yaml index 1fee85f58d..f2faf9ed42 100644 --- a/packages/theme/catalog-info.yaml +++ b/packages/theme/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/types/catalog-info.yaml b/packages/types/catalog-info.yaml index 1bd30379cc..cd20c3758e 100644 --- a/packages/types/catalog-info.yaml +++ b/packages/types/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: production type: backstage-common-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/version-bridge/catalog-info.yaml b/packages/version-bridge/catalog-info.yaml index 6f37a4b276..43899985ab 100644 --- a/packages/version-bridge/catalog-info.yaml +++ b/packages/version-bridge/catalog-info.yaml @@ -4,9 +4,8 @@ metadata: name: backstage-version-bridge title: '@backstage/version-bridge' description: >- - Utilities used by @backstage packages to support multiple concurrent - versions + Utilities used by @backstage packages to support multiple concurrent versions spec: lifecycle: production type: backstage-web-library - owner: maintainers + owner: framework-maintainers diff --git a/packages/yarn-plugin/catalog-info.yaml b/packages/yarn-plugin/catalog-info.yaml index c3a9be8fa5..dbb07a7602 100644 --- a/packages/yarn-plugin/catalog-info.yaml +++ b/packages/yarn-plugin/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: tooling-maintainers diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 78358a8970..ea622deaba 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-api-docs +## 0.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/plugin-catalog@1.29.1-next.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + ## 0.12.7-next.1 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 389f8265b6..4854695e23 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.12.7-next.1", + "version": "0.12.7-next.2", "description": "A Backstage plugin that helps represent API entities in the frontend", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app-backend/catalog-info.yaml b/plugins/app-backend/catalog-info.yaml index 27135f4f8d..d75255357b 100644 --- a/plugins/app-backend/catalog-info.yaml +++ b/plugins/app-backend/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app-node/catalog-info.yaml b/plugins/app-node/catalog-info.yaml index 6624153c6c..3eb9184e2e 100644 --- a/plugins/app-node/catalog-info.yaml +++ b/plugins/app-node/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app-visualizer/CHANGELOG.md b/plugins/app-visualizer/CHANGELOG.md index 4f3b6f2178..9d0513b41a 100644 --- a/plugins/app-visualizer/CHANGELOG.md +++ b/plugins/app-visualizer/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-app-visualizer +## 0.1.19-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + ## 0.1.19-next.0 ### Patch Changes diff --git a/plugins/app-visualizer/catalog-info.yaml b/plugins/app-visualizer/catalog-info.yaml index cb444af524..ed59a5eb45 100644 --- a/plugins/app-visualizer/catalog-info.yaml +++ b/plugins/app-visualizer/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app-visualizer/package.json b/plugins/app-visualizer/package.json index 057a383b20..d481e89225 100644 --- a/plugins/app-visualizer/package.json +++ b/plugins/app-visualizer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-visualizer", - "version": "0.1.19-next.0", + "version": "0.1.19-next.1", "description": "Visualizes the Backstage app structure", "backstage": { "role": "frontend-plugin", diff --git a/plugins/app/CHANGELOG.md b/plugins/app/CHANGELOG.md index a45b254f79..790b735bb1 100644 --- a/plugins/app/CHANGELOG.md +++ b/plugins/app/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-app +## 0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + ## 0.1.9-next.1 ### Patch Changes diff --git a/plugins/app/catalog-info.yaml b/plugins/app/catalog-info.yaml index 5bae243ea7..3396a5c98e 100644 --- a/plugins/app/catalog-info.yaml +++ b/plugins/app/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-frontend-plugin - owner: maintainers + owner: framework-maintainers diff --git a/plugins/app/package.json b/plugins/app/package.json index 5cada40ebc..fed1f47f4c 100644 --- a/plugins/app/package.json +++ b/plugins/app/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app", - "version": "0.1.9-next.1", + "version": "0.1.9-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "app", diff --git a/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml b/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml index d3e2c11c17..dc54d48600 100644 --- a/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-atlassian-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-auth0-provider/catalog-info.yaml b/plugins/auth-backend-module-auth0-provider/catalog-info.yaml index 26b540ca8a..46503619da 100644 --- a/plugins/auth-backend-module-auth0-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-auth0-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml b/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml index b3ced6b59d..8282046b05 100644 --- a/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-aws-alb-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml b/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml index 174f1a52e4..edbfcbca30 100644 --- a/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-azure-easyauth-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml b/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml index 817eb561a5..2e6f8d4481 100644 --- a/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-bitbucket-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml b/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml index bcb659b1c2..893f873c4e 100644 --- a/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-bitbucket-server-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml b/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml index cdd42a040b..0c8547e048 100644 --- a/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-cloudflare-access-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-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 index ef604027df..a043e002c3 100644 --- a/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-gcp-iap-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-github-provider/catalog-info.yaml b/plugins/auth-backend-module-github-provider/catalog-info.yaml index 8eda78fdaa..f8c11a2a17 100644 --- a/plugins/auth-backend-module-github-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-github-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml b/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml index 55086778e4..5d355a19ad 100644 --- a/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-gitlab-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-google-provider/catalog-info.yaml b/plugins/auth-backend-module-google-provider/catalog-info.yaml index 0897a12168..f32053ea3f 100644 --- a/plugins/auth-backend-module-google-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-google-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-guest-provider/catalog-info.yaml b/plugins/auth-backend-module-guest-provider/catalog-info.yaml index 5d3513296b..45a2bd8701 100644 --- a/plugins/auth-backend-module-guest-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-guest-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml b/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml index 1742fc9ed6..4f6609bef3 100644 --- a/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-microsoft-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml b/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml index 4ee7997b0c..cae10fccd6 100644 --- a/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-oauth2-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml b/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml index c26202c97d..7518c1b534 100644 --- a/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-oauth2-proxy-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-oidc-provider/catalog-info.yaml b/plugins/auth-backend-module-oidc-provider/catalog-info.yaml index 896738898b..207ee0dd8f 100644 --- a/plugins/auth-backend-module-oidc-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-oidc-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-okta-provider/catalog-info.yaml b/plugins/auth-backend-module-okta-provider/catalog-info.yaml index 7a5536edcf..e12e80b4ff 100644 --- a/plugins/auth-backend-module-okta-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-okta-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml b/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml index 6988215b59..9995800641 100644 --- a/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-onelogin-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml b/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml index 9d1ef1c299..5fa819c9d9 100644 --- a/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-pinniped-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml b/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml index d40b1e4308..e35b58128d 100644 --- a/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml +++ b/plugins/auth-backend-module-vmware-cloud-provider/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin-module - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-backend/catalog-info.yaml b/plugins/auth-backend/catalog-info.yaml index 38ac5f0753..1fbb18685d 100644 --- a/plugins/auth-backend/catalog-info.yaml +++ b/plugins/auth-backend/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-backend-plugin - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-node/catalog-info.yaml b/plugins/auth-node/catalog-info.yaml index 4b23642810..936aae901e 100644 --- a/plugins/auth-node/catalog-info.yaml +++ b/plugins/auth-node/catalog-info.yaml @@ -6,4 +6,4 @@ metadata: spec: lifecycle: experimental type: backstage-node-library - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-react/CHANGELOG.md b/plugins/auth-react/CHANGELOG.md index 238b119291..0a1043daa1 100644 --- a/plugins/auth-react/CHANGELOG.md +++ b/plugins/auth-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-react +## 0.1.15-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/errors@1.2.7 + ## 0.1.15-next.0 ### Patch Changes diff --git a/plugins/auth-react/catalog-info.yaml b/plugins/auth-react/catalog-info.yaml index b75aa32c56..1118130d74 100644 --- a/plugins/auth-react/catalog-info.yaml +++ b/plugins/auth-react/catalog-info.yaml @@ -7,4 +7,4 @@ metadata: spec: lifecycle: experimental type: backstage-web-library - owner: maintainers + owner: auth-maintainers diff --git a/plugins/auth-react/package.json b/plugins/auth-react/package.json index 214276dbc6..3a04afbf51 100644 --- a/plugins/auth-react/package.json +++ b/plugins/auth-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-react", - "version": "0.1.15-next.0", + "version": "0.1.15-next.1", "description": "Web library for the auth plugin", "backstage": { "role": "web-library", diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 68006127be..5cff146b61 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.3.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + ## 0.3.0-next.1 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index ecacd6966c..7b336e3455 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", - "version": "0.3.0-next.1", + "version": "0.3.0-next.2", "description": "Common functionalities for bitbucket-cloud plugins", "backstage": { "role": "common-library", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 929fac5677..600a4f7e93 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.4.11-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.4.11-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 0308b1f6e1..157fe665b0 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", - "version": "0.4.11-next.1", + "version": "0.4.11-next.2", "description": "A Backstage catalog backend module that helps integrate towards AWS", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 600cb2080c..9ce4ec3f4e 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + ## 0.3.5-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index e8dcea480b..5fc27e581d 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", - "version": "0.3.5-next.1", + "version": "0.3.5-next.2", "description": "A Backstage catalog backend module that helps integrate towards Azure", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index c203b575c8..f562d20651 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.4.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.4.8-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index eeae1afeb7..8d73b5fd74 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", - "version": "0.4.8-next.1", + "version": "0.4.8-next.2", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 18ea70c09b..ecd451bd5a 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.4.1-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index 0c3b601cb1..9dd5e16e55 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.4.1-next.1", + "version": "0.4.1-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 66de538a1a..e207e24dcf 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + ## 0.3.2-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index c907cb6bfd..9d176a1a9d 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.3.2-next.1", + "version": "0.3.2-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "catalog", diff --git a/plugins/catalog-backend-module-github-org/CHANGELOG.md b/plugins/catalog-backend-module-github-org/CHANGELOG.md index 7a8de4a045..14fb414c48 100644 --- a/plugins/catalog-backend-module-github-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-github-org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-github-org +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend-module-github@0.9.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-github-org/package.json b/plugins/catalog-backend-module-github-org/package.json index c445e926c6..ce47411b0a 100644 --- a/plugins/catalog-backend-module-github-org/package.json +++ b/plugins/catalog-backend-module-github-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github-org", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "description": "The github-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index 9e28f853f9..51e1498de5 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-backend-module-github +## 0.9.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.9.0-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 84fd003bcb..bb2e436804 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-github", - "version": "0.9.0-next.1", + "version": "0.9.0-next.2", "description": "A Backstage catalog backend module that helps integrate towards GitHub", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md index 64f599f3a0..7d5701a31d 100644 --- a/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab-org/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-gitlab-org +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend-module-gitlab@0.6.6-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab-org/package.json b/plugins/catalog-backend-module-gitlab-org/package.json index 39f1864944..44015c6c16 100644 --- a/plugins/catalog-backend-module-gitlab-org/package.json +++ b/plugins/catalog-backend-module-gitlab-org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab-org", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The gitlab-org backend module for the catalog plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index 44623a8405..76e67018cf 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.6.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.6.6-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 10f5c92b8b..e129b83cd9 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", - "version": "0.6.6-next.1", + "version": "0.6.6-next.2", "description": "A Backstage catalog backend module that helps integrate towards GitLab", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md index 988e281d76..98efe68781 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md +++ b/plugins/catalog-backend-module-incremental-ingestion/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-backend-module-incremental-ingestion +## 0.7.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.7.0-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-incremental-ingestion/package.json b/plugins/catalog-backend-module-incremental-ingestion/package.json index fdf6641505..60b3873e0b 100644 --- a/plugins/catalog-backend-module-incremental-ingestion/package.json +++ b/plugins/catalog-backend-module-incremental-ingestion/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-incremental-ingestion", - "version": "0.7.0-next.1", + "version": "0.7.0-next.2", "description": "An entity provider for streaming large asset sources into the catalog", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index cc7072e891..431a672828 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.11.5-next.2 + +### Patch Changes + +- e253d1d: Improves error reporting for missing metadata.name in LDAP catalog provider. +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + ## 0.11.5-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index bd04eb70c3..4d9888ac6d 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "version": "0.11.5-next.1", + "version": "0.11.5-next.2", "description": "A Backstage catalog backend module that helps integrate towards LDAP", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts index 007678c188..4e4f4f7cfd 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.test.ts @@ -988,6 +988,34 @@ describe('defaultUserTransformer', () => { }, }); }); + + it('throws and includes message when uid (metadata.name) is missing', async () => { + const config: UserConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'uid', + name: 'uid', + displayName: 'cn', + email: 'mail', + memberOf: 'memberOf', + }, + set: {}, + }; + + const entry = searchEntry({ + description: ['description-value'], + cn: ['cn-value'], + mail: ['mail-value'], + memberOf: ['x', 'y', 'z'], + }); + + await expect( + defaultUserTransformer(DefaultLdapVendor, config, entry), + ).rejects.toThrow( + "User syncing failed: missing 'uid' attribute, consider applying a user filter to skip processing users with incomplete data.", + ); + }); }); describe('defaultGroupTransformer', () => { @@ -1073,6 +1101,38 @@ describe('defaultGroupTransformer', () => { }, }); }); + + it('throws and includes message when cn (metadata.name) is missing', async () => { + const config: GroupConfig = { + dn: 'ddd', + options: {}, + map: { + rdn: 'cn', + name: 'cn', + displayName: 'cn', + email: 'mail', + description: 'description', + type: 'type', + members: 'members', + memberOf: 'memberOf', + }, + }; + + const entry = searchEntry({ + description: ['description-value'], + mail: ['mail-value'], + avatarUrl: ['avatarUrl-value'], + memberOf: ['x', 'y', 'z'], + entryDN: ['dn-value'], + entryUUID: ['uuid-value'], + }); + + await expect( + defaultGroupTransformer(DefaultLdapVendor, config, entry), + ).rejects.toThrow( + "Group syncing failed: missing 'cn' attribute, consider applying a group filter to skip processing groups with incomplete data.", + ); + }); }); /** diff --git a/plugins/catalog-backend-module-ldap/src/ldap/read.ts b/plugins/catalog-backend-module-ldap/src/ldap/read.ts index 176d6fca7a..9b9041913d 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/read.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/read.ts @@ -34,6 +34,7 @@ import { LdapVendor } from './vendors'; import { GroupTransformer, UserTransformer } from './types'; import { mapStringAttr } from './util'; import { LoggerService } from '@backstage/backend-plugin-api'; +import { InputError } from '@backstage/errors'; /** * The default implementation of the transformation from an LDAP entry to a @@ -70,6 +71,13 @@ export async function defaultUserTransformer( mapStringAttr(entry, vendor, map.name, v => { entity.metadata.name = v; }); + + if (!entity.metadata.name) { + throw new InputError( + `User syncing failed: missing '${map.name}' attribute, consider applying a user filter to skip processing users with incomplete data.`, + ); + } + mapStringAttr(entry, vendor, map.description, v => { entity.metadata.description = v; }); @@ -180,6 +188,13 @@ export async function defaultGroupTransformer( mapStringAttr(entry, vendor, map.name, v => { entity.metadata.name = v; }); + + if (!entity.metadata.name) { + throw new InputError( + `Group syncing failed: missing '${map.name}' attribute, consider applying a group filter to skip processing groups with incomplete data.`, + ); + } + mapStringAttr(entry, vendor, map.description, v => { entity.metadata.description = v; }); diff --git a/plugins/catalog-backend-module-logs/CHANGELOG.md b/plugins/catalog-backend-module-logs/CHANGELOG.md index 73ba915c4a..a7e0d37921 100644 --- a/plugins/catalog-backend-module-logs/CHANGELOG.md +++ b/plugins/catalog-backend-module-logs/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-logs +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@2.0.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-logs/package.json b/plugins/catalog-backend-module-logs/package.json index 27a5a04766..a6f748e4ca 100644 --- a/plugins/catalog-backend-module-logs/package.json +++ b/plugins/catalog-backend-module-logs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-logs", - "version": "0.1.10-next.1", + "version": "0.1.10-next.2", "description": "A module that subscribes to catalog related events and logs them.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index b403e55dd1..c6b53d288d 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + ## 0.2.10-next.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index 75ea03d23d..5f0c106b0d 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", - "version": "0.2.10-next.1", + "version": "0.2.10-next.2", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index f5fd6342f9..1f9802a3e8 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog-backend +## 2.0.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 2.0.0-next.1 ### Major Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8f2f8afe94..3b548a9f67 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "2.0.0-next.1", + "version": "2.0.0-next.2", "description": "The Backstage backend plugin that provides the Backstage catalog", "backstage": { "role": "backend-plugin", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 269dcf2a4f..7f669774f8 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog-graph +## 0.4.19-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + ## 0.4.19-next.1 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 08d88ceca7..d674ca6f95 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.4.19-next.1", + "version": "0.4.19-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-graph", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 8ede217379..3722f55f86 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-import +## 0.13.0-next.2 + +### Minor Changes + +- e2fd549: **BREAKING**: `generateStepper` and `defaultGenerateStepper` now require a translation argument to be passed through for supporting translations. + +### Patch Changes + +- 66a1140: Add i18n support for `catalog-import` plugin. +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/plugin-catalog-common@1.1.4-next.0 + ## 0.12.14-next.1 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index b6730e7fd6..cb046ecfb4 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.12.14-next.1", + "version": "0.13.0-next.2", "description": "A Backstage plugin the helps you import entities into your catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog-import/report-alpha.api.md b/plugins/catalog-import/report-alpha.api.md index 24789a42e4..d4dab4549a 100644 --- a/plugins/catalog-import/report-alpha.api.md +++ b/plugins/catalog-import/report-alpha.api.md @@ -16,8 +16,69 @@ import { TranslationRef } from '@backstage/core-plugin-api/alpha'; export const catalogImportTranslationRef: TranslationRef< 'catalog-import', { - readonly pageTitle: 'Register an existing component'; + readonly 'buttons.back': 'Back'; + readonly 'defaultImportPage.headerTitle': 'Register an existing component'; + readonly 'defaultImportPage.contentHeaderTitle': 'Start tracking your component in {{appTitle}}'; + readonly 'defaultImportPage.supportTitle': 'Start tracking your component in {{appTitle}} by adding it to the software catalog.'; readonly 'importInfoCard.title': 'Register an existing component'; + readonly 'importInfoCard.deepLinkTitle': 'Learn more about the Software Catalog'; + readonly 'importInfoCard.linkDescription': 'Enter the URL to your source code repository to add it to {{appTitle}}.'; + readonly 'importInfoCard.fileLinkTitle': 'Link to an existing entity file'; + readonly 'importInfoCard.examplePrefix': 'Example: '; + readonly 'importInfoCard.fileLinkDescription': 'The wizard analyzes the file, previews the entities, and adds them to the {{appTitle}} catalog.'; + readonly 'importInfoCard.githubIntegration.label': 'GitHub only'; + readonly 'importInfoCard.githubIntegration.title': 'Link to a repository'; + readonly 'importStepper.finish.title': 'Finish'; + readonly 'importStepper.noLocation.title': 'Create Pull Request'; + readonly 'importStepper.noLocation.createPr.ownerLabel': 'Entity Owner'; + readonly 'importStepper.noLocation.createPr.detailsTitle': 'Pull Request Details'; + readonly 'importStepper.noLocation.createPr.titleLabel': 'Pull Request Title'; + readonly 'importStepper.noLocation.createPr.titlePlaceholder': 'Add Backstage catalog entity descriptor files'; + readonly 'importStepper.noLocation.createPr.bodyLabel': 'Pull Request Body'; + readonly 'importStepper.noLocation.createPr.bodyPlaceholder': 'A describing text with Markdown support'; + readonly 'importStepper.noLocation.createPr.configurationTitle': 'Entity Configuration'; + readonly 'importStepper.noLocation.createPr.componentNameLabel': 'Name of the created component'; + readonly 'importStepper.noLocation.createPr.componentNamePlaceholder': 'my-component'; + readonly 'importStepper.noLocation.createPr.ownerLoadingText': 'Loading groups…'; + readonly 'importStepper.noLocation.createPr.ownerHelperText': 'Select an owner from the list or enter a reference to a Group or a User'; + readonly 'importStepper.noLocation.createPr.ownerErrorHelperText': 'required value'; + readonly 'importStepper.noLocation.createPr.ownerPlaceholder': 'my-group'; + readonly 'importStepper.noLocation.createPr.codeownersHelperText': 'WARNING: This may fail if no CODEOWNERS file is found at the target location.'; + readonly 'importStepper.singleLocation.title': 'Select Locations'; + readonly 'importStepper.singleLocation.description': 'Discovered Locations: 1'; + readonly 'importStepper.multipleLocations.title': 'Select Locations'; + readonly 'importStepper.multipleLocations.description': 'Discovered Locations: {{length, number}}'; + readonly 'importStepper.analyze.title': 'Select URL'; + readonly 'importStepper.prepare.title': 'Import Actions'; + readonly 'importStepper.prepare.description': 'Optional'; + readonly 'importStepper.review.title': 'Review'; + readonly 'stepFinishImportLocation.repository.title': 'The following Pull Request has been opened: '; + readonly 'stepFinishImportLocation.repository.description': 'Your entities will be imported as soon as the Pull Request is merged.'; + readonly 'stepFinishImportLocation.backButtonText': 'Register another'; + readonly 'stepFinishImportLocation.locations.new': 'The following entities have been added to the catalog:'; + readonly 'stepFinishImportLocation.locations.backButtonText': 'Register another'; + readonly 'stepFinishImportLocation.locations.existing': 'A refresh was triggered for the following locations:'; + readonly 'stepFinishImportLocation.locations.viewButtonText': 'View Component'; + readonly 'stepInitAnalyzeUrl.error.default': 'Received unknown analysis result of type {{type}}. Please contact the support team.'; + readonly 'stepInitAnalyzeUrl.error.url': 'Must start with http:// or https://.'; + readonly 'stepInitAnalyzeUrl.error.repository': "Couldn't generate entities for your repository"; + readonly 'stepInitAnalyzeUrl.error.locations': 'There are no entities at this location'; + readonly 'stepInitAnalyzeUrl.urlHelperText': 'Enter the full path to your entity file to start tracking your component'; + readonly 'stepInitAnalyzeUrl.nextButtonText': 'Analyze'; + readonly 'stepPrepareCreatePullRequest.nextButtonText': 'Create PR'; + readonly 'stepPrepareCreatePullRequest.previewPr.title': 'Preview Pull Request'; + readonly 'stepPrepareCreatePullRequest.previewPr.subheader': 'Create a new Pull Request'; + readonly 'stepPrepareCreatePullRequest.previewCatalogInfo.title': 'Preview Entities'; + readonly 'stepPrepareSelectLocations.locations.description': 'Select one or more locations that are present in your git repository:'; + readonly 'stepPrepareSelectLocations.locations.selectAll': 'Select All'; + readonly 'stepPrepareSelectLocations.nextButtonText': 'Review'; + readonly 'stepPrepareSelectLocations.existingLocations.description': 'These locations already exist in the catalog:'; + readonly 'stepReviewLocation.refresh': 'Refresh'; + readonly 'stepReviewLocation.import': 'Import'; + readonly 'stepReviewLocation.catalog.new': 'The following entities will be added to the catalog:'; + readonly 'stepReviewLocation.catalog.exists': 'The following locations already exist in the catalog:'; + readonly 'stepReviewLocation.prepareResult.title': 'The following Pull Request has been opened: '; + readonly 'stepReviewLocation.prepareResult.description': 'You can already import the location and {{appTitle}} will fetch the entities as soon as the Pull Request is merged.'; } >; diff --git a/plugins/catalog-import/report.api.md b/plugins/catalog-import/report.api.md index 9bc71a4296..ed7d2f3347 100644 --- a/plugins/catalog-import/report.api.md +++ b/plugins/catalog-import/report.api.md @@ -6,6 +6,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha'; import { ComponentProps } from 'react'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { ConfigApi } from '@backstage/core-plugin-api'; @@ -24,6 +25,7 @@ import { ScmAuthApi } from '@backstage/integration-react'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { SubmitHandler } from 'react-hook-form'; import { TextFieldProps } from '@material-ui/core/TextField/TextField'; +import { TranslationFunction } from '@backstage/core-plugin-api/alpha'; import { UseFormProps } from 'react-hook-form'; import { UseFormReturn } from 'react-hook-form'; @@ -145,6 +147,7 @@ export { catalogImportPlugin as plugin }; export function defaultGenerateStepper( flow: ImportFlows, defaults: StepperProvider, + t: TranslationFunction, ): StepperProvider; // @public @@ -213,6 +216,7 @@ export interface ImportStepperProps { generateStepper?: ( flow: ImportFlows, defaults: StepperProvider, + t: TranslationFunction, ) => StepperProvider; // (undocumented) initialUrl?: string; diff --git a/plugins/catalog-import/src/alpha.tsx b/plugins/catalog-import/src/alpha.tsx index 0d1599ee5c..3fade2d92b 100644 --- a/plugins/catalog-import/src/alpha.tsx +++ b/plugins/catalog-import/src/alpha.tsx @@ -92,3 +92,5 @@ export default createFrontendPlugin({ importPage: convertLegacyRouteRef(rootRouteRef), }, }); + +export { catalogImportTranslationRef } from './translation'; diff --git a/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts b/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts index 9f058dc744..00e77461b6 100644 --- a/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts +++ b/plugins/catalog-import/src/api/AzureRepoApiClient.test.ts @@ -154,7 +154,7 @@ function mockPrEndpoint() { describe('RepoApiClient', () => { const server = setupServer(); registerMswTestHooks(server); - const testToken = new Date().toString(); + const testToken = new Date().toLocaleString('en-US'); const sut = new RepoApiClient({ project: 'project', tenantUrl: 'https://dev.azure.com/acme', @@ -316,7 +316,7 @@ describe('createAzurePullRequest', () => { }); it('should create a new Pull request', async () => { - const testToken = new Date().getTime().toString(); + const testToken = new Date().toLocaleString('en-US'); const options: AzurePrOptions = { tenantUrl: 'https://dev.azure.com/acme', repository: 'test', diff --git a/plugins/catalog-import/src/components/Buttons/index.tsx b/plugins/catalog-import/src/components/Buttons/index.tsx index 6ba5a83fb5..f709f65a75 100644 --- a/plugins/catalog-import/src/components/Buttons/index.tsx +++ b/plugins/catalog-import/src/components/Buttons/index.tsx @@ -15,11 +15,14 @@ */ import { LinkButton } from '@backstage/core-components'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Button from '@material-ui/core/Button'; import CircularProgress from '@material-ui/core/CircularProgress'; import { makeStyles } from '@material-ui/core/styles'; import { ComponentProps } from 'react'; +import { catalogImportTranslationRef } from '../../translation'; + const useStyles = makeStyles(theme => ({ wrapper: { marginTop: theme.spacing(1), @@ -62,11 +65,12 @@ export const NextButton = ( }; export const BackButton = (props: ComponentProps) => { + const { t } = useTranslationRef(catalogImportTranslationRef); const classes = useStyles(); return ( ); }; diff --git a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx index 18ffe2bd8d..24a79866b3 100644 --- a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx +++ b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.tsx @@ -22,13 +22,14 @@ import { SupportButton, } from '@backstage/core-components'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Grid from '@material-ui/core/Grid'; -import useMediaQuery from '@material-ui/core/useMediaQuery'; import { useTheme } from '@material-ui/core/styles'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; + +import { catalogImportTranslationRef } from '../../translation'; import { ImportInfoCard } from '../ImportInfoCard'; import { ImportStepper } from '../ImportStepper'; -import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogImportTranslationRef } from '../../translation'; /** * The default catalog import page. @@ -42,8 +43,6 @@ export const DefaultImportPage = () => { const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const appTitle = configApi.getOptionalString('app.title') || 'Backstage'; - const supportTitle = `Start tracking your component in ${appTitle} by adding it to the software catalog.`; - const contentItems = [ @@ -56,10 +55,14 @@ export const DefaultImportPage = () => { return ( -
+
- - {supportTitle} + + + {t('defaultImportPage.supportTitle', { appTitle })} + diff --git a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx index fc15d01538..1777c0786b 100644 --- a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx +++ b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx @@ -16,11 +16,12 @@ import { InfoCard } from '@backstage/core-components'; import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Chip from '@material-ui/core/Chip'; import Typography from '@material-ui/core/Typography'; + import { catalogImportApiRef } from '../../api'; import { useCatalogFilename } from '../../hooks'; -import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; import { catalogImportTranslationRef } from '../../translation'; /** @@ -58,31 +59,37 @@ export const ImportInfoCard = (props: ImportInfoCardProps) => { title={t('importInfoCard.title')} titleTypographyProps={{ component: 'h3' }} deepLink={{ - title: 'Learn more about the Software Catalog', + title: t('importInfoCard.deepLinkTitle'), link: 'https://backstage.io/docs/features/software-catalog/', }} > - Enter the URL to your source code repository to add it to {appTitle}. + {t('importInfoCard.linkDescription', { appTitle })} - Link to an existing entity file + {t('importInfoCard.fileLinkTitle')} - Example: {exampleLocationUrl} + {t('importInfoCard.examplePrefix')} + {exampleLocationUrl} - The wizard analyzes the file, previews the entities, and adds them to - the {appTitle} catalog. + {t('importInfoCard.fileLinkDescription', { appTitle })} {hasGithubIntegration && ( <> - Link to a repository{' '} - + {t('importInfoCard.githubIntegration.title')} + - Example: {exampleRepositoryUrl} + {t('importInfoCard.examplePrefix')} + {exampleRepositoryUrl} The wizard discovers all {catalogFilename} files in the diff --git a/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx b/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx index 30d02032b6..614751c292 100644 --- a/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/ImportStepper.tsx @@ -16,11 +16,15 @@ import { InfoCard, InfoCardVariants } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; +import { TranslationFunction } from '@backstage/core-plugin-api/alpha'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha'; import Step from '@material-ui/core/Step'; import StepContent from '@material-ui/core/StepContent'; import Stepper from '@material-ui/core/Stepper'; import { makeStyles } from '@material-ui/core/styles'; import { useMemo } from 'react'; + import { catalogImportApiRef } from '../../api'; import { ImportFlows, ImportState, useImportState } from '../useImportState'; import { @@ -46,6 +50,7 @@ export interface ImportStepperProps { generateStepper?: ( flow: ImportFlows, defaults: StepperProvider, + t: TranslationFunction, ) => StepperProvider; variant?: InfoCardVariants; } @@ -56,6 +61,7 @@ export interface ImportStepperProps { * @public */ export const ImportStepper = (props: ImportStepperProps) => { + const { t } = useTranslationRef(catalogImportTranslationRef); const { initialUrl, generateStepper = defaultGenerateStepper, @@ -67,8 +73,8 @@ export const ImportStepper = (props: ImportStepperProps) => { const state = useImportState({ initialUrl }); const states = useMemo( - () => generateStepper(state.activeFlow, defaultStepper), - [generateStepper, state.activeFlow], + () => generateStepper(state.activeFlow, defaultStepper, t), + [generateStepper, state.activeFlow, t], ); const render = (step: StepConfiguration) => { @@ -90,25 +96,25 @@ export const ImportStepper = (props: ImportStepperProps) => { {render( states.analyze( state as Extract, - { apis: { catalogImportApi } }, + { apis: { catalogImportApi }, t }, ), )} {render( states.prepare( state as Extract, - { apis: { catalogImportApi } }, + { apis: { catalogImportApi }, t }, ), )} {render( states.review( state as Extract, - { apis: { catalogImportApi } }, + { apis: { catalogImportApi }, t }, ), )} {render( states.finish( state as Extract, - { apis: { catalogImportApi } }, + { apis: { catalogImportApi }, t }, ), )} diff --git a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx index d23ffb4db9..7d597c75ad 100644 --- a/plugins/catalog-import/src/components/ImportStepper/defaults.tsx +++ b/plugins/catalog-import/src/components/ImportStepper/defaults.tsx @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TranslationFunction } from '@backstage/core-plugin-api/alpha'; +import { catalogImportTranslationRef } from '@backstage/plugin-catalog-import/alpha'; import Box from '@material-ui/core/Box'; import Checkbox from '@material-ui/core/Checkbox'; import FormControlLabel from '@material-ui/core/FormControlLabel'; @@ -48,19 +50,31 @@ export type StepConfiguration = { export interface StepperProvider { analyze: ( s: Extract, - opts: { apis: StepperApis }, + opts: { + apis: StepperApis; + t: TranslationFunction; + }, ) => StepConfiguration; prepare: ( s: Extract, - opts: { apis: StepperApis }, + opts: { + apis: StepperApis; + t: TranslationFunction; + }, ) => StepConfiguration; review: ( s: Extract, - opts: { apis: StepperApis }, + opts: { + apis: StepperApis; + t: TranslationFunction; + }, ) => StepConfiguration; finish: ( s: Extract, - opts: { apis: StepperApis }, + opts: { + apis: StepperApis; + t: TranslationFunction; + }, ) => StepConfiguration; } @@ -72,11 +86,13 @@ export interface StepperProvider { * * @param flow - the name of the active flow * @param defaults - the default steps + * @param t - the translation function * @public */ export function defaultGenerateStepper( flow: ImportFlows, defaults: StepperProvider, + t: TranslationFunction, ): StepperProvider { switch (flow) { // the prepare step is skipped but the label of the step is updated @@ -88,11 +104,11 @@ export function defaultGenerateStepper( - Discovered Locations: 1 + {t('importStepper.singleLocation.description')} } > - Select Locations + {t('importStepper.singleLocation.title')} ), content: <>, @@ -113,11 +129,13 @@ export function defaultGenerateStepper( - Discovered Locations: {state.analyzeResult.locations.length} + {t('importStepper.multipleLocations.description', { + length: state.analyzeResult.locations.length, + })} } > - Select Locations + {t('importStepper.multipleLocations.title')} ), content: ( @@ -141,7 +159,9 @@ export function defaultGenerateStepper( } return { - stepLabel: Create Pull Request, + stepLabel: ( + {t('importStepper.noLocation.title')} + ), content: ( ( <> - Pull Request Details + + {t('importStepper.noLocation.createPr.detailsTitle')} + - Entity Configuration + + {t( + 'importStepper.noLocation.createPr.configurationTitle', + )} + - WARNING: This may fail if no CODEOWNERS file is found at - the target location. + {t( + 'importStepper.noLocation.createPr.codeownersHelperText', + )} )} @@ -261,8 +306,8 @@ export function defaultGenerateStepper( } export const defaultStepper: StepperProvider = { - analyze: (state, { apis }) => ({ - stepLabel: Select URL, + analyze: (state, { apis, t }) => ({ + stepLabel: {t('importStepper.analyze.title')}, content: ( ({ + prepare: (state, { t }) => ({ stepLabel: ( - Optional}> - Import Actions + + {t('importStepper.prepare.description')} + + } + > + {t('importStepper.prepare.title')} ), content: , }), - review: state => ({ - stepLabel: Review, + review: (state, { t }) => ({ + stepLabel: {t('importStepper.review.title')}, content: ( ({ - stepLabel: Finish, + finish: (state, { t }) => ({ + stepLabel: {t('importStepper.finish.title')}, content: ( { + const { t } = useTranslationRef(catalogImportTranslationRef); const entityRoute = useRouteRef(entityRouteRef); if (prepareResult.type === 'repository') { return ( <> - The following Pull Request has been opened:{' '} + {t('stepFinishImportLocation.repository.title')} { - Your entities will be imported as soon as the Pull Request is merged. + {t('stepFinishImportLocation.repository.description')} - Register another + + {t('stepFinishImportLocation.backButtonText')} + ); @@ -94,9 +100,7 @@ export const StepFinishImportLocation = ({ prepareResult, onReset }: Props) => { <> {newLocations.length > 0 && ( <> - - The following entities have been added to the catalog: - + {t('stepFinishImportLocation.locations.new')} { {existingLocations.length > 0 && ( <> - A refresh was triggered for the following locations: + {t('stepFinishImportLocation.locations.existing')} { {newComponentEntity && ( - View Component + {t('stepFinishImportLocation.locations.viewButtonText')} )} - Register another + + {t('stepFinishImportLocation.backButtonText')} + ); diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx index b1d5de7527..ed6fb5683f 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx @@ -15,8 +15,8 @@ */ import { errorApiRef } from '@backstage/core-plugin-api'; -import { TestApiProvider } from '@backstage/test-utils'; -import { act, render, screen } from '@testing-library/react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; import { AnalyzeResult, catalogImportApiRef } from '../../api/'; @@ -60,23 +60,24 @@ describe('', () => { }); it('renders without exploding', async () => { - render( undefined} />, { - wrapper: Wrapper, - }); + await renderInTestApp( + + undefined} /> + , + ); expect(screen.getByRole('textbox', { name: /URL/i })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: /URL/i })).toHaveValue(''); }); it('should use default analysis url', async () => { - render( - undefined} - analysisUrl="https://default" - />, - { - wrapper: Wrapper, - }, + await renderInTestApp( + + undefined} + analysisUrl="https://default" + /> + , ); expect(screen.getByRole('textbox', { name: /URL/i })).toBeInTheDocument(); @@ -88,9 +89,11 @@ describe('', () => { it('should not analyze without url', async () => { const onAnalysisFn = jest.fn(); - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); await act(async () => { try { @@ -108,9 +111,11 @@ describe('', () => { it('should not analyze invalid value', async () => { const onAnalysisFn = jest.fn(); - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); await act(async () => { await userEvent.type( @@ -136,9 +141,11 @@ describe('', () => { locations: [location], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -170,9 +177,11 @@ describe('', () => { locations: [location, location], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -203,9 +212,11 @@ describe('', () => { locations: [], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -244,9 +255,11 @@ describe('', () => { ], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -279,9 +292,11 @@ describe('', () => { generatedEntities: [], } as AnalyzeResult; - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve(analyzeResult), @@ -320,11 +335,10 @@ describe('', () => { ], } as AnalyzeResult; - render( - , - { - wrapper: Wrapper, - }, + await renderInTestApp( + + + , ); catalogImportApi.analyzeUrl.mockReturnValueOnce( @@ -349,9 +363,11 @@ describe('', () => { it('should report unknown type to the errorapi', async () => { const onAnalysisFn = jest.fn(); - render(, { - wrapper: Wrapper, - }); + await renderInTestApp( + + + , + ); catalogImportApi.analyzeUrl.mockReturnValueOnce( Promise.resolve({ type: 'unknown' } as any as AnalyzeResult), diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx index 2e2994aeb4..5a6cad055c 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.tsx @@ -15,12 +15,15 @@ */ import { errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import FormHelperText from '@material-ui/core/FormHelperText'; import Grid from '@material-ui/core/Grid'; import TextField from '@material-ui/core/TextField'; import { useCallback, useState } from 'react'; import { useForm } from 'react-hook-form'; + import { AnalyzeResult, catalogImportApiRef } from '../../api'; +import { catalogImportTranslationRef } from '../../translation'; import { NextButton } from '../Buttons'; import { asInputRef } from '../helpers'; import { ImportFlows, PrepareResult } from '../useImportState'; @@ -55,6 +58,7 @@ export interface StepInitAnalyzeUrlProps { * @public */ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { + const { t } = useTranslationRef(catalogImportTranslationRef); const { onAnalysis, analysisUrl = '', @@ -95,7 +99,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { ) { onAnalysis('no-location', url, analysisResult); } else { - setError("Couldn't generate entities for your repository"); + setError(t('stepInitAnalyzeUrl.error.repository')); setSubmitted(false); } break; @@ -108,16 +112,16 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { } else if (analysisResult.locations.length > 1) { onAnalysis('multiple-locations', url, analysisResult); } else { - setError('There are no entities at this location'); + setError(t('stepInitAnalyzeUrl.error.locations')); setSubmitted(false); } break; } default: { - const err = `Received unknown analysis result of type ${ - (analysisResult as any).type - }. Please contact the support team.`; + const err = t('stepInitAnalyzeUrl.error.default', { + type: (analysisResult as any).type, + }); setError(err); setSubmitted(false); @@ -130,7 +134,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { setSubmitted(false); } }, - [catalogImportApi, disablePullRequest, errorApi, onAnalysis], + [catalogImportApi, disablePullRequest, errorApi, onAnalysis, t], ); return ( @@ -143,7 +147,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { httpsValidator: (value: any) => (typeof value === 'string' && value.match(/^http[s]?:\/\//) !== null) || - 'Must start with http:// or https://.', + t('stepInitAnalyzeUrl.error.url'), }, }), )} @@ -151,7 +155,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { id="url" label="URL" placeholder={exampleLocationUrl} - helperText="Enter the full path to your entity file to start tracking your component" + helperText={t('stepInitAnalyzeUrl.urlHelperText')} margin="normal" variant="outlined" error={Boolean(errors.url)} @@ -167,7 +171,7 @@ export const StepInitAnalyzeUrl = (props: StepInitAnalyzeUrlProps) => { loading={submitted} type="submit" > - Analyze + {t('stepInitAnalyzeUrl.nextButtonText')} diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.test.tsx index 45b244d582..d90f950794 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/PreviewPullRequestComponent.test.tsx @@ -14,8 +14,9 @@ * limitations under the License. */ +import { renderInTestApp } from '@backstage/test-utils'; import { makeStyles } from '@material-ui/core/styles'; -import { render, screen } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import { renderHook } from '@testing-library/react'; import { PreviewPullRequestComponent } from './PreviewPullRequestComponent'; @@ -27,7 +28,7 @@ const useStyles = makeStyles({ describe('', () => { it('renders without exploding', async () => { - render( + await renderInTestApp( ', () => { it('renders card with custom styles', async () => { const { result } = renderHook(() => useStyles()); - render( + await renderInTestApp( ', () => { const title = screen.getByText('My Title'); const description = screen.getByText('description', { selector: 'strong' }); expect(title).toBeInTheDocument(); - expect(title).not.toBeVisible(); expect(description).toBeInTheDocument(); - expect(description).not.toBeVisible(); + + // FIXME: https://github.com/testing-library/jest-dom/issues/444 + // expect(title).not.toBeVisible(); + // expect(description).not.toBeVisible(); + + const card = title.closest(`.${result.current.displayNone}`); + expect(card).toBeInTheDocument(); + expect(description.closest(`.${result.current.displayNone}`)).toBe(card); }); it('renders with custom styles', async () => { const { result } = renderHook(() => useStyles()); - render( + await renderInTestApp( { const { title, description, classes } = props; + const { t } = useTranslationRef(catalogImportTranslationRef); return ( - + diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx index edb2e391db..6f69b739bd 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx @@ -16,9 +16,14 @@ import { configApiRef, errorApiRef } from '@backstage/core-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; -import { TestApiProvider, mockApis } from '@backstage/test-utils'; +import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; import TextField from '@material-ui/core/TextField'; -import { render, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; import { AnalyzeResult, catalogImportApiRef } from '../../api'; @@ -27,7 +32,6 @@ import { generateEntities, StepPrepareCreatePullRequest, } from './StepPrepareCreatePullRequest'; -import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; describe('', () => { const catalogImportApi: jest.Mocked = { @@ -90,24 +94,23 @@ describe('', () => { it('renders without exploding', async () => { catalogApi.getEntities.mockReturnValue(Promise.resolve({ items: [] })); - render( - { - return ( - <> - - - - - - ); - }} - />, - { - wrapper: Wrapper, - }, + await renderInTestApp( + + { + return ( + <> + + + + + + ); + }} + /> + , ); const title = await screen.findByText('My title'); @@ -129,32 +132,31 @@ describe('', () => { }), ); - render( - { - return ( - <> - - - - - - ); - }} - />, - { - wrapper: Wrapper, - }, + await renderInTestApp( + + { + return ( + <> + + + + + + ); + }} + /> + , ); await userEvent.type(await screen.findByLabelText('name'), '-changed'); @@ -211,24 +213,23 @@ spec: new Error('some error'), ); - render( - { - return ( - <> - - - - - - ); - }} - />, - { - wrapper: Wrapper, - }, + await renderInTestApp( + + { + return ( + <> + + + + + + ); + }} + /> + , ); await userEvent.click( @@ -256,15 +257,14 @@ spec: }), ); - render( - , - { - wrapper: Wrapper, - }, + await renderInTestApp( + + + , ); await waitFor(() => { diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx index ce7448c0db..a40f4868e9 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.tsx @@ -17,6 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { errorApiRef, useApi } from '@backstage/core-plugin-api'; import { assertError } from '@backstage/errors'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { catalogApiRef, humanizeEntityRef, @@ -30,8 +31,10 @@ import { ReactNode, useCallback, useEffect, useState } from 'react'; import { NestedValue, UseFormReturn } from 'react-hook-form'; import useAsync from 'react-use/esm/useAsync'; import YAML from 'yaml'; + import { AnalyzeResult, catalogImportApiRef } from '../../api'; import { useCatalogFilename } from '../../hooks'; +import { catalogImportTranslationRef } from '../../translation'; import { PartialEntity } from '../../types'; import { BackButton, NextButton } from '../Buttons'; import { PrepareResult } from '../useImportState'; @@ -127,6 +130,7 @@ export const StepPrepareCreatePullRequest = ( ) => { const { analyzeResult, onPrepare, onGoBack, renderFormFields } = props; + const { t } = useTranslationRef(catalogImportTranslationRef); const classes = useStyles(); const catalogApi = useApi(catalogApiRef); const catalogImportApi = useApi(catalogImportApiRef); @@ -252,7 +256,9 @@ export const StepPrepareCreatePullRequest = ( })} - Preview Pull Request + + {t('stepPrepareCreatePullRequest.previewPr.title')} + - Preview Entities + + {t('stepPrepareCreatePullRequest.previewCatalogInfo.title')} + - Create PR + {t('stepPrepareCreatePullRequest.nextButtonText')} diff --git a/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx b/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx index 1d94b22bf4..5aa7760c98 100644 --- a/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx +++ b/plugins/catalog-import/src/components/StepPrepareSelectLocations/StepPrepareSelectLocations.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import Checkbox from '@material-ui/core/Checkbox'; import Grid from '@material-ui/core/Grid'; import ListItem from '@material-ui/core/ListItem'; @@ -21,12 +22,14 @@ import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemText from '@material-ui/core/ListItemText'; import Typography from '@material-ui/core/Typography'; import LocationOnIcon from '@material-ui/icons/LocationOn'; +import partition from 'lodash/partition'; import { useCallback, useState } from 'react'; + import { AnalyzeResult } from '../../api'; +import { catalogImportTranslationRef } from '../../translation'; import { BackButton, NextButton } from '../Buttons'; import { EntityListComponent } from '../EntityListComponent'; import { PrepareResult } from '../useImportState'; -import partition from 'lodash/partition'; type Props = { analyzeResult: Extract; @@ -49,6 +52,8 @@ export const StepPrepareSelectLocations = ({ onPrepare, onGoBack, }: Props) => { + const { t } = useTranslationRef(catalogImportTranslationRef); + const [selectedUrls, setSelectedUrls] = useState( prepareResult?.locations.map(l => l.target) || [], ); @@ -82,8 +87,7 @@ export const StepPrepareSelectLocations = ({ {locations.length > 0 && ( <> - Select one or more locations that are present in your git - repository: + {t('stepPrepareSelectLocations.locations.description')} - + } onItemClick={onItemClick} @@ -120,7 +126,9 @@ export const StepPrepareSelectLocations = ({ {existingLocations.length > 0 && ( <> - These locations already exist in the catalog: + + {t('stepPrepareSelectLocations.existingLocations.description')} + } @@ -133,7 +141,7 @@ export const StepPrepareSelectLocations = ({ {onGoBack && } - Review + {t('stepPrepareSelectLocations.nextButtonText')} diff --git a/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx b/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx index 122c219a8b..ddf2cd51d5 100644 --- a/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx +++ b/plugins/catalog-import/src/components/StepReviewLocation/StepReviewLocation.tsx @@ -14,20 +14,22 @@ * limitations under the License. */ +import { stringifyEntityRef } from '@backstage/catalog-model'; +import { Link } from '@backstage/core-components'; +import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api'; +import { assertError } from '@backstage/errors'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import FormHelperText from '@material-ui/core/FormHelperText'; import Grid from '@material-ui/core/Grid'; import Typography from '@material-ui/core/Typography'; import LocationOnIcon from '@material-ui/icons/LocationOn'; import { useCallback, useState } from 'react'; + import { BackButton, NextButton } from '../Buttons'; import { EntityListComponent } from '../EntityListComponent'; import { PrepareResult, ReviewResult } from '../useImportState'; - -import { configApiRef, useAnalytics, useApi } from '@backstage/core-plugin-api'; -import { Link } from '@backstage/core-components'; -import { stringifyEntityRef } from '@backstage/catalog-model'; -import { assertError } from '@backstage/errors'; +import { catalogImportTranslationRef } from '../../translation'; type Props = { prepareResult: PrepareResult; @@ -40,6 +42,7 @@ export const StepReviewLocation = ({ onReview, onGoBack, }: Props) => { + const { t } = useTranslationRef(catalogImportTranslationRef); const catalogApi = useApi(catalogApiRef); const configApi = useApi(configApiRef); const analytics = useAnalytics(); @@ -119,7 +122,7 @@ export const StepReviewLocation = ({ {prepareResult.type === 'repository' && ( <> - The following Pull Request has been opened:{' '} + {t('stepReviewLocation.prepareResult.title')} - You can already import the location and {appTitle} will fetch the - entities as soon as the Pull Request is merged. + {t('stepReviewLocation.prepareResult.description', { appTitle })} )} {exists - ? 'The following locations already exist in the catalog:' - : 'The following entities will be added to the catalog:'} + ? t('stepReviewLocation.catalog.exists') + : t('stepReviewLocation.catalog.new')} handleClick()} > - {exists ? 'Refresh' : 'Import'} + {exists + ? t('stepReviewLocation.refresh') + : t('stepReviewLocation.import')} diff --git a/plugins/catalog-import/src/translation.ts b/plugins/catalog-import/src/translation.ts index 353e09056b..857b551d9c 100644 --- a/plugins/catalog-import/src/translation.ts +++ b/plugins/catalog-import/src/translation.ts @@ -20,9 +20,132 @@ import { createTranslationRef } from '@backstage/core-plugin-api/alpha'; export const catalogImportTranslationRef = createTranslationRef({ id: 'catalog-import', messages: { - pageTitle: 'Register an existing component', + buttons: { + back: 'Back', + }, + defaultImportPage: { + headerTitle: 'Register an existing component', + contentHeaderTitle: 'Start tracking your component in {{appTitle}}', + supportTitle: + 'Start tracking your component in {{appTitle}} by adding it to the software catalog.', + }, importInfoCard: { title: 'Register an existing component', + deepLinkTitle: 'Learn more about the Software Catalog', + linkDescription: + 'Enter the URL to your source code repository to add it to {{appTitle}}.', + fileLinkTitle: 'Link to an existing entity file', + examplePrefix: 'Example: ', + fileLinkDescription: + 'The wizard analyzes the file, previews the entities, and adds them to the {{appTitle}} catalog.', + githubIntegration: { + title: 'Link to a repository', + label: 'GitHub only', + }, + }, + importStepper: { + singleLocation: { + title: 'Select Locations', + description: 'Discovered Locations: 1', + }, + multipleLocations: { + title: 'Select Locations', + description: 'Discovered Locations: {{length, number}}', + }, + noLocation: { + title: 'Create Pull Request', + createPr: { + detailsTitle: 'Pull Request Details', + titleLabel: 'Pull Request Title', + titlePlaceholder: 'Add Backstage catalog entity descriptor files', + bodyLabel: 'Pull Request Body', + bodyPlaceholder: 'A describing text with Markdown support', + configurationTitle: 'Entity Configuration', + componentNameLabel: 'Name of the created component', + componentNamePlaceholder: 'my-component', + ownerLoadingText: 'Loading groups…', + ownerHelperText: + 'Select an owner from the list or enter a reference to a Group or a User', + ownerErrorHelperText: 'required value', + ownerLabel: 'Entity Owner', + ownerPlaceholder: 'my-group', + codeownersHelperText: + 'WARNING: This may fail if no CODEOWNERS file is found at the target location.', + }, + }, + analyze: { + title: 'Select URL', + }, + prepare: { + title: 'Import Actions', + description: 'Optional', + }, + review: { + title: 'Review', + }, + finish: { + title: 'Finish', + }, + }, + stepFinishImportLocation: { + backButtonText: 'Register another', + repository: { + title: 'The following Pull Request has been opened: ', + description: + 'Your entities will be imported as soon as the Pull Request is merged.', + }, + locations: { + new: 'The following entities have been added to the catalog:', + existing: 'A refresh was triggered for the following locations:', + viewButtonText: 'View Component', + backButtonText: 'Register another', + }, + }, + stepInitAnalyzeUrl: { + error: { + repository: "Couldn't generate entities for your repository", + locations: 'There are no entities at this location', + default: + 'Received unknown analysis result of type {{type}}. Please contact the support team.', + url: 'Must start with http:// or https://.', + }, + urlHelperText: + 'Enter the full path to your entity file to start tracking your component', + nextButtonText: 'Analyze', + }, + stepPrepareCreatePullRequest: { + previewPr: { + title: 'Preview Pull Request', + subheader: 'Create a new Pull Request', + }, + previewCatalogInfo: { + title: 'Preview Entities', + }, + nextButtonText: 'Create PR', + }, + stepPrepareSelectLocations: { + locations: { + description: + 'Select one or more locations that are present in your git repository:', + selectAll: 'Select All', + }, + existingLocations: { + description: 'These locations already exist in the catalog:', + }, + nextButtonText: 'Review', + }, + stepReviewLocation: { + prepareResult: { + title: 'The following Pull Request has been opened: ', + description: + 'You can already import the location and {{appTitle}} will fetch the entities as soon as the Pull Request is merged.', + }, + catalog: { + exists: 'The following locations already exist in the catalog:', + new: 'The following entities will be added to the catalog:', + }, + refresh: 'Refresh', + import: 'Import', }, }, }); diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 4a261ad2aa..7ca221a40d 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-react +## 1.18.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/frontend-test-utils@0.3.2-next.2 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 1.18.0-next.1 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index aeeb398cad..bb9d1bde29 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "1.18.0-next.1", + "version": "1.18.0-next.2", "description": "A frontend library that helps other Backstage plugins interact with the catalog", "backstage": { "role": "web-library", diff --git a/plugins/catalog-unprocessed-entities/CHANGELOG.md b/plugins/catalog-unprocessed-entities/CHANGELOG.md index d85e0919cd..e7b747928f 100644 --- a/plugins/catalog-unprocessed-entities/CHANGELOG.md +++ b/plugins/catalog-unprocessed-entities/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-unprocessed-entities +## 0.2.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + ## 0.2.17-next.1 ### Patch Changes diff --git a/plugins/catalog-unprocessed-entities/package.json b/plugins/catalog-unprocessed-entities/package.json index e985ba2d9c..c6663150c0 100644 --- a/plugins/catalog-unprocessed-entities/package.json +++ b/plugins/catalog-unprocessed-entities/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-unprocessed-entities", - "version": "0.2.17-next.1", + "version": "0.2.17-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "catalog-unprocessed-entities", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 9cfa2876ba..b410114940 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog +## 1.29.1-next.2 + +### Patch Changes + +- bf85d37: Fix for missing `routeRef` when using `core-plugin-api` in a dialog context +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.29.1-next.1 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 87f0ce2052..8eda14596b 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "1.29.1-next.1", + "version": "1.29.1-next.2", "description": "The Backstage plugin for browsing the Backstage catalog", "backstage": { "role": "frontend-plugin", diff --git a/plugins/catalog/src/alpha/contextMenuItems.tsx b/plugins/catalog/src/alpha/contextMenuItems.tsx index d503bc33a1..4173a4d074 100644 --- a/plugins/catalog/src/alpha/contextMenuItems.tsx +++ b/plugins/catalog/src/alpha/contextMenuItems.tsx @@ -37,6 +37,7 @@ import { import { rootRouteRef, unregisterRedirectRouteRef } from '../routes'; import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common/alpha'; import { useEffect } from 'react'; +import { compatWrapper } from '@backstage/core-compat-api'; export const copyEntityUrlContextMenuItem = EntityContextMenuItemBlueprint.make( { @@ -99,6 +100,7 @@ export const unregisterEntityContextMenuItem = const dialogApi = useApi(dialogApiRef); const navigate = useNavigate(); const catalogRoute = useRouteRef(rootRouteRef); + const { t } = useTranslationRef(catalogTranslationRef); const unregisterRedirectRoute = useRouteRef(unregisterRedirectRouteRef); const unregisterPermission = useEntityPermission( @@ -109,21 +111,23 @@ export const unregisterEntityContextMenuItem = title: t('entityContextMenu.unregisterMenuTitle'), disabled: !unregisterPermission.allowed, onClick: async () => { - dialogApi.showModal(({ dialog }: { dialog: DialogApiDialog }) => ( - dialog.close()} - onConfirm={() => { - dialog.close(); - navigate( - unregisterRedirectRoute - ? unregisterRedirectRoute() - : catalogRoute(), - ); - }} - /> - )); + dialogApi.showModal(({ dialog }: { dialog: DialogApiDialog }) => + compatWrapper( + dialog.close()} + onConfirm={() => { + dialog.close(); + navigate( + unregisterRedirectRoute + ? unregisterRedirectRoute() + : catalogRoute(), + ); + }} + />, + ), + ); }, }; }, diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 9b3a950ffd..464688c590 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -47,7 +47,7 @@ import { OffsetPaginatedCatalogTable } from './OffsetPaginatedCatalogTable'; import { CursorPaginatedCatalogTable } from './CursorPaginatedCatalogTable'; import { defaultCatalogTableColumnsFunc } from './defaultCatalogTableColumnsFunc'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; -import { catalogTranslationRef } from '../../alpha/translation'; +import { catalogTranslationRef } from '../../alpha'; import { FavoriteToggleIcon } from '@backstage/core-components'; /** @@ -194,7 +194,7 @@ export const CatalogTable = (props: CatalogTableProps) => { .join(' '); const actions = props.actions || defaultActions; - const options = { + const options: TableProps['options'] = { actionsColumnIndex: -1, loadingType: 'linear' as const, showEmptyDataSourceMessage: !loading, diff --git a/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx index e49ecf3cd9..1fcc2683be 100644 --- a/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CursorPaginatedCatalogTable.tsx @@ -35,7 +35,6 @@ export function CursorPaginatedCatalogTable(props: PaginatedCatalogTableProps) { columns={columns} data={data} options={{ - paginationPosition: 'both', ...options, // These settings are configured to force server side pagination pageSizeOptions: [], diff --git a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx index 96c81088ca..896cf0f8b6 100644 --- a/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/OffsetPaginatedCatalogTable.tsx @@ -47,7 +47,6 @@ export function OffsetPaginatedCatalogTable( columns={columns} data={data} options={{ - paginationPosition: 'both', pageSizeOptions: [5, 10, 20, 50, 100], pageSize: limit, emptyRowsWhenPaging: false, @@ -57,14 +56,9 @@ export function OffsetPaginatedCatalogTable( Toolbar: CatalogTableToolbar, }} page={page} - onPageChange={newPage => { - setPage(newPage); - }} - onRowsPerPageChange={pageSize => { - setLimit(pageSize); - }} + onPageChange={setPage} + onRowsPerPageChange={setLimit} totalCount={totalItems} - localization={{ pagination: { labelDisplayedRows: '' } }} {...restProps} /> ); diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 34168e5c87..ff5cd9d89f 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-config-schema +## 0.1.68-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.1.68-next.0 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index f2ede3c05e..f21d03be67 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.68-next.0", + "version": "0.1.68-next.1", "description": "A Backstage plugin that lets you browse the configuration schema of your app", "backstage": { "role": "frontend-plugin", diff --git a/plugins/devtools-backend/CHANGELOG.md b/plugins/devtools-backend/CHANGELOG.md index b1875099e6..b51d75a457 100644 --- a/plugins/devtools-backend/CHANGELOG.md +++ b/plugins/devtools-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-devtools-backend +## 0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/cli-common@0.1.15 + - @backstage/config-loader@1.10.1-next.0 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-devtools-common@0.1.16-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.5.5-next.1 ### Patch Changes diff --git a/plugins/devtools-backend/package.json b/plugins/devtools-backend/package.json index abf589f322..f3831118b7 100644 --- a/plugins/devtools-backend/package.json +++ b/plugins/devtools-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools-backend", - "version": "0.5.5-next.1", + "version": "0.5.5-next.2", "backstage": { "role": "backend-plugin", "pluginId": "devtools", diff --git a/plugins/devtools/CHANGELOG.md b/plugins/devtools/CHANGELOG.md index 7d3bfebced..267df2e952 100644 --- a/plugins/devtools/CHANGELOG.md +++ b/plugins/devtools/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-devtools +## 0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/errors@1.2.7 + - @backstage/plugin-devtools-common@0.1.16-next.0 + ## 0.1.27-next.1 ### Patch Changes diff --git a/plugins/devtools/package.json b/plugins/devtools/package.json index 2c6cac0cf4..2a1c4890a1 100644 --- a/plugins/devtools/package.json +++ b/plugins/devtools/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-devtools", - "version": "0.1.27-next.1", + "version": "0.1.27-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "devtools", diff --git a/plugins/events-backend-module-github/CHANGELOG.md b/plugins/events-backend-module-github/CHANGELOG.md index 72262b82c9..db61a7eba0 100644 --- a/plugins/events-backend-module-github/CHANGELOG.md +++ b/plugins/events-backend-module-github/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-events-backend-module-github +## 0.4.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-events-node@0.4.11-next.1 + ## 0.4.0-next.1 ### Minor Changes diff --git a/plugins/events-backend-module-github/package.json b/plugins/events-backend-module-github/package.json index b83a52e308..8b38788c91 100644 --- a/plugins/events-backend-module-github/package.json +++ b/plugins/events-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-events-backend-module-github", - "version": "0.4.0-next.1", + "version": "0.4.0-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "events", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index de493d3af3..9af56aa38d 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/plugin-todo-list +## 1.0.39-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + ## 1.0.39-next.0 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 2e33a7825c..620f8ad05b 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.39-next.0", + "version": "1.0.39-next.1", "backstage": { "role": "frontend-plugin", "pluginId": "todo-list", diff --git a/plugins/home-react/CHANGELOG.md b/plugins/home-react/CHANGELOG.md index 009ef27cc2..06a3cdff85 100644 --- a/plugins/home-react/CHANGELOG.md +++ b/plugins/home-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-home-react +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + ## 0.1.26-next.0 ### Patch Changes diff --git a/plugins/home-react/package.json b/plugins/home-react/package.json index 7d105e22b4..43f4e79aed 100644 --- a/plugins/home-react/package.json +++ b/plugins/home-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home-react", - "version": "0.1.26-next.0", + "version": "0.1.26-next.1", "description": "A Backstage plugin that contains react components helps you build a home page", "backstage": { "role": "web-library", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index 4496735e3f..73eb07ffd7 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-home +## 0.8.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.16.2-next.0 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-home-react@0.1.26-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + ## 0.8.8-next.1 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index c89a9db849..c832e8f888 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-home", - "version": "0.8.8-next.1", + "version": "0.8.8-next.2", "description": "A Backstage plugin that helps you build a home page", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 3c580c2fb8..4147c855f8 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-kubernetes-backend +## 0.19.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-kubernetes-node@0.3.0-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.19.6-next.1 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 730a63db91..95854d8e88 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.19.6-next.1", + "version": "0.19.6-next.2", "description": "A Backstage backend plugin that integrates towards Kubernetes", "backstage": { "role": "backend-plugin", diff --git a/plugins/kubernetes-cluster/CHANGELOG.md b/plugins/kubernetes-cluster/CHANGELOG.md index b7987e7327..aeaf6868ed 100644 --- a/plugins/kubernetes-cluster/CHANGELOG.md +++ b/plugins/kubernetes-cluster/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-kubernetes-cluster +## 0.0.25-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.0.25-next.1 ### Patch Changes diff --git a/plugins/kubernetes-cluster/package.json b/plugins/kubernetes-cluster/package.json index 8e06903b3a..c7d0013ad2 100644 --- a/plugins/kubernetes-cluster/package.json +++ b/plugins/kubernetes-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-cluster", - "version": "0.0.25-next.1", + "version": "0.0.25-next.2", "description": "A Backstage plugin that shows details of Kubernetes clusters", "backstage": { "role": "frontend-plugin", diff --git a/plugins/kubernetes-react/CHANGELOG.md b/plugins/kubernetes-react/CHANGELOG.md index de556688b3..51e96bbea9 100644 --- a/plugins/kubernetes-react/CHANGELOG.md +++ b/plugins/kubernetes-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-react +## 0.5.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.5.7-next.0 ### Patch Changes diff --git a/plugins/kubernetes-react/package.json b/plugins/kubernetes-react/package.json index 7beaa4f166..0dd828d88d 100644 --- a/plugins/kubernetes-react/package.json +++ b/plugins/kubernetes-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-react", - "version": "0.5.7-next.0", + "version": "0.5.7-next.1", "description": "Web library for the kubernetes-react plugin", "backstage": { "role": "web-library", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index f63120448e..b2c602a222 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-kubernetes +## 0.12.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-kubernetes-react@0.5.7-next.1 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-kubernetes-common@0.9.5-next.0 + ## 0.12.7-next.1 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index c032c3a006..60c232bc51 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.12.7-next.1", + "version": "0.12.7-next.2", "description": "A Backstage plugin that integrates towards Kubernetes", "backstage": { "role": "frontend-plugin", diff --git a/plugins/notifications-backend-module-email/CHANGELOG.md b/plugins/notifications-backend-module-email/CHANGELOG.md index e00c05cd1f..f0baa3195d 100644 --- a/plugins/notifications-backend-module-email/CHANGELOG.md +++ b/plugins/notifications-backend-module-email/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-notifications-backend-module-email +## 0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-notifications-common@0.0.8 + ## 0.3.9-next.1 ### Patch Changes diff --git a/plugins/notifications-backend-module-email/package.json b/plugins/notifications-backend-module-email/package.json index 0b73322476..566a446aa3 100644 --- a/plugins/notifications-backend-module-email/package.json +++ b/plugins/notifications-backend-module-email/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-email", - "version": "0.3.9-next.1", + "version": "0.3.9-next.2", "description": "The email backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/notifications-backend-module-slack/CHANGELOG.md b/plugins/notifications-backend-module-slack/CHANGELOG.md index 7842ae0428..2cdd8848a4 100644 --- a/plugins/notifications-backend-module-slack/CHANGELOG.md +++ b/plugins/notifications-backend-module-slack/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-notifications-backend-module-slack +## 0.1.1-next.2 + +### Patch Changes + +- 4f10768: Fix slack notification processor to handle a notification with an empty description +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + ## 0.1.1-next.1 ### Patch Changes diff --git a/plugins/notifications-backend-module-slack/package.json b/plugins/notifications-backend-module-slack/package.json index b6cb16d81a..33770bdeac 100644 --- a/plugins/notifications-backend-module-slack/package.json +++ b/plugins/notifications-backend-module-slack/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications-backend-module-slack", - "version": "0.1.1-next.1", + "version": "0.1.1-next.2", "description": "The slack backend module for the notifications plugin.", "backstage": { "role": "backend-plugin-module", @@ -35,10 +35,10 @@ }, "dependencies": { "@backstage/backend-plugin-api": "workspace:^", - "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/plugin-notifications-common": "workspace:^", "@backstage/plugin-notifications-node": "workspace:^", "@backstage/types": "workspace:^", diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts index f5893ff456..45a74cee70 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.test.ts @@ -109,7 +109,6 @@ const DEFAULT_ENTITIES_RESPONSE = { describe('SlackNotificationProcessor', () => { const logger = mockServices.logger.mock(); const auth = mockServices.auth(); - const discovery = mockServices.discovery(); const config = mockServices.rootConfig({ data: { app: { @@ -136,7 +135,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -158,6 +156,10 @@ describe('SlackNotificationProcessor', () => { blocks: [ { type: 'section', + text: { + text: 'No description provided', + type: 'mrkdwn', + }, accessory: { type: 'button', text: { @@ -195,7 +197,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -229,6 +230,10 @@ describe('SlackNotificationProcessor', () => { blocks: [ { type: 'section', + text: { + text: 'No description provided', + type: 'mrkdwn', + }, accessory: { type: 'button', text: { @@ -267,7 +272,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -302,7 +306,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -358,7 +361,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(broadcastConfig, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -397,7 +399,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -421,7 +422,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: [DEFAULT_ENTITIES_RESPONSE.items[2]], @@ -461,7 +461,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -507,7 +506,6 @@ describe('SlackNotificationProcessor', () => { const processor = SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, catalog: catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items, @@ -543,4 +541,125 @@ describe('SlackNotificationProcessor', () => { expect(slack.chat.postMessage).not.toHaveBeenCalled(); }); }); + + describe('when replacing user entity refs with Slack IDs', () => { + const createBaseMessage = (text: string) => ({ + channel: 'U12345678', + text: 'notification', + attachments: [ + { + color: '#00A699', + blocks: [ + { + type: 'section', + text: { + type: 'mrkdwn', + text, + }, + accessory: { + type: 'button', + text: { + type: 'plain_text', + text: 'View More', + }, + action_id: 'button-action', + }, + }, + { + type: 'context', + elements: [ + { + type: 'plain_text', + text: 'Severity: normal', + emoji: true, + }, + { + type: 'plain_text', + text: 'Topic: N/A', + emoji: true, + }, + ], + }, + ], + fallback: 'notification', + }, + ], + }); + + it('should replace user entity refs with Slack compatible mentions', async () => { + const slack = new WebClient(); + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + description: + 'Hello <@user:default/mock> and <@user:default/mock-without-slack-annotation>', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { + title: 'notification', + description: + 'Hello <@user:default/mock> and <@user:default/mock-without-slack-annotation>', + }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledWith( + createBaseMessage( + 'Hello <@U12345678> and <@user:default/mock-without-slack-annotation>', + ), + ); + }); + + it('should handle text without user entity refs', async () => { + const slack = new WebClient(); + const processor = SlackNotificationProcessor.fromConfig(config, { + auth, + logger, + catalog: catalogServiceMock({ + entities: DEFAULT_ENTITIES_RESPONSE.items, + }), + slack, + })[0]; + + await processor.postProcess( + { + origin: 'plugin', + id: '1234', + user: 'user:default/mock', + created: new Date(), + payload: { + title: 'notification', + description: 'Hello world', + }, + }, + { + recipients: { type: 'entity', entityRef: 'user:default/mock' }, + payload: { + title: 'notification', + description: 'Hello world', + }, + }, + ); + + expect(slack.chat.postMessage).toHaveBeenCalledWith( + createBaseMessage('Hello world'), + ); + }); + }); }); diff --git a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts index ca51eeb068..c587580657 100644 --- a/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts +++ b/plugins/notifications-backend-module-slack/src/lib/SlackNotificationProcessor.ts @@ -14,12 +14,7 @@ * limitations under the License. */ -import { - AuthService, - DiscoveryService, - LoggerService, -} from '@backstage/backend-plugin-api'; -import { CatalogApi } from '@backstage/catalog-client'; +import { AuthService, LoggerService } from '@backstage/backend-plugin-api'; import { Entity, isUserEntity, @@ -39,25 +34,26 @@ import { ChatPostMessageArguments, WebClient } from '@slack/web-api'; import DataLoader from 'dataloader'; import pThrottle from 'p-throttle'; import { ANNOTATION_SLACK_BOT_NOTIFY } from './constants'; -import { toChatPostMessageArgs } from './util'; +import { ExpiryMap, toChatPostMessageArgs } from './util'; +import { CatalogService } from '@backstage/plugin-catalog-node'; export class SlackNotificationProcessor implements NotificationProcessor { private readonly logger: LoggerService; - private readonly catalog: CatalogApi; + private readonly catalog: CatalogService; private readonly auth: AuthService; private readonly slack: WebClient; private readonly sendNotifications; private readonly messagesSent: Counter; private readonly messagesFailed: Counter; private readonly broadcastChannels?: string[]; + private readonly entityLoader: DataLoader; static fromConfig( config: Config, options: { auth: AuthService; - discovery: DiscoveryService; logger: LoggerService; - catalog: CatalogApi; + catalog: CatalogService; slack?: WebClient; broadcastChannels?: string[]; }, @@ -79,9 +75,8 @@ export class SlackNotificationProcessor implements NotificationProcessor { private constructor(options: { slack: WebClient; auth: AuthService; - discovery: DiscoveryService; logger: LoggerService; - catalog: CatalogApi; + catalog: CatalogService; broadcastChannels?: string[]; }) { const { auth, catalog, logger, slack, broadcastChannels } = options; @@ -91,6 +86,31 @@ export class SlackNotificationProcessor implements NotificationProcessor { this.slack = slack; this.broadcastChannels = broadcastChannels; + this.entityLoader = new DataLoader( + async entityRefs => { + return await this.catalog + .getEntitiesByRefs( + { + entityRefs: entityRefs.slice(), + fields: [ + `kind`, + `spec.profile.email`, + `metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`, + ], + }, + { credentials: await this.auth.getOwnServiceCredentials() }, + ) + .then(r => r.items); + }, + { + name: 'SlackNotificationProcessor.entityLoader', + cacheMap: new ExpiryMap(durationToMilliseconds({ minutes: 10 })), + maxBatchSize: 100, + batchScheduleFn: cb => + setTimeout(cb, durationToMilliseconds({ milliseconds: 10 })), + }, + ); + const meter = metrics.getMeter('default'); this.messagesSent = meter.createCounter( 'notifications.processors.slack.sent.count', @@ -193,7 +213,6 @@ export class SlackNotificationProcessor implements NotificationProcessor { }), ); - console.log('dispatching message'); await this.sendNotifications(outbound); return options; @@ -236,8 +255,11 @@ export class SlackNotificationProcessor implements NotificationProcessor { } // Prepare outbound messages + const formattedPayload = await this.formatPayloadDescriptionForSlack( + options.payload, + ); const outbound = destinations.map(channel => - toChatPostMessageArgs({ channel, payload: options.payload }), + toChatPostMessageArgs({ channel, payload: formattedPayload }), ); // Log debug info @@ -249,41 +271,58 @@ export class SlackNotificationProcessor implements NotificationProcessor { await this.sendNotifications(outbound); } - async getEntities( - entityRefs: readonly string[], - ): Promise<(Entity | undefined)[]> { - const { token } = await this.auth.getPluginRequestToken({ - onBehalfOf: await this.auth.getOwnServiceCredentials(), - targetPluginId: 'catalog', - }); + private async formatPayloadDescriptionForSlack( + payload: Notification['payload'], + ) { + return { + ...payload, + description: await this.replaceUserRefsWithSlackIds(payload.description), + }; + } - const response = await this.catalog.getEntitiesByRefs( - { - entityRefs: entityRefs.slice(), - fields: [ - `kind`, - `spec.profile.email`, - `metadata.annotations.${ANNOTATION_SLACK_BOT_NOTIFY}`, - ], - }, - { - token, - }, + async replaceUserRefsWithSlackIds( + text?: string, + ): Promise { + if (!text) return undefined; + + // Match user entity refs like "<@user:default/billy>" + const userRefRegex = /<@(user:[^>]+)>/gi; + const matches = [...text.matchAll(userRefRegex)]; + + if (matches.length === 0) return text; + + const uniqueUserRefs = new Set( + matches.map(match => match[1].toLowerCase()), ); - return response.items; + const slackIdMap = new Map(); + + await Promise.all( + [...uniqueUserRefs].map(async userRef => { + try { + const slackId = await this.getSlackNotificationTarget(userRef); + if (slackId) { + slackIdMap.set(userRef, `<@${slackId}>`); + } + } catch (error) { + this.logger.warn( + `Failed to resolve Slack ID for user ref "${userRef}": ${error}`, + ); + } + }), + ); + + return text.replace(userRefRegex, (match, userRef) => { + const slackId = slackIdMap.get(userRef.toLowerCase()); + return slackId ?? match; + }); } async getSlackNotificationTarget( entityRef: string, ): Promise { - const entityLoader = new DataLoader( - entityRefs => this.getEntities(entityRefs), - ); - const entity = await entityLoader.load(entityRef); - + const entity = await this.entityLoader.load(entityRef); if (!entity) { - console.log(`Entity not found: ${entityRef}`); throw new NotFoundError(`Entity not found: ${entityRef}`); } diff --git a/plugins/notifications-backend-module-slack/src/lib/util.ts b/plugins/notifications-backend-module-slack/src/lib/util.ts index 5e2adbeec5..24f9f6a964 100644 --- a/plugins/notifications-backend-module-slack/src/lib/util.ts +++ b/plugins/notifications-backend-module-slack/src/lib/util.ts @@ -46,12 +46,10 @@ export function toSlackBlockKit(payload: NotificationPayload): KnownBlock[] { return [ { type: 'section', - ...(description && { - text: { - type: 'mrkdwn', - text: description ?? 'No description provided', - }, - }), + text: { + type: 'mrkdwn', + text: description ?? 'No description provided', + }, accessory: { type: 'button', text: { @@ -92,3 +90,42 @@ function getColor(severity: NotificationSeverity | undefined) { return '#00A699'; // Neutral color } } + +// Simple expiry map for the data loader, which only expects a map that implements set, get, and delete and clear +export class ExpiryMap extends Map { + #ttlMs: number; + #timestamps: Map = new Map(); + + constructor(ttlMs: number) { + super(); + this.#ttlMs = ttlMs; + } + + set(key: K, value: V) { + const result = super.set(key, value); + this.#timestamps.set(key, Date.now()); + return result; + } + + get(key: K) { + if (!this.has(key)) { + return undefined; + } + const timestamp = this.#timestamps.get(key)!; + if (Date.now() - timestamp > this.#ttlMs) { + this.delete(key); + return undefined; + } + return super.get(key); + } + + delete(key: K) { + this.#timestamps.delete(key); + return super.delete(key); + } + + clear() { + this.#timestamps.clear(); + return super.clear(); + } +} diff --git a/plugins/notifications-backend-module-slack/src/module.ts b/plugins/notifications-backend-module-slack/src/module.ts index cb096dabba..ff783ef104 100644 --- a/plugins/notifications-backend-module-slack/src/module.ts +++ b/plugins/notifications-backend-module-slack/src/module.ts @@ -17,9 +17,9 @@ import { coreServices, createBackendModule, } from '@backstage/backend-plugin-api'; -import { CatalogClient } from '@backstage/catalog-client'; import { notificationsProcessingExtensionPoint } from '@backstage/plugin-notifications-node'; import { SlackNotificationProcessor } from './lib/SlackNotificationProcessor'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node'; /** * The Slack notification processor for use with the notifications plugin. @@ -35,21 +35,16 @@ export const notificationsModuleSlack = createBackendModule({ deps: { auth: coreServices.auth, config: coreServices.rootConfig, - discovery: coreServices.discovery, logger: coreServices.logger, + catalog: catalogServiceRef, notifications: notificationsProcessingExtensionPoint, }, - async init({ auth, config, discovery, logger, notifications }) { - const catalogClient = new CatalogClient({ - discoveryApi: discovery, - }); - + async init({ auth, config, logger, catalog, notifications }) { notifications.addProcessor( SlackNotificationProcessor.fromConfig(config, { auth, - discovery, logger, - catalog: catalogClient, + catalog, }), ); }, diff --git a/plugins/notifications/CHANGELOG.md b/plugins/notifications/CHANGELOG.md index 0e8034ba48..f171b8a148 100644 --- a/plugins/notifications/CHANGELOG.md +++ b/plugins/notifications/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-notifications +## 0.5.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-signals-react@0.0.13-next.0 + - @backstage/errors@1.2.7 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/plugin-notifications-common@0.0.8 + ## 0.5.5-next.1 ### Patch Changes diff --git a/plugins/notifications/package.json b/plugins/notifications/package.json index d766758857..0a9d383056 100644 --- a/plugins/notifications/package.json +++ b/plugins/notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-notifications", - "version": "0.5.5-next.1", + "version": "0.5.5-next.2", "backstage": { "role": "frontend-plugin", "pluginId": "notifications", diff --git a/plugins/org-react/CHANGELOG.md b/plugins/org-react/CHANGELOG.md index 2af1cb5abd..766eb1b7ca 100644 --- a/plugins/org-react/CHANGELOG.md +++ b/plugins/org-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-org-react +## 0.1.38-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + ## 0.1.38-next.1 ### Patch Changes diff --git a/plugins/org-react/package.json b/plugins/org-react/package.json index f129987c01..8659c22098 100644 --- a/plugins/org-react/package.json +++ b/plugins/org-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org-react", - "version": "0.1.38-next.1", + "version": "0.1.38-next.2", "backstage": { "role": "web-library", "pluginId": "org", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 0fccadefa0..5dd3fe3463 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-org +## 0.6.39-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + ## 0.6.39-next.1 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 893e5060fc..5337c5ec0f 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.6.39-next.1", + "version": "0.6.39-next.2", "description": "A Backstage plugin that helps you create entity pages for your organization", "backstage": { "role": "frontend-plugin", diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 2d076700ec..d68a3a1566 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-react +## 0.4.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-common@0.9.0-next.0 + ## 0.4.34-next.0 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index 0deb739819..f52f0eb58b 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.34-next.0", + "version": "0.4.34-next.1", "backstage": { "role": "web-library", "pluginId": "permission", diff --git a/plugins/scaffolder-backend-module-azure/CHANGELOG.md b/plugins/scaffolder-backend-module-azure/CHANGELOG.md index 05f71427c7..4575bae2c7 100644 --- a/plugins/scaffolder-backend-module-azure/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-azure/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-azure +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-azure/package.json b/plugins/scaffolder-backend-module-azure/package.json index d1c26d78eb..9507c527d7 100644 --- a/plugins/scaffolder-backend-module-azure/package.json +++ b/plugins/scaffolder-backend-module-azure/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-azure", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The azure module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md index 2e6e218f58..cd1207a738 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-cloud +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json index cbf2c5daae..79ea0f1131 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-cloud", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The Bitbucket Cloud module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md index b2bf58fc04..ad54b35865 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket-server +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket-server/package.json b/plugins/scaffolder-backend-module-bitbucket-server/package.json index 8315efaedd..c092f10207 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/package.json +++ b/plugins/scaffolder-backend-module-bitbucket-server/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket-server", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The Bitbucket Server module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md index 99e0d6a3ed..c8bfd2fc7b 100644 --- a/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-bitbucket +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-bitbucket/package.json b/plugins/scaffolder-backend-module-bitbucket/package.json index 3f69f1b5bf..699858702c 100644 --- a/plugins/scaffolder-backend-module-bitbucket/package.json +++ b/plugins/scaffolder-backend-module-bitbucket/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-bitbucket", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "description": "The bitbucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md index 6f3fb91881..06756b19fd 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-confluence-to-markdown +## 0.3.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.3.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json index fb91896783..de6045689f 100644 --- a/plugins/scaffolder-backend-module-confluence-to-markdown/package.json +++ b/plugins/scaffolder-backend-module-confluence-to-markdown/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-confluence-to-markdown", - "version": "0.3.9-next.1", + "version": "0.3.9-next.2", "description": "The confluence-to-markdown module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index ccb9289646..c1a9ce091f 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.3.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 0341a564b6..404bad2b94 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.3.10-next.1", + "version": "0.3.10-next.2", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md index c1b1041c0e..9f7f9b1631 100644 --- a/plugins/scaffolder-backend-module-gcp/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gcp/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gcp +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gcp/package.json b/plugins/scaffolder-backend-module-gcp/package.json index 688d2bb0d6..7c2fc0a624 100644 --- a/plugins/scaffolder-backend-module-gcp/package.json +++ b/plugins/scaffolder-backend-module-gcp/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gcp", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The GCP Bucket module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md index f77361fa0e..7c0367d1b0 100644 --- a/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gerrit +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gerrit/package.json b/plugins/scaffolder-backend-module-gerrit/package.json index 2959df75a0..aa6e7478ef 100644 --- a/plugins/scaffolder-backend-module-gerrit/package.json +++ b/plugins/scaffolder-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gerrit", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The gerrit module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md index 0ad531c6c2..c7e65d176e 100644 --- a/plugins/scaffolder-backend-module-gitea/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitea/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gitea +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitea/package.json b/plugins/scaffolder-backend-module-gitea/package.json index cd69424100..1be15b9164 100644 --- a/plugins/scaffolder-backend-module-gitea/package.json +++ b/plugins/scaffolder-backend-module-gitea/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitea", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "description": "The gitea module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-github/CHANGELOG.md b/plugins/scaffolder-backend-module-github/CHANGELOG.md index 8ba0029bed..6170587864 100644 --- a/plugins/scaffolder-backend-module-github/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-github/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend-module-github +## 0.7.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.7.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-github/package.json b/plugins/scaffolder-backend-module-github/package.json index af935262ae..0217d0b48d 100644 --- a/plugins/scaffolder-backend-module-github/package.json +++ b/plugins/scaffolder-backend-module-github/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-github", - "version": "0.7.1-next.1", + "version": "0.7.1-next.2", "description": "The github module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md index 9115edc834..aa4a43c22c 100644 --- a/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-gitlab +## 0.9.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.9.1-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-gitlab/package.json b/plugins/scaffolder-backend-module-gitlab/package.json index 6f4ad7b882..d2f699b219 100644 --- a/plugins/scaffolder-backend-module-gitlab/package.json +++ b/plugins/scaffolder-backend-module-gitlab/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-gitlab", - "version": "0.9.1-next.1", + "version": "0.9.1-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md index 00dfebcffd..4db8585828 100644 --- a/plugins/scaffolder-backend-module-notifications/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-notifications/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend-module-notifications +## 0.1.10-next.2 + +### Patch Changes + +- b60253d: Change notification send scaffolder action to use native zod schemas +- Updated dependencies + - @backstage/plugin-notifications-node@0.2.15-next.1 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-notifications-common@0.0.8 + ## 0.1.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-notifications/package.json b/plugins/scaffolder-backend-module-notifications/package.json index 4512b2c1a7..ea6c416a9e 100644 --- a/plugins/scaffolder-backend-module-notifications/package.json +++ b/plugins/scaffolder-backend-module-notifications/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-notifications", - "version": "0.1.10-next.1", + "version": "0.1.10-next.2", "description": "The notifications backend module for the scaffolder plugin.", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-notifications/report.api.md b/plugins/scaffolder-backend-module-notifications/report.api.md index cb796e5033..1d437cebd7 100644 --- a/plugins/scaffolder-backend-module-notifications/report.api.md +++ b/plugins/scaffolder-backend-module-notifications/report.api.md @@ -4,9 +4,7 @@ ```ts import { BackendFeature } from '@backstage/backend-plugin-api'; -import { JsonObject } from '@backstage/types'; import { NotificationService } from '@backstage/plugin-notifications-node'; -import { NotificationSeverity } from '@backstage/plugin-notifications-common'; import { TemplateAction } from '@backstage/plugin-scaffolder-node'; // @public (undocumented) @@ -14,17 +12,19 @@ export function createSendNotificationAction(options: { notifications: NotificationService; }): TemplateAction< { - recipients: string; - entityRefs?: string[]; + recipients: 'entity' | 'broadcast'; title: string; - info?: string; - link?: string; - severity?: NotificationSeverity; - scope?: string; - optional?: boolean; + entityRefs?: string[] | undefined; + info?: string | undefined; + link?: string | undefined; + severity?: 'normal' | 'high' | 'low' | 'critical' | undefined; + scope?: string | undefined; + optional?: boolean | undefined; }, - JsonObject, - 'v1' + { + [x: string]: any; + }, + 'v2' >; // @public diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts index 84473335e0..cd6fb54c51 100644 --- a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.examples.test.ts @@ -25,7 +25,7 @@ describe('notification:send', () => { send: jest.fn(), }; - let action: TemplateAction; + let action: TemplateAction; beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts index efd1377cae..ee87991c73 100644 --- a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.test.ts @@ -23,7 +23,7 @@ describe('notification:send', () => { send: jest.fn(), }; - let action: TemplateAction; + let action: TemplateAction; beforeEach(() => { jest.resetAllMocks(); diff --git a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts index d1954dfd8c..a8e86f9b48 100644 --- a/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts +++ b/plugins/scaffolder-backend-module-notifications/src/actions/sendNotification.ts @@ -17,10 +17,7 @@ import { NotificationRecipients, NotificationService, } from '@backstage/plugin-notifications-node'; -import { - NotificationPayload, - NotificationSeverity, -} from '@backstage/plugin-notifications-common'; +import { NotificationPayload } from '@backstage/plugin-notifications-common'; import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; import { examples } from './sendNotification.examples'; @@ -31,73 +28,41 @@ export function createSendNotificationAction(options: { notifications: NotificationService; }) { const { notifications } = options; - return createTemplateAction<{ - recipients: string; - entityRefs?: string[]; - title: string; - info?: string; - link?: string; - severity?: NotificationSeverity; - scope?: string; - optional?: boolean; - }>({ + return createTemplateAction({ id: 'notification:send', description: 'Sends a notification using NotificationService', examples, schema: { input: { - type: 'object', - required: ['recipients', 'title'], - properties: { - recipients: { - title: 'Recipient', - enum: ['broadcast', 'entity'], - description: + recipients: z => + z + .enum(['broadcast', 'entity']) + .describe( 'The recipient of the notification, either broadcast or entity. If using entity, also entityRef must be provided', - type: 'string', - }, - entityRefs: { - title: 'Entity references', - description: + ), + entityRefs: z => + z + .array(z.string()) + .optional() + .describe( 'The entity references to send the notification to, required if using recipient of entity', - type: 'array', - items: { - type: 'string', - }, - }, - title: { - title: 'Title', - description: 'Notification title', - type: 'string', - }, - info: { - title: 'Description', - description: 'Notification description', - type: 'string', - }, - link: { - title: 'Link', - description: 'Notification link', - type: 'string', - }, - severity: { - title: 'Severity', - type: 'string', - description: `Notification severity`, - enum: ['low', 'normal', 'high', 'critical'], - }, - scope: { - title: 'Scope', - description: 'Notification scope', - type: 'string', - }, - optional: { - title: 'Optional', - description: + ), + title: z => z.string().describe('Notification title'), + info: z => z.string().optional().describe('Notification description'), + link: z => z.string().optional().describe('Notification link'), + severity: z => + z + .enum(['low', 'normal', 'high', 'critical']) + .optional() + .describe('Notification severity'), + scope: z => z.string().optional().describe('Notification scope'), + optional: z => + z + .boolean() + .optional() + .describe( 'Do not fail the action if the notification sending fails', - type: 'boolean', - }, - }, + ), }, }, async handler(ctx) { diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index b8ff9dd9cf..9c9117bfb3 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.5.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + ## 0.5.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index b888aead7e..c4f58e24f0 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.5.9-next.1", + "version": "0.5.9-next.2", "description": "A module for the scaffolder backend that lets you template projects using Rails", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md index 4672939b4c..886803e1f0 100644 --- a/plugins/scaffolder-backend-module-sentry/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-sentry/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-sentry +## 0.2.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + ## 0.2.9-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-sentry/package.json b/plugins/scaffolder-backend-module-sentry/package.json index 9e74b5758e..c59a288861 100644 --- a/plugins/scaffolder-backend-module-sentry/package.json +++ b/plugins/scaffolder-backend-module-sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-sentry", - "version": "0.2.9-next.1", + "version": "0.2.9-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index cba5d9a47c..a319388ed3 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-node-test-utils@0.2.2-next.2 + ## 0.4.10-next.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index a943e3f4b9..577e6735a0 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.4.10-next.1", + "version": "0.4.10-next.2", "backstage": { "role": "backend-plugin-module", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index c0797151b9..bfcea59479 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-scaffolder-backend +## 1.33.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/config@1.3.2 + - @backstage/plugin-scaffolder-backend-module-gitlab@0.9.1-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.3.0-next.2 + - @backstage/plugin-scaffolder-backend-module-azure@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket@0.3.10-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-cloud@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-bitbucket-server@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gerrit@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-gitea@0.2.9-next.2 + - @backstage/plugin-scaffolder-backend-module-github@0.7.1-next.2 + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/plugin-auth-node@0.6.3-next.1 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-backend-module-scaffolder-entity-model@0.2.8-next.1 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-events-node@0.4.11-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 1.33.0-next.1 ### Minor Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 6b2eb79555..8a36adfd7a 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "1.33.0-next.1", + "version": "1.33.0-next.2", "description": "The Backstage backend plugin that helps you create new things", "backstage": { "role": "backend-plugin", diff --git a/plugins/scaffolder-node-test-utils/CHANGELOG.md b/plugins/scaffolder-node-test-utils/CHANGELOG.md index f3501eed74..02f9c91a97 100644 --- a/plugins/scaffolder-node-test-utils/CHANGELOG.md +++ b/plugins/scaffolder-node-test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-scaffolder-node-test-utils +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-node@0.8.2-next.2 + - @backstage/backend-test-utils@1.5.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/types@1.2.1 + ## 0.2.2-next.1 ### Patch Changes diff --git a/plugins/scaffolder-node-test-utils/package.json b/plugins/scaffolder-node-test-utils/package.json index 9354a2ae68..9473450114 100644 --- a/plugins/scaffolder-node-test-utils/package.json +++ b/plugins/scaffolder-node-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node-test-utils", - "version": "0.2.2-next.1", + "version": "0.2.2-next.2", "backstage": { "role": "node-library", "pluginId": "scaffolder", diff --git a/plugins/scaffolder-node/CHANGELOG.md b/plugins/scaffolder-node/CHANGELOG.md index a93c305958..9654b575de 100644 --- a/plugins/scaffolder-node/CHANGELOG.md +++ b/plugins/scaffolder-node/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-scaffolder-node +## 0.8.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 0.8.2-next.1 ### Patch Changes diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index 0c8dc3401e..011b09c91f 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-node", - "version": "0.8.2-next.1", + "version": "0.8.2-next.2", "description": "The plugin-scaffolder-node module for @backstage/plugin-scaffolder-backend", "backstage": { "role": "node-library", diff --git a/plugins/scaffolder-react/CHANGELOG.md b/plugins/scaffolder-react/CHANGELOG.md index dc405aae51..97925ee2a1 100644 --- a/plugins/scaffolder-react/CHANGELOG.md +++ b/plugins/scaffolder-react/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-scaffolder-react +## 1.16.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 1.16.0-next.1 ### Patch Changes diff --git a/plugins/scaffolder-react/package.json b/plugins/scaffolder-react/package.json index 612390b179..514da4fe93 100644 --- a/plugins/scaffolder-react/package.json +++ b/plugins/scaffolder-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-react", - "version": "1.16.0-next.1", + "version": "1.16.0-next.2", "description": "A frontend library that helps other Backstage plugins interact with the Scaffolder", "backstage": { "role": "web-library", diff --git a/plugins/scaffolder-react/report-alpha.api.md b/plugins/scaffolder-react/report-alpha.api.md index 06769b29e2..2014bff9be 100644 --- a/plugins/scaffolder-react/report-alpha.api.md +++ b/plugins/scaffolder-react/report-alpha.api.md @@ -344,8 +344,8 @@ export const scaffolderReactTranslationRef: TranslationRef< readonly 'stepper.backButtonText': 'Back'; readonly 'stepper.createButtonText': 'Create'; readonly 'stepper.reviewButtonText': 'Review'; - readonly 'stepper.stepIndexLabel': 'Step {{index, number}}'; readonly 'stepper.nextButtonText': 'Next'; + readonly 'stepper.stepIndexLabel': 'Step {{index, number}}'; readonly 'templateCategoryPicker.title': 'Categories'; readonly 'templateCard.noDescription': 'No description'; readonly 'templateCard.chooseButtonText': 'Choose'; diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index f887e0cad7..ba8d5db71b 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-scaffolder +## 1.31.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.17.0-next.2 + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/integration-react@1.2.7-next.2 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-permission-react@0.4.34-next.1 + - @backstage/plugin-scaffolder-react@1.16.0-next.2 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-scaffolder-common@1.5.11-next.0 + ## 1.31.0-next.1 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 338044b0af..9c71f584ed 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "1.31.0-next.1", + "version": "1.31.0-next.2", "description": "The Backstage plugin that helps you create new things", "backstage": { "role": "frontend-plugin", diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 80be642c02..08897eb4c3 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.7.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration-aws-node@0.1.16-next.0 + - @backstage/config@1.3.2 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.7.2-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 6b7de58534..fb9399cf2d 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "1.7.2-next.1", + "version": "1.7.2-next.2", "description": "A module for the search backend that implements search using ElasticSearch", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend-module-techdocs/CHANGELOG.md b/plugins/search-backend-module-techdocs/CHANGELOG.md index 23ee89b58c..4af1906d57 100644 --- a/plugins/search-backend-module-techdocs/CHANGELOG.md +++ b/plugins/search-backend-module-techdocs/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search-backend-module-techdocs +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.3.2 + - @backstage/plugin-techdocs-node@1.13.3-next.2 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/catalog-client@1.10.0-next.0 + - @backstage/catalog-model@1.7.3 + - @backstage/plugin-catalog-common@1.1.4-next.0 + - @backstage/plugin-catalog-node@1.17.0-next.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 0.4.2-next.1 ### Patch Changes diff --git a/plugins/search-backend-module-techdocs/package.json b/plugins/search-backend-module-techdocs/package.json index f44b27bc78..8d088fe1b7 100644 --- a/plugins/search-backend-module-techdocs/package.json +++ b/plugins/search-backend-module-techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-techdocs", - "version": "0.4.2-next.1", + "version": "0.4.2-next.2", "description": "A module for the search backend that exports techdocs modules", "backstage": { "role": "backend-plugin-module", diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 94cb067c97..9f51eb47ea 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search-backend +## 2.0.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-defaults@0.10.0-next.2 + - @backstage/backend-openapi-utils@0.5.3-next.1 + - @backstage/config@1.3.2 + - @backstage/plugin-permission-node@0.10.0-next.1 + - @backstage/plugin-search-backend-node@1.3.11-next.1 + - @backstage/backend-plugin-api@1.3.1-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/plugin-permission-common@0.9.0-next.0 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 2.0.2-next.1 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 04619a3c2b..57b212d906 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "2.0.2-next.1", + "version": "2.0.2-next.2", "description": "The Backstage backend plugin that provides your backstage app with search", "backstage": { "role": "backend-plugin", diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index be4b5107ca..a6c9421949 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-search-react +## 1.9.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/theme@0.6.6-next.0 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.9.0-next.0 ### Minor Changes diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index 7d3b90c2b4..59bc492e10 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.9.0-next.0", + "version": "1.9.0-next.1", "backstage": { "role": "web-library", "pluginId": "search", diff --git a/plugins/search-react/report-alpha.api.md b/plugins/search-react/report-alpha.api.md index 98a3c533db..d3763bfcbf 100644 --- a/plugins/search-react/report-alpha.api.md +++ b/plugins/search-react/report-alpha.api.md @@ -8,6 +8,7 @@ import { ExtensionBlueprint } from '@backstage/frontend-plugin-api'; import { ListItemProps } from '@material-ui/core/ListItem'; import { SearchDocument } from '@backstage/plugin-search-common'; import { SearchResult } from '@backstage/plugin-search-common'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) export type BaseSearchResultListItemProps = T & { @@ -94,6 +95,23 @@ export interface SearchFilterResultTypeBlueprintParams { value: string; } +// @alpha (undocumented) +export const searchReactTranslationRef: TranslationRef< + 'search-react', + { + readonly 'searchBar.title': 'Search'; + readonly 'searchBar.placeholder': 'Search in {{org}}'; + readonly 'searchFilter.allOptionTitle': 'All'; + readonly 'searchPagination.limitLabel': 'Results per page:'; + readonly 'searchPagination.limitText': 'of {{num}}'; + readonly noResultsDescription: 'Sorry, no results were found'; + readonly 'searchResultGroup.linkTitle': 'See All'; + readonly 'searchResultGroup.addFilterButtonTitle': 'Add filter'; + readonly 'searchResultPager.next': 'Next'; + readonly 'searchResultPager.previous': 'Previous'; + } +>; + // @alpha (undocumented) export type SearchResultItemExtensionComponent = < P extends BaseSearchResultListItemProps, diff --git a/plugins/search-react/src/alpha/index.ts b/plugins/search-react/src/alpha/index.ts index 239ffa89dd..7c44f59d56 100644 --- a/plugins/search-react/src/alpha/index.ts +++ b/plugins/search-react/src/alpha/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export * from './blueprints'; +export { searchReactTranslationRef } from '../translation'; diff --git a/plugins/search-react/src/components/SearchBar/SearchBar.tsx b/plugins/search-react/src/components/SearchBar/SearchBar.tsx index 98bfe81b32..d2a1d08283 100644 --- a/plugins/search-react/src/components/SearchBar/SearchBar.tsx +++ b/plugins/search-react/src/components/SearchBar/SearchBar.tsx @@ -38,6 +38,8 @@ import { } from 'react'; import useDebounce from 'react-use/esm/useDebounce'; import { SearchContextProvider, useSearch } from '../../context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; /** * Props for {@link SearchBarBase}. @@ -81,6 +83,7 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => { const configApi = useApi(configApiRef); const [value, setValue] = useState(''); const forwardedValueRef = useRef(''); + const { t } = useTranslationRef(searchReactTranslationRef); useEffect(() => { setValue(prevValue => { @@ -129,12 +132,15 @@ export const SearchBarBase = forwardRef((props: SearchBarBaseProps, ref) => { } }, [onChange, onClear]); - const ariaLabel: string | undefined = label ? undefined : 'Search'; + const ariaLabel: string | undefined = label + ? undefined + : t('searchBar.title'); const inputPlaceholder = placeholder ?? - `Search in ${configApi.getOptionalString('app.title') || 'Backstage'}`; - + t('searchBar.placeholder', { + org: configApi.getOptionalString('app.title') || 'Backstage', + }); const SearchIcon = useApp().getSystemIcon('search') || DefaultSearchIcon; const startAdornment = ( diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx index 5bbb8eb27f..ca6585a039 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.test.tsx @@ -418,5 +418,34 @@ describe('SearchFilter.Autocomplete', () => { expect(screen.getByTestId(`${name}-filter-spy`)).toHaveTextContent(''); }); }); + + it('allows typing a value and shows suggestions', async () => { + render( + + + + + , + ); + + const input = screen.getByRole('textbox'); + await userEvent.type(input, 'value'); + + await waitFor(() => { + expect(input).toHaveValue('value'); + expect(screen.getByRole('listbox')).toBeInTheDocument(); + expect( + screen.getByRole('option', { name: values[0] }), + ).toBeInTheDocument(); + expect( + screen.getByRole('option', { name: values[1] }), + ).toBeInTheDocument(); + }); + }); }); }); diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx index f95f0153fe..2002904f3f 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.Autocomplete.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ChangeEvent, useState } from 'react'; +import { ChangeEvent, useState, useMemo } from 'react'; import Chip from '@material-ui/core/Chip'; import TextField from '@material-ui/core/TextField'; import Autocomplete, { @@ -69,7 +69,10 @@ export const AutocompleteFilter = (props: SearchAutocompleteFilterProps) => { const filterValueWithLabel = ensureFilterValueWithLabel( filters[name] as string | string[] | undefined, ); - const filterValue = filterValueWithLabel || (multiple ? [] : null); + const filterValue = useMemo( + () => filterValueWithLabel || (multiple ? [] : null), + [filterValueWithLabel, multiple], + ); // Set new filter values on input change. const handleChange = ( diff --git a/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx b/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx index 736e3f3f93..d8d3b75aa2 100644 --- a/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx +++ b/plugins/search-react/src/components/SearchFilter/SearchFilter.test.tsx @@ -14,14 +14,18 @@ * limitations under the License. */ -import { screen, render, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { configApiRef } from '@backstage/core-plugin-api'; import { SearchContextProvider } from '../../context'; import { SearchFilter } from './SearchFilter'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; import { searchApiRef } from '../../api'; describe('SearchFilter', () => { @@ -55,14 +59,16 @@ describe('SearchFilter', () => { it('Check that element was rendered and received props', async () => { const CustomFilter = (props: { name: string }) =>
{props.name}
; - render(); + await renderInTestApp( + , + ); expect(screen.getByRole('heading', { name })).toBeInTheDocument(); }); describe('Checkbox', () => { it('Renders field name and values when provided as props', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on filter state', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on defaultValue', async () => { - render( + await renderInTestApp( { }); it('Checking / unchecking a value sets filter state', async () => { - render( + await renderInTestApp( { }); it('Checking / unchecking a value maintains unrelated filter state', async () => { - render( + await renderInTestApp( { describe('Select', () => { it('Renders field name and values when provided as props', async () => { - render( + await renderInTestApp( { }); it('Renders values when provided asynchronously', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on filter state', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on defaultValue', async () => { - render( + await renderInTestApp( { }); it('Selecting a value sets filter state', async () => { - render( + await renderInTestApp( { }); it('Selecting a value maintains unrelated filter state', async () => { - render( + await renderInTestApp( { values: givenValues, valuesDebounceMs, } = props; + const { t } = useTranslationRef(searchReactTranslationRef); useDefaultFilterValue(name, defaultValue); const asyncValues = typeof givenValues === 'function' ? givenValues : undefined; @@ -179,7 +182,10 @@ export const SelectFilter = (props: SearchFilterComponentProps) => { valuesDebounceMs, ); const allOptionValue = useRef(uuid()); - const allOption = { value: allOptionValue.current, label: 'All' }; + const allOption = { + value: allOptionValue.current, + label: t('searchFilter.allOptionTitle'), + }; const { filters, setFilters } = useSearch(); const handleChange = (value: SelectedItems) => { diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx index 94d90cff59..d601caadd9 100644 --- a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -19,7 +19,7 @@ import userEvent from '@testing-library/user-event'; import { mockApis, - renderWithEffects, + renderInTestApp, TestApiProvider, } from '@backstage/test-utils'; @@ -53,7 +53,7 @@ describe('SearchPagination', () => { }); it('Renders without exploding', async () => { - await renderWithEffects( + await renderInTestApp( { }); it('Define default page limit options', async () => { - await renderWithEffects( + await renderInTestApp( { it('Accept custom page limit label', async () => { const label = 'Page limit:'; - await renderWithEffects( + await renderInTestApp( { }); it('Show the total in text', async () => { - await renderWithEffects( + await renderInTestApp( { }); it('Accept custom page limit text', async () => { - await renderWithEffects( + await renderInTestApp( { }); it('Accept custom page limit options', async () => { - await renderWithEffects( + await renderInTestApp( { }); it('Set page limit in the context', async () => { - await renderWithEffects( + await renderInTestApp( { pageCursor: 'MQ==', // page: 1 }; - await renderWithEffects( + await renderInTestApp( { pageCursor: 'Mg==', // page: 2 }; - await renderWithEffects( + await renderInTestApp( { return Buffer.from(pageCursor.toString(), 'utf-8').toString('base64'); @@ -119,15 +121,18 @@ export type SearchPaginationBaseProps = { * @public */ export const SearchPaginationBase = (props: SearchPaginationBaseProps) => { + const { t } = useTranslationRef(searchReactTranslationRef); const { total: count = -1, cursor: pageCursor, hasNextPage, onCursorChange: onPageCursorChange, limit: rowsPerPage = 25, - limitLabel: labelRowsPerPage = 'Results per page:', + limitLabel: labelRowsPerPage = t('searchPagination.limitLabel'), limitText: labelDisplayedRows = ({ from, to }) => - count > 0 ? `of ${count}` : `${from}-${to}`, + count > 0 + ? t('searchPagination.limitText', { num: `${count}` }) + : `${from}-${to}`, limitOptions: rowsPerPageOptions, onLimitChange: onPageLimitChange, ...rest diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.tsx index b0f4570885..99c7f1fb3a 100644 --- a/plugins/search-react/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search-react/src/components/SearchResult/SearchResult.tsx @@ -32,6 +32,8 @@ import { SearchResultListItemExtensions, SearchResultListItemExtensionsProps, } from '../../extensions'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; /** * Props for {@link SearchResultContext} @@ -186,11 +188,12 @@ export type SearchResultProps = Pick & * @public */ export const SearchResultComponent = (props: SearchResultProps) => { + const { t } = useTranslationRef(searchReactTranslationRef); const { query, children, noResultsComponent = ( - + ), ...rest } = props; diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx index 4c2d300314..9a9290bcbc 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx @@ -51,6 +51,8 @@ import { useSearchResultListItemExtensions } from '../../extensions'; import { DefaultResultListItem } from '../DefaultResultListItem'; import { SearchResultState, SearchResultStateProps } from '../SearchResult'; +import { searchReactTranslationRef } from '../../translation'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; const useStyles = makeStyles((theme: Theme) => ({ listSubheader: { @@ -356,6 +358,7 @@ export function SearchResultGroupLayout( ) { const classes = useStyles(); const [anchorEl, setAnchorEl] = useState(null); + const { t } = useTranslationRef(searchReactTranslationRef); const { error, @@ -365,7 +368,7 @@ export function SearchResultGroupLayout( titleProps = {}, link = ( <> - See all + {t('searchResultGroup.linkTitle')} ), @@ -387,7 +390,7 @@ export function SearchResultGroupLayout( ), disableRenderingWithNoResults, noResultsComponent = disableRenderingWithNoResults ? null : ( - + ), ...rest } = props; @@ -434,7 +437,7 @@ export function SearchResultGroupLayout( component="button" icon={} variant="outlined" - label="Add filter" + label={t('searchResultGroup.addFilterButtonTitle')} aria-controls="filters-menu" aria-haspopup="true" onClick={handleClick} diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx index 48ed8ac92f..a5459d8619 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx @@ -30,6 +30,8 @@ import { useSearchResultListItemExtensions } from '../../extensions'; import { DefaultResultListItem } from '../DefaultResultListItem'; import { SearchResultState, SearchResultStateProps } from '../SearchResult'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; /** * Props for {@link SearchResultListLayout} @@ -72,6 +74,7 @@ export type SearchResultListLayoutProps = ListProps & { * @public */ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { + const { t } = useTranslationRef(searchReactTranslationRef); const { error, loading, @@ -84,7 +87,7 @@ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { ), disableRenderingWithNoResults, noResultsComponent = disableRenderingWithNoResults ? null : ( - + ), ...rest } = props; diff --git a/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx b/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx index 764bb28129..d1dca8bb95 100644 --- a/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx +++ b/plugins/search-react/src/components/SearchResultPager/SearchResultPager.tsx @@ -20,6 +20,8 @@ import ArrowBackIosIcon from '@material-ui/icons/ArrowBackIos'; import ArrowForwardIosIcon from '@material-ui/icons/ArrowForwardIos'; import { useSearch } from '../../context'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchReactTranslationRef } from '../../translation'; const useStyles = makeStyles(theme => ({ root: { @@ -36,6 +38,7 @@ const useStyles = makeStyles(theme => ({ export const SearchResultPager = () => { const { fetchNextPage, fetchPreviousPage } = useSearch(); const classes = useStyles(); + const { t } = useTranslationRef(searchReactTranslationRef); if (!fetchNextPage && !fetchPreviousPage) { return <>; @@ -49,7 +52,7 @@ export const SearchResultPager = () => { onClick={fetchPreviousPage} startIcon={} > - Previous + {t('searchResultPager.previous')} ); diff --git a/plugins/search-react/src/translation.ts b/plugins/search-react/src/translation.ts new file mode 100644 index 0000000000..6897911949 --- /dev/null +++ b/plugins/search-react/src/translation.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2025 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 { createTranslationRef } from '@backstage/core-plugin-api/alpha'; + +/** + * @alpha + */ +export const searchReactTranslationRef = createTranslationRef({ + id: 'search-react', + messages: { + searchBar: { + title: 'Search', + placeholder: 'Search in {{org}}', + }, + searchFilter: { + allOptionTitle: 'All', + }, + searchPagination: { + limitLabel: 'Results per page:', + limitText: 'of {{num}}', + }, + noResultsDescription: 'Sorry, no results were found', + searchResultGroup: { + linkTitle: 'See All', + addFilterButtonTitle: 'Add filter', + }, + searchResultPager: { + previous: 'Previous', + next: 'Next', + }, + }, +}); diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 62b1e53a2f..8a4d42569e 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search +## 1.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.10.7-next.0 + - @backstage/core-compat-api@0.4.2-next.2 + - @backstage/core-components@0.17.2-next.1 + - @backstage/frontend-plugin-api@0.10.2-next.1 + - @backstage/plugin-catalog-react@1.18.0-next.2 + - @backstage/plugin-search-react@1.9.0-next.1 + - @backstage/errors@1.2.7 + - @backstage/types@1.2.1 + - @backstage/version-bridge@1.0.11 + - @backstage/plugin-search-common@1.2.18-next.0 + ## 1.4.26-next.1 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 64e66dada5..8a872978e2 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "1.4.26-next.1", + "version": "1.4.26-next.2", "description": "The Backstage plugin that provides your backstage app with search", "backstage": { "role": "frontend-plugin", diff --git a/plugins/search/report-alpha.api.md b/plugins/search/report-alpha.api.md index b8df4d3b4c..3af0384d89 100644 --- a/plugins/search/report-alpha.api.md +++ b/plugins/search/report-alpha.api.md @@ -15,6 +15,7 @@ import { RouteRef } from '@backstage/frontend-plugin-api'; import { SearchFilterExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionComponent } from '@backstage/plugin-search-react/alpha'; import { SearchResultItemExtensionPredicate } from '@backstage/plugin-search-react/alpha'; +import { TranslationRef } from '@backstage/core-plugin-api/alpha'; // @alpha (undocumented) const _default: FrontendPlugin< @@ -247,5 +248,19 @@ export const searchPage: ExtensionDefinition<{ }; }>; +// @alpha (undocumented) +export const searchTranslationRef: TranslationRef< + 'search', + { + readonly 'searchModal.viewFullResults': 'View Full Results'; + readonly 'searchType.tabs.allTitle': 'All'; + readonly 'searchType.allResults': 'All Results'; + readonly 'searchType.accordion.collapse': 'Collapse'; + readonly 'searchType.accordion.allTitle': 'All'; + readonly 'searchType.accordion.numberOfResults': '{{number}} results'; + readonly 'sidebarSearchModal.title': 'Search'; + } +>; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/search/src/alpha.tsx b/plugins/search/src/alpha.tsx index fed0124fa7..5f55b46028 100644 --- a/plugins/search/src/alpha.tsx +++ b/plugins/search/src/alpha.tsx @@ -284,3 +284,6 @@ export default createFrontendPlugin({ root: rootRouteRef, }), }); + +/** @alpha */ +export { searchTranslationRef } from './translation'; diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx index 454d2eae96..af7e1c6e10 100644 --- a/plugins/search/src/components/SearchModal/SearchModal.tsx +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -39,6 +39,8 @@ import { useNavigate } from 'react-router-dom'; import { rootRouteRef } from '../../plugin'; import { SearchResultSet } from '@backstage/plugin-search-common'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; /** * @public @@ -121,6 +123,7 @@ export const Modal = ({ const navigate = useNavigate(); const { transitions } = useTheme(); const { focusContent } = useContent(); + const { t } = useTranslationRef(searchTranslationRef); const searchRootRoute = useRouteRef(rootRouteRef)(); const searchBarRef = useRef(null); @@ -171,7 +174,7 @@ export const Modal = ({ onClick={handleSearchBarSubmit} disableRipple > - View Full Results + {t('searchModal.viewFullResults')} diff --git a/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx b/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx index 6a124884c0..1245ceed5a 100644 --- a/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Accordion.test.tsx @@ -15,8 +15,12 @@ */ import { ReactNode } from 'react'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; -import { act, render, waitFor } from '@testing-library/react'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; +import { act, waitFor } from '@testing-library/react'; import user from '@testing-library/user-event'; import { searchApiRef, @@ -81,7 +85,7 @@ describe('SearchType.Accordion', () => { }; it('should render as expected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -103,7 +107,7 @@ describe('SearchType.Accordion', () => { }); it('should set entire types array when a type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -115,7 +119,7 @@ describe('SearchType.Accordion', () => { }); it('should reset types array when all is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( { }); it('should reset page cursor when a new type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -143,7 +147,7 @@ describe('SearchType.Accordion', () => { }); it('should show result counts if enabled', async () => { - const { getAllByText } = render( + const { getAllByText } = await renderInTestApp( ({ icon: { @@ -83,6 +85,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const searchApi = useApi(searchApiRef); const [expanded, setExpanded] = useState(true); const { defaultValue, name, showCounts, types: givenTypes } = props; + const { t } = useTranslationRef(searchTranslationRef); const toggleExpanded = () => setExpanded(prevState => !prevState); const handleClick = (type: string) => { @@ -103,7 +106,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const definedTypes = [ { value: '', - name: 'All', + name: t('searchType.accordion.allTitle'), icon: , }, ...givenTypes, @@ -117,7 +120,7 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { const counts = await Promise.all( definedTypes - .map(t => t.value) + .map(type => type.value) .map(async type => { const { numberOfResults } = await searchApi.query({ term, @@ -130,9 +133,10 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { return [ type, numberOfResults !== undefined - ? `${ - numberOfResults >= 10000 ? `>10000` : numberOfResults - } results` + ? t('searchType.accordion.numberOfResults', { + number: + numberOfResults >= 10000 ? `>10000` : `${numberOfResults}`, + }) : ' -- ', ]; }), @@ -160,8 +164,8 @@ export const SearchTypeAccordion = (props: SearchTypeAccordionProps) => { IconButtonProps={{ size: 'small' }} > {expanded - ? 'Collapse' - : definedTypes.filter(t => t.value === selected)[0]!.name} + ? t('searchType.accordion.collapse') + : definedTypes.filter(type => type.value === selected)[0]!.name} { }; it('should render as expected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -85,7 +89,7 @@ describe('SearchType.Tabs', () => { }); it('should set entire types array when a type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , @@ -97,7 +101,7 @@ describe('SearchType.Tabs', () => { }); it('should reset types array when all is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( { }); it('should reset page cursor when a new type is selected', async () => { - const { getByText } = render( + const { getByText } = await renderInTestApp( , diff --git a/plugins/search/src/components/SearchType/SearchType.Tabs.tsx b/plugins/search/src/components/SearchType/SearchType.Tabs.tsx index f4323da915..ed09ea940a 100644 --- a/plugins/search/src/components/SearchType/SearchType.Tabs.tsx +++ b/plugins/search/src/components/SearchType/SearchType.Tabs.tsx @@ -20,6 +20,8 @@ import Tab from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; import { makeStyles } from '@material-ui/core/styles'; import { Theme } from '@material-ui/core/styles'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; const useStyles = makeStyles((theme: Theme) => ({ tabs: { @@ -49,6 +51,7 @@ export const SearchTypeTabs = (props: SearchTypeTabsProps) => { const classes = useStyles(); const { setPageCursor, setTypes, types } = useSearch(); const { defaultValue, types: givenTypes } = props; + const { t } = useTranslationRef(searchTranslationRef); const changeTab = (_: ChangeEvent<{}>, newType: string) => { setTypes(newType !== '' ? [newType] : []); @@ -66,7 +69,7 @@ export const SearchTypeTabs = (props: SearchTypeTabsProps) => { const definedTypes = [ { value: '', - name: 'All', + name: t('searchType.tabs.allTitle'), }, ...givenTypes, ]; diff --git a/plugins/search/src/components/SearchType/SearchType.test.tsx b/plugins/search/src/components/SearchType/SearchType.test.tsx index fd014369f7..231463c9bf 100644 --- a/plugins/search/src/components/SearchType/SearchType.test.tsx +++ b/plugins/search/src/components/SearchType/SearchType.test.tsx @@ -15,14 +15,18 @@ */ import { configApiRef } from '@backstage/core-plugin-api'; -import { render, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SearchContextProvider, searchApiRef, } from '@backstage/plugin-search-react'; import { SearchType } from './SearchType'; -import { mockApis, TestApiProvider } from '@backstage/test-utils'; +import { + mockApis, + renderInTestApp, + TestApiProvider, +} from '@backstage/test-utils'; describe('SearchType', () => { const initialState = { @@ -53,7 +57,7 @@ describe('SearchType', () => { describe('Type Filter', () => { it('Renders field name and values when provided as props', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on type filter state', async () => { - render( + await renderInTestApp( { }); it('Renders correctly based on type filter defaultValue', async () => { - render( + await renderInTestApp( { }); it('Selecting a value sets type filter state', async () => { - render( + await renderInTestApp( { }); it('Selecting none defaults to empty state', async () => { - render( + await renderInTestApp( ({ label: { @@ -63,6 +65,7 @@ const SearchType = (props: SearchTypeProps) => { const { className, defaultValue, name, values = [] } = props; const classes = useStyles(); const { types, setTypes } = useSearch(); + const { t } = useTranslationRef(searchTranslationRef); useEffectOnce(() => { if (!types.length) { @@ -94,7 +97,7 @@ const SearchType = (props: SearchTypeProps) => { variant="outlined" value={types} onChange={handleChange} - placeholder="All Results" + placeholder={t('searchType.allResults')} renderValue={selected => (
{(selected as string[]).map(value => ( diff --git a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx index ad687a35db..5dde4f385b 100644 --- a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx +++ b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx @@ -22,6 +22,8 @@ import { SearchModalProvider, useSearchModal, } from '../SearchModal'; +import { useTranslationRef } from '@backstage/frontend-plugin-api'; +import { searchTranslationRef } from '../../translation'; /** * Props for {@link SidebarSearchModal}. @@ -38,13 +40,14 @@ export type SidebarSearchModalProps = Pick< const SidebarSearchModalContent = (props: SidebarSearchModalProps) => { const { state, toggleModal } = useSearchModal(); const Icon = props.icon ? props.icon : SearchIcon; + const { t } = useTranslationRef(searchTranslationRef); return ( <>