diff --git a/.changeset/angry-countries-cheat.md b/.changeset/angry-countries-cheat.md deleted file mode 100644 index e97e0ae56c..0000000000 --- a/.changeset/angry-countries-cheat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Switched to dynamically determining the packages that are unsafe to repack when executing the CLI within the Backstage main repo. diff --git a/.changeset/big-cougars-glow.md b/.changeset/big-cougars-glow.md new file mode 100644 index 0000000000..d1630905db --- /dev/null +++ b/.changeset/big-cougars-glow.md @@ -0,0 +1,5 @@ +--- +'@backstage/dev-utils': patch +--- + +Add theme switcher to sidebar of dev app. diff --git a/.changeset/clean-fans-change.md b/.changeset/clean-fans-change.md deleted file mode 100644 index fad4a9cfa6..0000000000 --- a/.changeset/clean-fans-change.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes': patch ---- - -fix: kubernetes plugin shall pass id token on get clusters request if possible diff --git a/.changeset/dry-spies-cover.md b/.changeset/dry-spies-cover.md deleted file mode 100644 index aae683faaf..0000000000 --- a/.changeset/dry-spies-cover.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/core-components': patch -'@backstage/theme': patch ---- - -Added a warning variant to `DismissableBanner` component. If you are using a -custom theme, you will need to add the optional `palette.banner.warning` color, -otherwise this variant will fall back to the `palette.banner.error` color. diff --git a/.changeset/early-cobras-explode.md b/.changeset/early-cobras-explode.md new file mode 100644 index 0000000000..34b4cb0e98 --- /dev/null +++ b/.changeset/early-cobras-explode.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-client': patch +--- + +Update to the right version of @backstage/errors diff --git a/.changeset/early-trains-hammer.md b/.changeset/early-trains-hammer.md deleted file mode 100644 index 853788e015..0000000000 --- a/.changeset/early-trains-hammer.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -Tweak logic for msgraph catalog ingesting for display names with security groups - -Previously security groups that weren't mail enabled were imported with UUIDs, now they use the display name. diff --git a/.changeset/eight-months-agree.md b/.changeset/eight-months-agree.md new file mode 100644 index 0000000000..0f117d8a40 --- /dev/null +++ b/.changeset/eight-months-agree.md @@ -0,0 +1,19 @@ +--- +'@backstage/plugin-gcp-projects': patch +--- + +UI updates to GCP-projects plugin + +Adds the following to the project list page: + +- pagination +- filtering +- sorting +- rows per page +- show/hide columns + +Makes breadcrumb a link back to project list for the project details and new project views. + +In project list page, updates New project button to use RouterLink instead of `href` to avoid login prompt. + +In project details view, links to project details and logs now work, clicking on these will open the project or logs in GCP in new tab. diff --git a/.changeset/few-waves-dream.md b/.changeset/few-waves-dream.md deleted file mode 100644 index 72123171b8..0000000000 --- a/.changeset/few-waves-dream.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -AWSS3UrlReader now throws a `NotModifiedError` (exported from @backstage/backend-common) when s3 returns a 304 response. diff --git a/.changeset/flat-dodos-bake.md b/.changeset/flat-dodos-bake.md deleted file mode 100644 index f6b52aac1a..0000000000 --- a/.changeset/flat-dodos-bake.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-jenkins-backend': patch ---- - -Don't require a validation pattern for the Jenkins base URL. diff --git a/.changeset/forty-ligers-protect.md b/.changeset/forty-ligers-protect.md new file mode 100644 index 0000000000..e3e46fb7f3 --- /dev/null +++ b/.changeset/forty-ligers-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +remove double config dep diff --git a/.changeset/four-years-develop.md b/.changeset/four-years-develop.md deleted file mode 100644 index cfd3704f2f..0000000000 --- a/.changeset/four-years-develop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Allow for `cellStyle` property on `TableColumn` to be a function as well as `React.CSSProperties` as per the Material UI Table component diff --git a/.changeset/giant-drinks-wave.md b/.changeset/giant-drinks-wave.md new file mode 100644 index 0000000000..ecf4c8fbdd --- /dev/null +++ b/.changeset/giant-drinks-wave.md @@ -0,0 +1,5 @@ +--- +'@backstage/dev-utils': patch +--- + +Migrated to using `@backstage/app-defaults`. diff --git a/.changeset/giant-years-help.md b/.changeset/giant-years-help.md deleted file mode 100644 index c6ca0def30..0000000000 --- a/.changeset/giant-years-help.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/cli': patch -'@backstage/config-loader': patch ---- - -Reading app config from a remote server diff --git a/.changeset/happy-rice-tickle.md b/.changeset/happy-rice-tickle.md new file mode 100644 index 0000000000..1e39dc2e9c --- /dev/null +++ b/.changeset/happy-rice-tickle.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Added the `isDatabaseConflictError` function. diff --git a/.changeset/hot-walls-fail.md b/.changeset/hot-walls-fail.md new file mode 100644 index 0000000000..3728e10ff6 --- /dev/null +++ b/.changeset/hot-walls-fail.md @@ -0,0 +1,13 @@ +--- +'@backstage/create-app': patch +--- + +Migrated the app template use the new `@backstage/app-defaults` for the `createApp` import, since the `createApp` exported by `@backstage/app-core-api` will be removed in the future. + +To migrate an existing application, add the latest version of `@backstage/app-defaults` as a dependency in `packages/app/package.json`, and make the following change to `packages/app/src/App.tsx`: + +```diff +-import { createApp, FlatRoutes } from '@backstage/core-app-api'; ++import { createApp } from '@backstage/app-defaults'; ++import { FlatRoutes } from '@backstage/core-app-api'; +``` diff --git a/.changeset/late-walls-cry.md b/.changeset/late-walls-cry.md deleted file mode 100644 index c9fcb3112a..0000000000 --- a/.changeset/late-walls-cry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-elasticsearch': patch ---- - -Pinning version of elastic search client to 7.13.0 to prevent breaking change towards third party ElasticSearch clusters on 7.14.0. diff --git a/.changeset/light-knives-camp.md b/.changeset/light-knives-camp.md deleted file mode 100644 index 1e7fa37082..0000000000 --- a/.changeset/light-knives-camp.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-ilert': patch ---- - -Change the version of `@date-io/luxon` from 2.x to 1.x to make it compatible with material-ui-pickers diff --git a/.changeset/long-bugs-kiss.md b/.changeset/long-bugs-kiss.md deleted file mode 100644 index 49e0edae72..0000000000 --- a/.changeset/long-bugs-kiss.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kafka': patch ---- - -Use IdentityApi to provide Auth Token for KafkaBackendClient Api calls diff --git a/.changeset/loud-bats-flow.md b/.changeset/loud-bats-flow.md deleted file mode 100644 index af4837dbbe..0000000000 --- a/.changeset/loud-bats-flow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/test-utils': patch ---- - -Update Keyboard deprecation with a link to the recommended successor diff --git a/.changeset/lovely-cars-sneeze.md b/.changeset/lovely-cars-sneeze.md deleted file mode 100644 index 8e6bfd691c..0000000000 --- a/.changeset/lovely-cars-sneeze.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Expose some classes and interfaces public so TaskWorkers can run externally from the scaffolder API. diff --git a/.changeset/many-mayflies-notice.md b/.changeset/many-mayflies-notice.md deleted file mode 100644 index a96d1fcce5..0000000000 --- a/.changeset/many-mayflies-notice.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/plugin-azure-devops': patch -'@backstage/plugin-azure-devops-backend': minor -'@backstage/plugin-azure-devops-common': patch ---- - -refactor(`@backstage/plugin-azure-devops`): Consume types from `@backstage/plugin-azure-devops-common`. -Stop re-exporting types from `@backstage/plugin-azure-devops-backend`. -Added new types to `@backstage/plugin-azure-devops-common`. diff --git a/.changeset/mean-cars-wash.md b/.changeset/mean-cars-wash.md deleted file mode 100644 index 187f5d7e21..0000000000 --- a/.changeset/mean-cars-wash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-client': patch ---- - -Improved API documentation for catalog-client. diff --git a/.changeset/metal-impalas-allow.md b/.changeset/metal-impalas-allow.md deleted file mode 100644 index 8ad727f759..0000000000 --- a/.changeset/metal-impalas-allow.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -1. Techdocs publisher constructors now use parameter objects when being - instantiated - -2. Internal refactor of `LocalPublish` publisher to use `fromConfig` for - creation to be aligned with other publishers; this does not impact - `LocalPublish` usage. - -```diff -- const publisher = new LocalPublish(config, logger, discovery); -+ const publisher = LocalPublish.fromConfig(config, logger, discovery); -``` diff --git a/.changeset/modern-beers-tickle.md b/.changeset/modern-beers-tickle.md deleted file mode 100644 index 6ad9cc872d..0000000000 --- a/.changeset/modern-beers-tickle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Only use settings that have a value when creating a new FirestoreKeyStore instance diff --git a/.changeset/moody-snails-admire.md b/.changeset/moody-snails-admire.md new file mode 100644 index 0000000000..7622546989 --- /dev/null +++ b/.changeset/moody-snails-admire.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': minor +--- + +Tweaked style insertion logic to make sure that JSS stylesheets always receive the highest priority. diff --git a/.changeset/nasty-actors-push.md b/.changeset/nasty-actors-push.md deleted file mode 100644 index 8f4d0d92a8..0000000000 --- a/.changeset/nasty-actors-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-sentry': patch ---- - -fix: sentry-plugin can forward identity token to backend (for case when it requires authorization) diff --git a/.changeset/neat-pugs-wait.md b/.changeset/neat-pugs-wait.md deleted file mode 100644 index 8902f44c9d..0000000000 --- a/.changeset/neat-pugs-wait.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-code-coverage': patch ---- - -Change represented test date from epoch to something more human friendly. Round test coverage to 2 decimal places. diff --git a/.changeset/nice-tools-cheat.md b/.changeset/nice-tools-cheat.md deleted file mode 100644 index c4f0e162bf..0000000000 --- a/.changeset/nice-tools-cheat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Make `ExitCodeError` call `super` early to avoid compiler warnings diff --git a/.changeset/nine-dots-suffer.md b/.changeset/nine-dots-suffer.md deleted file mode 100644 index 3efd307a10..0000000000 --- a/.changeset/nine-dots-suffer.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Updated the app template to no longer include the `--no-private` flag for the `create-plugin` command. - -To apply this change to an existing application, remove the `--no-private` flag from the `create-plugin` command in the root `package.json`: - -```diff - "prettier:check": "prettier --check .", -- "create-plugin": "backstage-cli create-plugin --scope internal --no-private", -+ "create-plugin": "backstage-cli create-plugin --scope internal", - "remove-plugin": "backstage-cli remove-plugin" -``` diff --git a/.changeset/odd-rats-walk.md b/.changeset/odd-rats-walk.md deleted file mode 100644 index 3a8710cc32..0000000000 --- a/.changeset/odd-rats-walk.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/config-loader': patch ---- - -Bump msw to the same version as the rest diff --git a/.changeset/perfect-avocados-mate.md b/.changeset/perfect-avocados-mate.md deleted file mode 100644 index 9394b57449..0000000000 --- a/.changeset/perfect-avocados-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-code-coverage': patch ---- - -Make dates in X-Axis sort in ascending order diff --git a/.changeset/real-mails-add.md b/.changeset/real-mails-add.md deleted file mode 100644 index 30a9100699..0000000000 --- a/.changeset/real-mails-add.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -fixed route resolving (issue #7741) when user cannot select a tab in any of the tabbed pages (like the Catalog page) if it shares the same initial letters as a preceding tab. (i.e. where tab with a path of /ci is followed by a path of /ci-2, user cannot select /ci-2 as /ci will always be selected first). diff --git a/.changeset/rich-pillows-cough.md b/.changeset/rich-pillows-cough.md new file mode 100644 index 0000000000..959eb69214 --- /dev/null +++ b/.changeset/rich-pillows-cough.md @@ -0,0 +1,12 @@ +--- +'@backstage/backend-common': patch +--- + +Make sure that server builder `start()` propagates errors (such as failing to bind to the required port) properly and doesn't resolve the promise prematurely. + +After this change, the backend logger will be able to actually capture the error as it happens: + +``` +2021-11-11T10:54:21.334Z backstage info Initializing http server +2021-11-11T10:54:21.335Z backstage error listen EADDRINUSE: address already in use :::7000 code=EADDRINUSE errno=-48 syscall=listen address=:: port=7000 +``` diff --git a/.changeset/selfish-bags-punch.md b/.changeset/selfish-bags-punch.md deleted file mode 100644 index 1950da9357..0000000000 --- a/.changeset/selfish-bags-punch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Added forwarding of the `audience` option for the SAML provider, making it possible to enable `audience` verification. diff --git a/.changeset/selfish-countries-prove.md b/.changeset/selfish-countries-prove.md deleted file mode 100644 index 03e36f6d80..0000000000 --- a/.changeset/selfish-countries-prove.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Adds a new endpoint for consuming logs from the Scaffolder that uses long polling instead of Server Sent Events. - -This is useful if Backstage is accessed from an environment that doesn't support SSE correctly, which happens in combination with certain enterprise HTTP Proxy servers. - -It is intended to switch the endpoint globally for the whole instance. -If you want to use it, you can provide a reconfigured API to the `scaffolderApiRef`: - -```tsx -// packages/app/src/apis.ts - -// ... -import { - scaffolderApiRef, - ScaffolderClient, -} from '@backstage/plugin-scaffolder'; - -export const apis: AnyApiFactory[] = [ - // ... - - createApiFactory({ - api: scaffolderApiRef, - deps: { - discoveryApi: discoveryApiRef, - identityApi: identityApiRef, - scmIntegrationsApi: scmIntegrationsApiRef, - }, - factory: ({ discoveryApi, identityApi, scmIntegrationsApi }) => - new ScaffolderClient({ - discoveryApi, - identityApi, - scmIntegrationsApi, - // use long polling instead of an eventsource - useLongPollingLogs: true, - }), - }), -]; -``` diff --git a/.changeset/seven-zoos-sparkle.md b/.changeset/seven-zoos-sparkle.md deleted file mode 100644 index 1399c3254e..0000000000 --- a/.changeset/seven-zoos-sparkle.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Removed the version pinning of the packages `graphql-language-service-interface` and `graphql-language-service-parser`. This should no longer be necessary. - -You can apply the same change in your repository by ensuring that the following does _NOT_ appear in your root `package.json`. - -```json -"resolutions": { - "graphql-language-service-interface": "2.8.2", - "graphql-language-service-parser": "1.9.0" - }, -``` diff --git a/.changeset/sharp-meals-fetch.md b/.changeset/sharp-meals-fetch.md new file mode 100644 index 0000000000..1d3df65ba4 --- /dev/null +++ b/.changeset/sharp-meals-fetch.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': minor +--- + +Removed the unused `UserFlags` type. diff --git a/.changeset/sharp-moons-jog.md b/.changeset/sharp-moons-jog.md new file mode 100644 index 0000000000..d3329af08e --- /dev/null +++ b/.changeset/sharp-moons-jog.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-plugin-api': patch +--- + +Deprecated the `theme` property on `AppTheme`, replacing it with `Provider`. See https://backstage.io/docs/api/deprecations#app-theme for more details. diff --git a/.changeset/slimy-days-leave.md b/.changeset/slimy-days-leave.md deleted file mode 100644 index df772d80d4..0000000000 --- a/.changeset/slimy-days-leave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Allow singleton and flexibly nested EntityFilters diff --git a/.changeset/slow-moles-act.md b/.changeset/slow-moles-act.md new file mode 100644 index 0000000000..ab920bfa5d --- /dev/null +++ b/.changeset/slow-moles-act.md @@ -0,0 +1,5 @@ +--- +'@backstage/test-utils': patch +--- + +Migrated to using `createSpecializedApp`. diff --git a/.changeset/smart-penguins-compete.md b/.changeset/smart-penguins-compete.md deleted file mode 100644 index d3518600a6..0000000000 --- a/.changeset/smart-penguins-compete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -export `loadIdentityOwnerRefs` and `loadCatalogOwnerRefs` all the way diff --git a/.changeset/spicy-panthers-thank.md b/.changeset/spicy-panthers-thank.md deleted file mode 100644 index 6c28b002f4..0000000000 --- a/.changeset/spicy-panthers-thank.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -I have added default icons for the catalog, scaffolder, techdocs, and search. diff --git a/.changeset/tall-boxes-sit.md b/.changeset/tall-boxes-sit.md deleted file mode 100644 index a74e0400bd..0000000000 --- a/.changeset/tall-boxes-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Add Theme Overrides for Sidebar diff --git a/.changeset/tall-lions-return.md b/.changeset/tall-lions-return.md deleted file mode 100644 index 56bb0fc3dd..0000000000 --- a/.changeset/tall-lions-return.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Bumped `@spotify/eslint-config-typescript` from `v10` to `v12`, dropping support for Node.js v12. diff --git a/.changeset/techdocs-buh-bump-ts.md b/.changeset/techdocs-buh-bump-ts.md deleted file mode 100644 index 438843e04b..0000000000 --- a/.changeset/techdocs-buh-bump-ts.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Default TechDocs container used at docs generation-time is now [v0.3.5](https://github.com/backstage/techdocs-container/releases/tag/v0.3.5). diff --git a/.changeset/techdocs-tender-bugs-sort.md b/.changeset/techdocs-tender-bugs-sort.md deleted file mode 100644 index be670b06ff..0000000000 --- a/.changeset/techdocs-tender-bugs-sort.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Updated to properly join URL segments under any OS for both AWS S3 and GCP diff --git a/.changeset/tender-chefs-approve.md b/.changeset/tender-chefs-approve.md deleted file mode 100644 index 1cc618507c..0000000000 --- a/.changeset/tender-chefs-approve.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core-app-api': patch -'@backstage/core-plugin-api': patch ---- - -Improve API documentation for @backstage/core-plugin-api diff --git a/.changeset/tough-buckets-explain.md b/.changeset/tough-buckets-explain.md deleted file mode 100644 index a2ca37d516..0000000000 --- a/.changeset/tough-buckets-explain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-azure-devops': patch ---- - -Simplified queue time calculation in `BuildTable`. diff --git a/.changeset/twenty-swans-matter.md b/.changeset/twenty-swans-matter.md new file mode 100644 index 0000000000..55cd3d6dc3 --- /dev/null +++ b/.changeset/twenty-swans-matter.md @@ -0,0 +1,9 @@ +--- +'@backstage/core-app-api': patch +--- + +The `createApp` function from `@backstage/core-app-api` has been deprecated, with two new options being provided as a replacement. + +The first and most commonly used one is `createApp` from the new `@backstage/app-defaults` package, which behaves just like the existing `createApp`. In the future this method is likely to be expanded to add more APIs and other pieces into the default setup, for example the Utility APIs from `@backstage/integration-react`. + +The other option that we now provide is to use `createSpecializedApp` from `@backstage/core-app-api`. This is a more low-level API where you need to provide a full set of options, including your own `components`, `icons`, `defaultApis`, and `themes`. The `createSpecializedApp` way of creating an app is particularly useful if you are not using `@backstage/core-components` or MUI, as it allows you to avoid those dependencies completely. diff --git a/.changeset/wet-pears-thank.md b/.changeset/wet-pears-thank.md deleted file mode 100644 index f9b5576fb3..0000000000 --- a/.changeset/wet-pears-thank.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix a bug where only file mode 775 is considered an executable diff --git a/.changeset/wicked-boats-lie.md b/.changeset/wicked-boats-lie.md deleted file mode 100644 index 8c5da92d9c..0000000000 --- a/.changeset/wicked-boats-lie.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch -'@backstage/plugin-scaffolder-backend-module-rails': patch -'@backstage/plugin-scaffolder-backend-module-yeoman': patch ---- - -Add missing API docs to scaffolder action plugins diff --git a/.changeset/young-steaks-punch.md b/.changeset/young-steaks-punch.md deleted file mode 100644 index 55997af565..0000000000 --- a/.changeset/young-steaks-punch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-plugin-api': patch ---- - -Deprecate unused ApiRef types diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 7e478953bc..c14047cd4d 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -52,6 +52,7 @@ configmaps configs const cookiecutter +cron css Datadog dataflow @@ -162,6 +163,8 @@ Monorepo monorepos msgraph msw +mutex +mutexes mysql namespace namespaced diff --git a/.github/workflows/snyk-github-issue-sync.yml b/.github/workflows/snyk-github-issue-sync.yml index 5f49be4e1a..0c50b2c505 100644 --- a/.github/workflows/snyk-github-issue-sync.yml +++ b/.github/workflows/snyk-github-issue-sync.yml @@ -7,8 +7,42 @@ on: jobs: sync: runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [14.x] + steps: - uses: actions/checkout@v2 + + # Beginning of yarn setup, keep in sync between all workflows, see ci.yml + - name: use node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + registry-url: https://registry.npmjs.org/ # Needed for auth + - name: cache all node_modules + id: cache-modules + uses: actions/cache@v2 + with: + path: '**/node_modules' + key: ${{ runner.os }}-v${{ matrix.node-version }}-node_modules-${{ hashFiles('yarn.lock', '**/package.json') }} + - name: find location of global yarn cache + id: yarn-cache + if: steps.cache-modules.outputs.cache-hit != 'true' + run: echo "::set-output name=dir::$(yarn cache dir)" + - name: cache global yarn cache + uses: actions/cache@v2 + if: steps.cache-modules.outputs.cache-hit != 'true' + with: + path: ${{ steps.yarn-cache.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + - name: yarn install + run: yarn install --frozen-lockfile + # End of yarn setup + - name: Create Snyk report uses: snyk/actions/node@master continue-on-error: true # Snyk CLI exits with error when vulnerabilities are found @@ -23,3 +57,5 @@ jobs: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} - name: Update Github issues run: yarn ts-node scripts/snyk-github-issue-sync.ts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/api/deprecations.md b/docs/api/deprecations.md new file mode 100644 index 0000000000..774c8ef03d --- /dev/null +++ b/docs/api/deprecations.md @@ -0,0 +1,56 @@ +--- +id: deprecations +title: Deprecations +description: A list of active and past deprecations +--- + +## Introduction + +This page contains extended documentation for some of the deprecations in +various parts of Backstage. It is not an exhaustive list as most deprecation +only come in the form of a changelog notice and a console warning. The +deprecations listed here are the ones that need a bit more guidance than what +fits in a console message. + +### App Theme + +`Released 2021-11-12 in @backstage/core-plugin-api v0.1.13` + +In order to provide more flexibility in what types of themes can be used and how +they are applied, the `theme` property on the `AppTheme` type is being +deprecated and replaced by a `Provider` property instead. The `Provider` +property is a React component that will be mounted at the root of the app +whenever that theme is active. This also removes the tight connection to MUI and +opens up for other type of themes, and removes the hardcoded usage of +``. + +To migrate an existing theme, remove the `theme` property and move it over to a +new `Provider` component, using `ThemeProvider` from MUI to provide the new +theme, along with ``. For example a theme that currently looks like +this: + +```tsx +const darkTheme = { + id: 'dark', + title: 'Dark Theme', + variant: 'dark', + icon: , + theme: darkTheme, +}; +``` + +Would be migrated to the following: + +```tsx +const darkTheme = { + id: 'dark', + title: 'Dark Theme', + variant: 'dark', + icon: , + Provider: ({ children }) => ( + + {children} + + ), +}; +``` diff --git a/docs/features/search/getting-started.md b/docs/features/search/getting-started.md index aa40c1e539..27219e529a 100644 --- a/docs/features/search/getting-started.md +++ b/docs/features/search/getting-started.md @@ -109,6 +109,25 @@ const routes = ( ); ``` +### Using the Search Modal + +In `Root.tsx`, add the `SidebarSearchModal` component: + +```bash +import { SidebarSearchModal } from '@backstage/plugin-search'; + +export const Root = ({ children }: PropsWithChildren<{}>) => ( + + + + + +... +``` + +For more information about using `Root.tsx`, please see +[the changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md#0315). + ## Adding Search to the Backend Add the following plugins into your backend app: diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 8f855776a0..e80e8a1842 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -172,6 +172,7 @@ This page itself can be exported as a routable extension in the plugin: ```ts export const CustomCatalogIndexPage = myPlugin.provide( createRoutableExtension({ + name: 'CustomCatalogIndexPage', component: () => import('./components/CustomCatalogPage').then(m => m.CustomCatalogPage), mountPoint: catalogRouteRef, diff --git a/docs/local-dev/cli-commands.md b/docs/local-dev/cli-commands.md index 0397ffa09c..0b14a2e136 100644 --- a/docs/local-dev/cli-commands.md +++ b/docs/local-dev/cli-commands.md @@ -512,7 +512,8 @@ Usage: backstage-cli config:check [options] Options: --package <name> Only load config schema that applies to the given package - --lax Do not require environment variables to be set + --lax Do not require environment variables to be set + --frontend Only validate the frontend configuration --config <path> Config files to load instead of app-config.yaml (default: []) -h, --help display help for command ``` diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md index 8d22c05a79..33568ac561 100644 --- a/docs/plugins/composability.md +++ b/docs/plugins/composability.md @@ -136,6 +136,7 @@ a component: ```ts export const FooPage = plugin.provide( createRoutableExtension({ + name: 'FooPage', component: () => import('./components/FooPage').then(m => m.FooPage), mountPoint: fooPageRouteRef, }), @@ -417,6 +418,7 @@ export const myPlugin = createPlugin({ export const MyPage = myPlugin.provide( createRoutableExtension({ + name: 'MyPage', component: () => import('./components/MyPage').then(m => m.MyPage), mountPoint: rootRouteRef, }), diff --git a/docs/plugins/plugin-development.md b/docs/plugins/plugin-development.md index f61fae0968..362ecc5d5a 100644 --- a/docs/plugins/plugin-development.md +++ b/docs/plugins/plugin-development.md @@ -65,6 +65,7 @@ export const examplePlugin = createPlugin({ // Each extension should also be exported from your plugin package. export const ExamplePage = examplePlugin.provide( createRoutableExtension({ + name: 'ExamplePage', // The component needs to be lazy-loaded. It's what will actually be rendered in the end. component: () => import('./components/ExampleComponent').then(m => m.ExampleComponent), diff --git a/docs/plugins/structure-of-a-plugin.md b/docs/plugins/structure-of-a-plugin.md index 83fcfbddba..9e8b67710a 100644 --- a/docs/plugins/structure-of-a-plugin.md +++ b/docs/plugins/structure-of-a-plugin.md @@ -73,6 +73,7 @@ export const examplePlugin = createPlugin({ export const ExamplePage = examplePlugin.provide( createRoutableExtension({ + name: 'ExamplePage', component: () => import('./components/ExampleComponent').then(m => m.ExampleComponent), mountPoint: rootRouteRef, diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 23cc6135c0..ec5c5d8d68 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -151,7 +151,10 @@ { "type": "subcategory", "label": "GitLab", - "ids": ["integrations/gitlab/locations"] + "ids": [ + "integrations/gitlab/locations", + "integrations/gitlab/discovery" + ] }, { "type": "subcategory", @@ -253,7 +256,8 @@ "type": "subcategory", "label": "API Reference", "ids": ["reference/index"] - } + }, + "api/deprecations" ], "Tutorials": [ "tutorials/journey", diff --git a/mkdocs.yml b/mkdocs.yml index 2f4aa4203f..8c7114abbc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -100,6 +100,7 @@ nav: - Org Data: 'integrations/github/org.md' - GitLab: - Locations: 'integrations/gitlab/locations.md' + - Discovery: 'integrations/gitlab/discovery.md' - Google GCS: - Locations: 'integrations/google-cloud-storage/locations.md' - LDAP: @@ -163,6 +164,7 @@ nav: - API Reference: - Guides: - Utility APIs: 'api/utility-apis.md' + - Deprecations: 'api/deprecations.md' - Tutorials: - Future developer journey: 'tutorials/journey.md' - Migrating away from @backstage/core: 'tutorials/migrating-away-from-core.md' diff --git a/packages/app-defaults/.eslintrc.js b/packages/app-defaults/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/packages/app-defaults/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/packages/app-defaults/README.md b/packages/app-defaults/README.md new file mode 100644 index 0000000000..15a0bd3d24 --- /dev/null +++ b/packages/app-defaults/README.md @@ -0,0 +1,17 @@ +# @backstage/app-defaults + +This package provides a default wiring of a Backstage app that avoids boilerplate when setting up a standard Backstage app. + +## Installation + +Install the package via Yarn: + +```sh +cd packages/app +yarn add @backstage/app-defaults +``` + +## Documentation + +- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +- [Backstage Documentation](https://backstage.io/docs) diff --git a/packages/app-defaults/api-report.md b/packages/app-defaults/api-report.md new file mode 100644 index 0000000000..57ea61ad8b --- /dev/null +++ b/packages/app-defaults/api-report.md @@ -0,0 +1,26 @@ +## API Report File for "@backstage/app-defaults" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AppComponents } from '@backstage/core-app-api'; +import { AppIcons } from '@backstage/core-app-api'; +import { AppOptions } from '@backstage/core-app-api'; +import { AppTheme } from '@backstage/core-plugin-api'; +import { BackstageApp } from '@backstage/core-app-api'; +import { IconComponent } from '@backstage/core-plugin-api'; + +// @public +export function createApp( + options?: Omit & OptionalAppOptions, +): BackstageApp; + +// @public +export type OptionalAppOptions = { + icons?: Partial & { + [key in string]: IconComponent; + }; + themes?: (Partial & Omit)[]; + components?: Partial; +}; +``` diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json new file mode 100644 index 0000000000..9438966b3b --- /dev/null +++ b/packages/app-defaults/package.json @@ -0,0 +1,53 @@ +{ + "name": "@backstage/app-defaults", + "description": "Provides the default wiring of a Backstage App", + "version": "0.1.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/app-defaults" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "scripts": { + "build": "backstage-cli build --outputs types,esm", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/core-components": "^0.7.3", + "@backstage/core-app-api": "^0.1.20", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "react": "^16.12.0", + "react-router-dom": "6.0.0-beta.0" + }, + "devDependencies": { + "@backstage/cli": "^0.8.2", + "@backstage/test-utils": "^0.1.21", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^11.2.5", + "@types/jest": "^26.0.7", + "@types/node": "^14.14.32", + "@types/react": "*" + }, + "files": [ + "dist" + ] +} diff --git a/packages/app-defaults/src/createApp.test.tsx b/packages/app-defaults/src/createApp.test.tsx new file mode 100644 index 0000000000..45988a4840 --- /dev/null +++ b/packages/app-defaults/src/createApp.test.tsx @@ -0,0 +1,42 @@ +/* + * 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 { screen } from '@testing-library/react'; +import { renderWithEffects } from '@backstage/test-utils'; +import React, { PropsWithChildren } from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { createApp } from './createApp'; + +describe('Optional ThemeProvider', () => { + it('should render app with user-provided ThemeProvider', async () => { + const components = { + NotFoundErrorPage: () => null, + BootErrorPage: () => null, + Progress: () => null, + Router: MemoryRouter, + ErrorBoundaryFallback: () => null, + ThemeProvider: ({ children }: PropsWithChildren<{}>) => ( +
{children}
+ ), + }; + + const App = createApp({ components }).getProvider(); + + await renderWithEffects(); + + expect(screen.getByRole('main')).toBeInTheDocument(); + }); +}); diff --git a/packages/app-defaults/src/createApp.tsx b/packages/app-defaults/src/createApp.tsx new file mode 100644 index 0000000000..d85dfa04eb --- /dev/null +++ b/packages/app-defaults/src/createApp.tsx @@ -0,0 +1,93 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { apis, components, icons, themes } from './defaults'; +import { + AppTheme, + BackstagePlugin, + IconComponent, +} from '@backstage/core-plugin-api'; +import { + AppComponents, + AppOptions, + AppIcons, + createSpecializedApp, +} from '@backstage/core-app-api'; + +/** + * Creates a new Backstage App using a default set of components, icons and themes unless + * they are explicitly provided. + * + * @public + */ +export function createApp( + options?: Omit & OptionalAppOptions, +) { + return createSpecializedApp({ + ...options, + apis: options?.apis ?? [], + bindRoutes: options?.bindRoutes, + components: { + ...components, + ...options?.components, + }, + configLoader: options?.configLoader, + defaultApis: apis, + icons: { + ...icons, + ...options?.icons, + }, + plugins: (options?.plugins as BackstagePlugin[]) ?? [], + themes: options?.themes ?? themes, + }); +} + +/** + * The set of app options that {@link createApp} will provide defaults for + * if they are not passed in explicitly. + * + * @public + */ +export type OptionalAppOptions = { + /** + * A set of icons to override the default icons with. + * + * The override is applied for each icon individually. + * + * @public + */ + icons?: Partial & { + [key in string]: IconComponent; + }; + + /** + * A set of themes that override all of the default app themes. + * + * If this option is provided none of the default themes will be used. + * + * @public + */ + themes?: (Partial & Omit)[]; // TODO: simplify once AppTheme is updated + + /** + * A set of components to override the default components with. + * + * The override is applied for each icon individually. + * + * @public + */ + components?: Partial; +}; diff --git a/packages/app-defaults/src/defaults/apis.ts b/packages/app-defaults/src/defaults/apis.ts new file mode 100644 index 0000000000..d3c76d52bd --- /dev/null +++ b/packages/app-defaults/src/defaults/apis.ts @@ -0,0 +1,274 @@ +/* + * 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 { + AlertApiForwarder, + NoOpAnalyticsApi, + ErrorApiForwarder, + ErrorAlerter, + GoogleAuth, + GithubAuth, + OAuth2, + OktaAuth, + GitlabAuth, + Auth0Auth, + MicrosoftAuth, + BitbucketAuth, + OAuthRequestManager, + WebStorage, + UrlPatternDiscovery, + SamlAuth, + OneLoginAuth, + UnhandledErrorForwarder, + AtlassianAuth, +} from '@backstage/core-app-api'; + +import { + createApiFactory, + alertApiRef, + analyticsApiRef, + errorApiRef, + discoveryApiRef, + oauthRequestApiRef, + googleAuthApiRef, + githubAuthApiRef, + oauth2ApiRef, + oktaAuthApiRef, + gitlabAuthApiRef, + auth0AuthApiRef, + microsoftAuthApiRef, + storageApiRef, + configApiRef, + samlAuthApiRef, + oneloginAuthApiRef, + oidcAuthApiRef, + bitbucketAuthApiRef, + atlassianAuthApiRef, +} from '@backstage/core-plugin-api'; + +export const apis = [ + createApiFactory({ + api: discoveryApiRef, + deps: { configApi: configApiRef }, + factory: ({ configApi }) => + UrlPatternDiscovery.compile( + `${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`, + ), + }), + createApiFactory({ + api: alertApiRef, + deps: {}, + factory: () => new AlertApiForwarder(), + }), + createApiFactory({ + api: analyticsApiRef, + deps: {}, + factory: () => new NoOpAnalyticsApi(), + }), + createApiFactory({ + api: errorApiRef, + deps: { alertApi: alertApiRef }, + factory: ({ alertApi }) => { + const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder()); + UnhandledErrorForwarder.forward(errorApi, { hidden: false }); + return errorApi; + }, + }), + createApiFactory({ + api: storageApiRef, + deps: { errorApi: errorApiRef }, + factory: ({ errorApi }) => WebStorage.create({ errorApi }), + }), + createApiFactory({ + api: oauthRequestApiRef, + deps: {}, + factory: () => new OAuthRequestManager(), + }), + createApiFactory({ + api: googleAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + GoogleAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: microsoftAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + MicrosoftAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: githubAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + GithubAuth.create({ + discoveryApi, + oauthRequestApi, + defaultScopes: ['read:user'], + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: oktaAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OktaAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: gitlabAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + GitlabAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: auth0AuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + Auth0Auth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: oauth2ApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OAuth2.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: samlAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, configApi }) => + SamlAuth.create({ + discoveryApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: oneloginAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OneLoginAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: oidcAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + OAuth2.create({ + discoveryApi, + oauthRequestApi, + provider: { + id: 'oidc', + title: 'Your Identity Provider', + icon: () => null, + }, + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: bitbucketAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => + BitbucketAuth.create({ + discoveryApi, + oauthRequestApi, + defaultScopes: ['team'], + environment: configApi.getOptionalString('auth.environment'), + }), + }), + createApiFactory({ + api: atlassianAuthApiRef, + deps: { + discoveryApi: discoveryApiRef, + oauthRequestApi: oauthRequestApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, oauthRequestApi, configApi }) => { + return AtlassianAuth.create({ + discoveryApi, + oauthRequestApi, + environment: configApi.getOptionalString('auth.environment'), + }); + }, + }), +]; diff --git a/packages/app-defaults/src/defaults/components.test.tsx b/packages/app-defaults/src/defaults/components.test.tsx new file mode 100644 index 0000000000..865eeed8a7 --- /dev/null +++ b/packages/app-defaults/src/defaults/components.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { OptionallyWrapInRouter } from './components'; + +describe('OptionallyWrapInRouter', () => { + it('should wrap with router if not yet inside a router', async () => { + render(Test); + + expect(screen.getByText('Test')).toBeInTheDocument(); + }); + + it('should not wrap with router if already inside a router', async () => { + render( + + Test + , + ); + + expect(screen.getByText('Test')).toBeInTheDocument(); + }); +}); diff --git a/packages/app-defaults/src/defaults/components.tsx b/packages/app-defaults/src/defaults/components.tsx new file mode 100644 index 0000000000..139bea4862 --- /dev/null +++ b/packages/app-defaults/src/defaults/components.tsx @@ -0,0 +1,86 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React, { ReactNode } from 'react'; +import Button from '@material-ui/core/Button'; +import { ErrorPanel, Progress, ErrorPage } from '@backstage/core-components'; +import { + MemoryRouter, + useInRouterContext, + BrowserRouter, +} from 'react-router-dom'; +import { + AppComponents, + BootErrorPageProps, + ErrorBoundaryFallbackProps, +} from '@backstage/core-plugin-api'; + +export function OptionallyWrapInRouter({ children }: { children: ReactNode }) { + if (useInRouterContext()) { + return <>{children}; + } + return {children}; +} + +const DefaultNotFoundPage = () => ( + +); + +const DefaultBootErrorPage = ({ step, error }: BootErrorPageProps) => { + let message = ''; + if (step === 'load-config') { + message = `The configuration failed to load, someone should have a look at this error: ${error.message}`; + } else if (step === 'load-chunk') { + message = `Lazy loaded chunk failed to load, try to reload the page: ${error.message}`; + } + // TODO: figure out a nicer way to handle routing on the error page, when it can be done. + return ( + + + + ); +}; + +const DefaultErrorBoundaryFallback = ({ + error, + resetError, + plugin, +}: ErrorBoundaryFallbackProps) => { + return ( + + + + ); +}; + +/** + * Creates a set of default components to pass along to {@link @backstage/core-app-api#createApp}. + * + * @public + */ +export const components: AppComponents = { + Progress, + Router: BrowserRouter, + NotFoundErrorPage: DefaultNotFoundPage, + BootErrorPage: DefaultBootErrorPage, + ErrorBoundaryFallback: DefaultErrorBoundaryFallback, +}; diff --git a/packages/core-app-api/src/app/icons.tsx b/packages/app-defaults/src/defaults/icons.tsx similarity index 63% rename from packages/core-app-api/src/app/icons.tsx rename to packages/app-defaults/src/defaults/icons.tsx index b234a292b1..c4f2d2b7e4 100644 --- a/packages/core-app-api/src/app/icons.tsx +++ b/packages/app-defaults/src/defaults/icons.tsx @@ -35,52 +35,27 @@ import MuiPeopleIcon from '@material-ui/icons/People'; import MuiPersonIcon from '@material-ui/icons/Person'; import MuiWarningIcon from '@material-ui/icons/Warning'; -type AppIconsKey = - | 'brokenImage' - | 'catalog' - | 'scaffolder' - | 'techdocs' - | 'search' - | 'chat' - | 'dashboard' - | 'docs' - | 'email' - | 'github' - | 'group' - | 'help' - | 'kind:api' - | 'kind:component' - | 'kind:domain' - | 'kind:group' - | 'kind:location' - | 'kind:system' - | 'kind:user' - | 'user' - | 'warning'; - -export type AppIcons = { [key in AppIconsKey]: IconComponent }; - -export const defaultAppIcons: AppIcons = { - brokenImage: MuiBrokenImageIcon, +export const icons = { + brokenImage: MuiBrokenImageIcon as IconComponent, // To be confirmed: see https://github.com/backstage/backstage/issues/4970 - catalog: MuiMenuBookIcon, - scaffolder: MuiCreateNewFolderIcon, - techdocs: MuiSubjectIcon, - search: MuiSearchIcon, - chat: MuiChatIcon, - dashboard: MuiDashboardIcon, - docs: MuiDocsIcon, - email: MuiEmailIcon, - github: MuiGitHubIcon, - group: MuiPeopleIcon, - help: MuiHelpIcon, - 'kind:api': MuiExtensionIcon, - 'kind:component': MuiMemoryIcon, - 'kind:domain': MuiApartmentIcon, - 'kind:group': MuiPeopleIcon, - 'kind:location': MuiLocationOnIcon, - 'kind:system': MuiCategoryIcon, - 'kind:user': MuiPersonIcon, - user: MuiPersonIcon, - warning: MuiWarningIcon, + catalog: MuiMenuBookIcon as IconComponent, + scaffolder: MuiCreateNewFolderIcon as IconComponent, + techdocs: MuiSubjectIcon as IconComponent, + search: MuiSearchIcon as IconComponent, + chat: MuiChatIcon as IconComponent, + dashboard: MuiDashboardIcon as IconComponent, + docs: MuiDocsIcon as IconComponent, + email: MuiEmailIcon as IconComponent, + github: MuiGitHubIcon as IconComponent, + group: MuiPeopleIcon as IconComponent, + help: MuiHelpIcon as IconComponent, + 'kind:api': MuiExtensionIcon as IconComponent, + 'kind:component': MuiMemoryIcon as IconComponent, + 'kind:domain': MuiApartmentIcon as IconComponent, + 'kind:group': MuiPeopleIcon as IconComponent, + 'kind:location': MuiLocationOnIcon as IconComponent, + 'kind:system': MuiCategoryIcon as IconComponent, + 'kind:user': MuiPersonIcon as IconComponent, + user: MuiPersonIcon as IconComponent, + warning: MuiWarningIcon as IconComponent, }; diff --git a/packages/app-defaults/src/defaults/index.ts b/packages/app-defaults/src/defaults/index.ts new file mode 100644 index 0000000000..d9ff18bc8c --- /dev/null +++ b/packages/app-defaults/src/defaults/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { apis } from './apis'; +export { components } from './components'; +export { icons } from './icons'; +export { themes } from './themes'; diff --git a/packages/app-defaults/src/defaults/themes.tsx b/packages/app-defaults/src/defaults/themes.tsx new file mode 100644 index 0000000000..90a4a0f47f --- /dev/null +++ b/packages/app-defaults/src/defaults/themes.tsx @@ -0,0 +1,50 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { darkTheme, lightTheme } from '@backstage/theme'; +import DarkIcon from '@material-ui/icons/Brightness2'; +import LightIcon from '@material-ui/icons/WbSunny'; +import { ThemeProvider } from '@material-ui/core/styles'; +import CssBaseline from '@material-ui/core/CssBaseline'; +import { AppTheme } from '@backstage/core-plugin-api'; + +export const themes: AppTheme[] = [ + { + id: 'light', + title: 'Light Theme', + variant: 'light', + icon: , + theme: lightTheme, + Provider: ({ children }) => ( + + {children} + + ), + }, + { + id: 'dark', + title: 'Dark Theme', + variant: 'dark', + icon: , + theme: darkTheme, + Provider: ({ children }) => ( + + {children} + + ), + }, +]; diff --git a/packages/app-defaults/src/index.ts b/packages/app-defaults/src/index.ts new file mode 100644 index 0000000000..16a6a693a5 --- /dev/null +++ b/packages/app-defaults/src/index.ts @@ -0,0 +1,24 @@ +/* + * 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. + */ + +/** + * Provides the default wiring of a Backstage App + * + * @packageDocumentation + */ + +export { createApp } from './createApp'; +export type { OptionalAppOptions } from './createApp'; diff --git a/packages/app-defaults/src/setupTests.ts b/packages/app-defaults/src/setupTests.ts new file mode 100644 index 0000000000..963c0f188b --- /dev/null +++ b/packages/app-defaults/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * 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 '@testing-library/jest-dom'; diff --git a/packages/app/package.json b/packages/app/package.json index 833e146d45..9a1fb108f6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -4,6 +4,7 @@ "private": true, "bundled": true, "dependencies": { + "@backstage/app-defaults": "^0.1.0", "@backstage/catalog-model": "^0.9.5", "@backstage/cli": "^0.8.0", "@backstage/core-app-api": "^0.1.18", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 109ae7922e..401665b62e 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -26,7 +26,8 @@ import { RELATION_PART_OF, RELATION_PROVIDES_API, } from '@backstage/catalog-model'; -import { createApp, FlatRoutes } from '@backstage/core-app-api'; +import { createApp } from '@backstage/app-defaults'; +import { FlatRoutes } from '@backstage/core-app-api'; import { AlertDisplay, OAuthRequestDialog, @@ -93,7 +94,6 @@ const app = createApp({ // Custom icon example alert: AlarmIcon, }, - components: { SignInPage: props => { return ( diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 7a666a020a..f365310595 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -29,7 +29,7 @@ import LogoIcon from './LogoIcon'; import { NavLink } from 'react-router-dom'; import { GraphiQLIcon } from '@backstage/plugin-graphiql'; import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; -import { SidebarSearch } from '@backstage/plugin-search'; +import { SidebarSearchModal } from '@backstage/plugin-search'; import { Shortcuts } from '@backstage/plugin-shortcuts'; import { Sidebar, @@ -79,7 +79,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( - + {/* Global nav, not org-specific */} diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index f0368b55d4..f3464917aa 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-common +## 0.9.9 + +### Patch Changes + +- 8c4cad0bf2: AWSS3UrlReader now throws a `NotModifiedError` (exported from @backstage/backend-common) when s3 returns a 304 response. +- 0611f3b3e2: Reading app config from a remote server +- Updated dependencies + - @backstage/config-loader@0.7.2 + ## 0.9.8 ### Patch Changes diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index a19447034e..7242c32a84 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -374,6 +374,9 @@ export class GitlabUrlReader implements UrlReader { export { isChildPath }; +// @public +export function isDatabaseConflictError(e: unknown): boolean; + // @public export function loadBackendConfig(options: { logger: Logger_2; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index a174862b4a..03ae1fbe62 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.9.8", + "version": "0.9.9", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -31,7 +31,7 @@ "dependencies": { "@backstage/cli-common": "^0.1.5", "@backstage/config": "^0.1.11", - "@backstage/config-loader": "^0.7.1", + "@backstage/config-loader": "^0.7.2", "@backstage/errors": "^0.1.4", "@backstage/integration": "^0.6.9", "@backstage/types": "^0.1.1", @@ -79,8 +79,8 @@ } }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/test-utils": "^0.1.20", + "@backstage/cli": "^0.8.2", + "@backstage/test-utils": "^0.1.21", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", "@types/concat-stream": "^1.6.0", diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index c0511c6b78..f8cdab1413 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -13,14 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Knex } from 'knex'; import { omit } from 'lodash'; import { Config, ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { createDatabaseClient, - ensureDatabaseExists, createNameOverride, + ensureDatabaseExists, normalizeConnection, } from './connection'; import { PluginDatabaseManager } from './types'; @@ -165,7 +166,7 @@ export class DatabaseManager { ); return { - // include base connection if client type has not been overriden + // include base connection if client type has not been overridden ...(overridden ? {} : baseConnection), ...connection, }; diff --git a/packages/backend-common/src/database/index.ts b/packages/backend-common/src/database/index.ts index 7fcb8bf930..2a820f7367 100644 --- a/packages/backend-common/src/database/index.ts +++ b/packages/backend-common/src/database/index.ts @@ -28,3 +28,4 @@ export { } from './connection'; export type { PluginDatabaseManager } from './types'; +export { isDatabaseConflictError } from './util'; diff --git a/packages/backend-common/src/database/util.ts b/packages/backend-common/src/database/util.ts new file mode 100644 index 0000000000..607554f294 --- /dev/null +++ b/packages/backend-common/src/database/util.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Tries to deduce whether a thrown error is a database conflict. + * + * @public + * @param e - A thrown error + * @returns True if the error looks like it was a conflict error thrown by a + * known database engine + */ +export function isDatabaseConflictError(e: unknown) { + const message = (e as any)?.message; + + return ( + typeof message === 'string' && + (/SQLITE_CONSTRAINT: UNIQUE/.test(message) || + /unique constraint/.test(message)) + ); +} diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index ed16c9c9a7..885baba54b 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -174,27 +174,27 @@ export class ServiceBuilderImpl implements ServiceBuilder { const server: http.Server = httpsSettings ? await createHttpsServer(app, httpsSettings, logger) : createHttpServer(app, logger); + const stoppableServer = stoppable(server, 0); + + useHotCleanup(this.module, () => + stoppableServer.stop((e: any) => { + if (e) console.error(e); + }), + ); return new Promise((resolve, reject) => { - app.on('error', e => { - logger.error(`Failed to start up on port ${port}, ${e}`); + function handleStartupError(e: unknown) { + server.close(); reject(e); + } + + server.on('error', handleStartupError); + + server.listen(port, host, () => { + server.off('error', handleStartupError); + logger.info(`Listening on ${host}:${port}`); + resolve(stoppableServer); }); - - const stoppableServer = stoppable( - server.listen(port, host, () => { - logger.info(`Listening on ${host}:${port}`); - }), - 0, - ); - - useHotCleanup(this.module, () => - stoppableServer.stop((e: any) => { - if (e) console.error(e); - }), - ); - - resolve(stoppableServer); }); } diff --git a/packages/backend-tasks/.eslintrc.js b/packages/backend-tasks/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/packages/backend-tasks/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/packages/backend-tasks/README.md b/packages/backend-tasks/README.md new file mode 100644 index 0000000000..d3c361c02c --- /dev/null +++ b/packages/backend-tasks/README.md @@ -0,0 +1,35 @@ +# @backstage/backend-tasks + +Common distributed task management for Backstage backends. + +## Usage + +Add the library to your backend package: + +```sh +# From your Backstage root directory +cd packages/backend +yarn add @backstage/backend-tasks +``` + +then make use of its facilities as necessary: + +```typescript +import { TaskScheduler } from '@backstage/backend-tasks'; +import { Duration } from 'luxon'; + +const scheduler = TaskScheduler.fromConfig(rootConfig).forPlugin('my-plugin'); + +await scheduler.scheduleTask({ + id: 'refresh-things', + frequency: Duration.fromObject({ minutes: 10 }), + fn: async () => { + await entityProvider.run(); + }, +}); +``` + +## Documentation + +- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) +- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md new file mode 100644 index 0000000000..eeb6412840 --- /dev/null +++ b/packages/backend-tasks/api-report.md @@ -0,0 +1,45 @@ +## API Report File for "@backstage/backend-tasks" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AbortSignal as AbortSignal_2 } from 'node-abort-controller'; +import { Config } from '@backstage/config'; +import { DatabaseManager } from '@backstage/backend-common'; +import { Duration } from 'luxon'; +import { Logger as Logger_2 } from 'winston'; + +// @public +export interface PluginTaskScheduler { + scheduleTask(task: TaskDefinition): Promise; +} + +// @public +export interface TaskDefinition { + fn: TaskFunction; + frequency: Duration; + id: string; + initialDelay?: Duration; + signal?: AbortSignal_2; + timeout: Duration; +} + +// @public +export type TaskFunction = + | ((abortSignal: AbortSignal_2) => void | Promise) + | (() => void | Promise); + +// @public +export class TaskScheduler { + constructor(databaseManager: DatabaseManager, logger: Logger_2); + forPlugin(pluginId: string): PluginTaskScheduler; + // (undocumented) + static fromConfig( + config: Config, + options?: { + databaseManager?: DatabaseManager; + logger?: Logger_2; + }, + ): TaskScheduler; +} +``` diff --git a/packages/backend-tasks/migrations/20210928160613_init.js b/packages/backend-tasks/migrations/20210928160613_init.js new file mode 100644 index 0000000000..21d80ae24c --- /dev/null +++ b/packages/backend-tasks/migrations/20210928160613_init.js @@ -0,0 +1,64 @@ +/* + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + // + // tasks + // + await knex.schema.createTable('backstage_backend_tasks__tasks', table => { + table.comment('Tasks used for scheduling work on multiple workers'); + table + .text('id') + .primary() + .notNullable() + .comment('The unique ID of this particular task'); + table + .text('settings_json') + .notNullable() + .comment('JSON serialized object with properties for this task'); + table + .dateTime('next_run_start_at') + .notNullable() + .comment('The next time that the task should be started'); + table + .text('current_run_ticket') + .nullable() + .comment('A unique ticket for the current task run'); + table + .dateTime('current_run_started_at') + .nullable() + .comment('The time that the current task run started'); + table + .dateTime('current_run_expires_at') + .nullable() + .comment('The time that the current task run will time out'); + }); +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + // + // tasks + // + await knex.schema.dropTable('backstage_backend_tasks__tasks'); +}; diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json new file mode 100644 index 0000000000..10db5cfaac --- /dev/null +++ b/packages/backend-tasks/package.json @@ -0,0 +1,55 @@ +{ + "name": "@backstage/backend-tasks", + "description": "Common distributed task management library for Backstage backends", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "packages/backend-tasks" + }, + "keywords": [ + "backstage" + ], + "license": "Apache-2.0", + "scripts": { + "build": "backstage-cli build --outputs cjs,types", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/backend-common": "^0.9.8", + "@backstage/config": "^0.1.11", + "@backstage/errors": "^0.1.4", + "@backstage/types": "^0.1.1", + "@types/luxon": "^2.0.4", + "knex": "^0.95.1", + "lodash": "^4.17.21", + "luxon": "^2.0.2", + "node-abort-controller": "^3.0.1", + "uuid": "^8.0.0", + "winston": "^3.2.1", + "zod": "^3.9.5" + }, + "devDependencies": { + "@backstage/backend-test-utils": "^0.1.8", + "@backstage/cli": "^0.8.1", + "jest": "^26.0.1", + "wait-for-expect": "^3.0.2" + }, + "files": [ + "dist", + "migrations/**/*.{js,d.ts}" + ] +} diff --git a/packages/backend-tasks/src/database/migrateBackendTasks.ts b/packages/backend-tasks/src/database/migrateBackendTasks.ts new file mode 100644 index 0000000000..cc0f4349ac --- /dev/null +++ b/packages/backend-tasks/src/database/migrateBackendTasks.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { resolvePackagePath } from '@backstage/backend-common'; +import { Knex } from 'knex'; +import { DB_MIGRATIONS_TABLE } from './tables'; + +const migrationsDir = resolvePackagePath( + '@backstage/backend-tasks', + 'migrations', +); + +export async function migrateBackendTasks(knex: Knex): Promise { + await knex.migrate.latest({ + directory: migrationsDir, + tableName: DB_MIGRATIONS_TABLE, + }); +} diff --git a/packages/backend-tasks/src/database/tables.ts b/packages/backend-tasks/src/database/tables.ts new file mode 100644 index 0000000000..63aad6e42a --- /dev/null +++ b/packages/backend-tasks/src/database/tables.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const DB_MIGRATIONS_TABLE = 'backstage_backend_tasks__knex_migrations'; +export const DB_TASKS_TABLE = 'backstage_backend_tasks__tasks'; + +export type DbTasksRow = { + id: string; + settings_json: string; + next_run_start_at: Date; + current_run_ticket?: string; + current_run_started_at?: Date | string; + current_run_expires_at?: Date | string; +}; diff --git a/packages/backend-tasks/src/index.ts b/packages/backend-tasks/src/index.ts new file mode 100644 index 0000000000..dd75aca68c --- /dev/null +++ b/packages/backend-tasks/src/index.ts @@ -0,0 +1,23 @@ +/* + * 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. + */ + +/** + * Common distributed task management library for Backstage backends + * + * @packageDocumentation + */ + +export * from './tasks'; diff --git a/packages/backend-tasks/src/setupTests.ts b/packages/backend-tasks/src/setupTests.ts new file mode 100644 index 0000000000..d3232290a7 --- /dev/null +++ b/packages/backend-tasks/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * 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. + */ + +export {}; diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts new file mode 100644 index 0000000000..e387b85413 --- /dev/null +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; +import { Duration } from 'luxon'; +import waitForExpect from 'wait-for-expect'; +import { migrateBackendTasks } from '../database/migrateBackendTasks'; +import { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl'; + +describe('PluginTaskManagerImpl', () => { + const databases = TestDatabases.create({ + ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], + }); + + async function init(databaseId: TestDatabaseId) { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + const manager = new PluginTaskSchedulerImpl( + async () => knex, + getVoidLogger(), + ); + return { knex, manager }; + } + + // This is just to test the wrapper code; most of the actual tests are in + // TaskWorker.test.ts + describe('scheduleTask', () => { + it.each(databases.eachSupportedId())( + 'can run the happy path, %p', + async databaseId => { + const { manager } = await init(databaseId); + + const fn = jest.fn(); + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromMillis(5000), + fn, + }); + + await waitForExpect(() => { + expect(fn).toBeCalled(); + }); + }, + 60_000, + ); + }); +}); diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts new file mode 100644 index 0000000000..93975bc327 --- /dev/null +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts @@ -0,0 +1,50 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Knex } from 'knex'; +import { Logger } from 'winston'; +import { TaskWorker } from './TaskWorker'; +import { PluginTaskScheduler, TaskDefinition } from './types'; +import { validateId } from './util'; + +/** + * Implements the actual task management. + */ +export class PluginTaskSchedulerImpl implements PluginTaskScheduler { + constructor( + private readonly databaseFactory: () => Promise, + private readonly logger: Logger, + ) {} + + async scheduleTask(task: TaskDefinition): Promise { + validateId(task.id); + + const knex = await this.databaseFactory(); + + const worker = new TaskWorker(task.id, task.fn, knex, this.logger); + await worker.start( + { + version: 1, + initialDelayDuration: task.initialDelay?.toISO(), + recurringAtMostEveryDuration: task.frequency.toISO(), + timeoutAfterDuration: task.timeout.toISO(), + }, + { + signal: task.signal, + }, + ); + } +} diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts new file mode 100644 index 0000000000..8b90afff42 --- /dev/null +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts @@ -0,0 +1,83 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Knex } from 'knex'; +import { Duration } from 'luxon'; +import { AbortSignal } from 'node-abort-controller'; +import { Logger } from 'winston'; +import { DbTasksRow, DB_TASKS_TABLE } from '../database/tables'; +import { sleep } from './util'; + +/** + * Makes sure to auto-expire and clean up things that time out or for other + * reasons should not be left lingering. + */ +export class PluginTaskSchedulerJanitor { + private readonly knex: Knex; + private readonly waitBetweenRuns: Duration; + private readonly logger: Logger; + + constructor(options: { + knex: Knex; + waitBetweenRuns: Duration; + logger: Logger; + }) { + this.knex = options.knex; + this.waitBetweenRuns = options.waitBetweenRuns; + this.logger = options.logger; + } + + async start(abortSignal?: AbortSignal) { + while (!abortSignal?.aborted) { + try { + await this.runOnce(); + } catch (e) { + this.logger.warn(`Error while performing janitorial tasks, ${e}`); + } + + await sleep(this.waitBetweenRuns, abortSignal); + } + } + + private async runOnce() { + // SQLite currently (Oct 1 2021) returns a number for returning() + // statements, effectively ignoring them and instead returning the outcome + // of the delete() - and knex also emits a warning about that fact, which + // is why we avoid that entirely for the sqlite3 driver. + // https://github.com/knex/knex/issues/4370 + // https://github.com/mapbox/node-sqlite3/issues/1453 + + const dbNull = this.knex.raw('null'); + + const tasksQuery = this.knex(DB_TASKS_TABLE) + .where('current_run_expires_at', '<', this.knex.fn.now()) + .update({ + current_run_ticket: dbNull, + current_run_started_at: dbNull, + current_run_expires_at: dbNull, + }); + + if (this.knex.client.config.client === 'sqlite3') { + const tasks = await tasksQuery; + this.logger.warn(`${tasks} tasks timed out and were lost`); + } else { + const tasks = await tasksQuery.returning(['id']); + for (const { id } of tasks) { + this.logger.warn(`Task timed out and was lost: ${id}`); + } + } + } +} diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.test.ts b/packages/backend-tasks/src/tasks/TaskScheduler.test.ts new file mode 100644 index 0000000000..ce8e797503 --- /dev/null +++ b/packages/backend-tasks/src/tasks/TaskScheduler.test.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DatabaseManager, getVoidLogger } from '@backstage/backend-common'; +import { TestDatabaseId, TestDatabases } from '@backstage/backend-test-utils'; +import { Duration } from 'luxon'; +import { TaskScheduler } from './TaskScheduler'; +import waitForExpect from 'wait-for-expect'; + +describe('TaskScheduler', () => { + const logger = getVoidLogger(); + const databases = TestDatabases.create({ + ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], + }); + + async function createDatabase( + databaseId: TestDatabaseId, + ): Promise { + const knex = await databases.init(databaseId); + const databaseManager: Partial = { + forPlugin: () => ({ + getClient: async () => knex, + }), + }; + return databaseManager as DatabaseManager; + } + + it.each(databases.eachSupportedId())( + 'can return a working plugin impl, %p', + async databaseId => { + const database = await createDatabase(databaseId); + const manager = new TaskScheduler(database, logger).forPlugin('test'); + const fn = jest.fn(); + + await manager.scheduleTask({ + id: 'task1', + timeout: Duration.fromMillis(5000), + frequency: Duration.fromMillis(5000), + fn, + }); + + await waitForExpect(() => { + expect(fn).toBeCalled(); + }); + }, + 60_000, + ); +}); diff --git a/packages/backend-tasks/src/tasks/TaskScheduler.ts b/packages/backend-tasks/src/tasks/TaskScheduler.ts new file mode 100644 index 0000000000..f1668a6691 --- /dev/null +++ b/packages/backend-tasks/src/tasks/TaskScheduler.ts @@ -0,0 +1,80 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DatabaseManager, getRootLogger } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; +import { once } from 'lodash'; +import { Duration } from 'luxon'; +import { Logger } from 'winston'; +import { migrateBackendTasks } from '../database/migrateBackendTasks'; +import { PluginTaskSchedulerImpl } from './PluginTaskSchedulerImpl'; +import { PluginTaskSchedulerJanitor } from './PluginTaskSchedulerJanitor'; +import { PluginTaskScheduler } from './types'; + +/** + * Deals with the scheduling of distributed tasks. + * + * @public + */ +export class TaskScheduler { + static fromConfig( + config: Config, + options?: { + databaseManager?: DatabaseManager; + logger?: Logger; + }, + ): TaskScheduler { + const databaseManager = + options?.databaseManager ?? DatabaseManager.fromConfig(config); + const logger = (options?.logger || getRootLogger()).child({ + type: 'taskManager', + }); + return new TaskScheduler(databaseManager, logger); + } + + constructor( + private readonly databaseManager: DatabaseManager, + private readonly logger: Logger, + ) {} + + /** + * Instantiates a task manager instance for the given plugin. + * + * @param pluginId - The unique ID of the plugin, for example "catalog" + * @returns A {@link PluginTaskScheduler} instance + */ + forPlugin(pluginId: string): PluginTaskScheduler { + const databaseFactory = once(async () => { + const knex = await this.databaseManager.forPlugin(pluginId).getClient(); + + await migrateBackendTasks(knex); + + const janitor = new PluginTaskSchedulerJanitor({ + knex, + waitBetweenRuns: Duration.fromObject({ minutes: 1 }), + logger: this.logger, + }); + janitor.start(); + + return knex; + }); + + return new PluginTaskSchedulerImpl( + databaseFactory, + this.logger.child({ plugin: pluginId }), + ); + } +} diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts new file mode 100644 index 0000000000..2f977ca639 --- /dev/null +++ b/packages/backend-tasks/src/tasks/TaskWorker.test.ts @@ -0,0 +1,245 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { TestDatabases } from '@backstage/backend-test-utils'; +import { Duration } from 'luxon'; +import waitForExpect from 'wait-for-expect'; +import { migrateBackendTasks } from '../database/migrateBackendTasks'; +import { DbTasksRow, DB_TASKS_TABLE } from '../database/tables'; +import { TaskWorker } from './TaskWorker'; +import { TaskSettingsV1 } from './types'; + +describe('TaskWorker', () => { + const logger = getVoidLogger(); + const databases = TestDatabases.create({ + ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it.each(databases.eachSupportedId())( + 'goes through the expected states, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + const settings: TaskSettingsV1 = { + version: 1, + initialDelayDuration: Duration.fromMillis(1000).toISO(), + recurringAtMostEveryDuration: Duration.fromMillis(2000).toISO(), + timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + }; + + const worker = new TaskWorker('task1', fn, knex, logger); + await worker.persistTask(settings); + + let row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }), + ); + expect(JSON.parse(row.settings_json)).toEqual({ + version: 1, + initialDelayDuration: 'PT1S', + recurringAtMostEveryDuration: 'PT2S', + timeoutAfterDuration: 'PT60S', + }); + + await expect(worker.findReadyTask()).resolves.toEqual({ + result: 'not-ready-yet', + }); + + waitForExpect(async () => { + await expect(worker.findReadyTask()).resolves.toEqual({ + result: 'ready', + }); + }); + + row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }), + ); + + await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true); + + row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: 'ticket', + current_run_started_at: expect.anything(), + current_run_expires_at: expect.anything(), + }), + ); + + await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe( + true, + ); + + row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: null, + current_run_started_at: null, + current_run_expires_at: null, + }), + ); + }, + 60_000, + ); + + it.each(databases.eachSupportedId())( + 'runs tasks more than once even when the task throws, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn().mockRejectedValue(new Error('failed')); + const settings: TaskSettingsV1 = { + version: 1, + initialDelayDuration: undefined, + recurringAtMostEveryDuration: Duration.fromMillis(0).toISO(), + timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + }; + + const worker = new TaskWorker('task1', fn, knex, logger); + worker.start(settings); + + waitForExpect(() => { + expect(fn).toBeCalledTimes(3); + }); + }, + 60_000, + ); + + it.each(databases.eachSupportedId())( + 'does not clobber ticket lock when stolen, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn( + async () => new Promise(resolve => setTimeout(resolve, 50)), + ); + const settings: TaskSettingsV1 = { + version: 1, + recurringAtMostEveryDuration: Duration.fromMillis(0).toISO(), + timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + }; + + const worker = new TaskWorker('task1', fn, knex, logger); + await worker.persistTask(settings); + await expect(worker.findReadyTask()).resolves.toEqual({ + result: 'ready', + settings, + }); + await expect(worker.tryClaimTask('ticket', settings)).resolves.toBe(true); + + let row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: 'ticket', + current_run_started_at: expect.anything(), + current_run_expires_at: expect.anything(), + }), + ); + + await knex(DB_TASKS_TABLE) + .where('id', '=', 'task1') + .update({ current_run_ticket: 'stolen' }); + + await expect(worker.tryReleaseTask('ticket', settings)).resolves.toBe( + false, + ); + + row = (await knex(DB_TASKS_TABLE))[0]; + expect(row).toEqual( + expect.objectContaining({ + id: 'task1', + current_run_ticket: 'stolen', + current_run_started_at: expect.anything(), + current_run_expires_at: expect.anything(), + }), + ); + }, + 60_000, + ); + + it.each(databases.eachSupportedId())( + 'gracefully handles a disappeared task row, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + const fn = jest.fn(async () => {}); + const settings: TaskSettingsV1 = { + version: 1, + recurringAtMostEveryDuration: Duration.fromMillis(0).toISO(), + timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + }; + + const worker1 = new TaskWorker('task1', fn, knex, logger); + await worker1.persistTask(settings); + await knex(DB_TASKS_TABLE).where('id', '=', 'task1').delete(); + await expect(worker1.findReadyTask()).resolves.toEqual({ + result: 'abort', + }); + + const worker2 = new TaskWorker('task2', fn, knex, logger); + await worker2.persistTask(settings); + await expect(worker2.findReadyTask()).resolves.toEqual({ + result: 'ready', + settings, + }); + await knex(DB_TASKS_TABLE).where('id', '=', 'task2').delete(); + await expect(worker2.tryClaimTask('ticket', settings)).resolves.toBe( + false, + ); + + const worker3 = new TaskWorker('task3', fn, knex, logger); + await worker3.persistTask(settings); + await expect(worker3.findReadyTask()).resolves.toEqual({ + result: 'ready', + settings, + }); + await expect(worker3.tryClaimTask('ticket', settings)).resolves.toBe( + true, + ); + await knex(DB_TASKS_TABLE).where('id', '=', 'task3').delete(); + await expect(worker3.tryReleaseTask('ticket', settings)).resolves.toBe( + false, + ); + }, + 60_000, + ); +}); diff --git a/packages/backend-tasks/src/tasks/TaskWorker.ts b/packages/backend-tasks/src/tasks/TaskWorker.ts new file mode 100644 index 0000000000..991d97b956 --- /dev/null +++ b/packages/backend-tasks/src/tasks/TaskWorker.ts @@ -0,0 +1,249 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Knex } from 'knex'; +import { Duration } from 'luxon'; +import { AbortSignal } from 'node-abort-controller'; +import { v4 as uuid } from 'uuid'; +import { Logger } from 'winston'; +import { DbTasksRow, DB_TASKS_TABLE } from '../database/tables'; +import { TaskFunction, TaskSettingsV1, taskSettingsV1Schema } from './types'; +import { delegateAbortController, nowPlus, sleep } from './util'; + +const WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 }); + +/** + * Performs the actual work of a task. + * + * @private + */ +export class TaskWorker { + private readonly taskId: string; + private readonly fn: TaskFunction; + private readonly knex: Knex; + private readonly logger: Logger; + + constructor(taskId: string, fn: TaskFunction, knex: Knex, logger: Logger) { + this.taskId = taskId; + this.fn = fn; + this.knex = knex; + this.logger = logger; + } + + async start(settings: TaskSettingsV1, options?: { signal?: AbortSignal }) { + try { + await this.persistTask(settings); + } catch (e) { + throw new Error(`Failed to persist task, ${e}`); + } + + this.logger.info( + `Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`, + ); + + (async () => { + try { + while (!options?.signal?.aborted) { + const runResult = await this.runOnce(options?.signal); + if (runResult.result === 'abort') { + break; + } + + await sleep(WORK_CHECK_FREQUENCY, options?.signal); + } + this.logger.info(`Task worker finished: ${this.taskId}`); + } catch (e) { + this.logger.warn(`Task worker failed unexpectedly, ${e}`); + } + })(); + } + + /** + * Makes a single attempt at running the task to completion, if ready. + * + * @returns The outcome of the attempt + */ + async runOnce( + signal?: AbortSignal, + ): Promise< + | { result: 'not-ready-yet' } + | { result: 'abort' } + | { result: 'failed' } + | { result: 'completed' } + > { + const findResult = await this.findReadyTask(); + if ( + findResult.result === 'not-ready-yet' || + findResult.result === 'abort' + ) { + return findResult; + } + + const taskSettings = findResult.settings; + const ticket = uuid(); + + const claimed = await this.tryClaimTask(ticket, taskSettings); + if (!claimed) { + return { result: 'not-ready-yet' }; + } + + // Abort the task execution either if the worker is stopped, or if the + // task timeout is hit + const taskAbortController = delegateAbortController(signal); + const timeoutHandle = setTimeout(() => { + taskAbortController.abort(); + }, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds')); + + try { + await this.fn(taskAbortController.signal); + } catch (e) { + await this.tryReleaseTask(ticket, taskSettings); + return { result: 'failed' }; + } finally { + clearTimeout(timeoutHandle); + } + + await this.tryReleaseTask(ticket, taskSettings); + return { result: 'completed' }; + } + + /** + * Perform the initial store of the task info + */ + async persistTask(settings: TaskSettingsV1) { + // Perform an initial parse to ensure that we will definitely be able to + // read it back again. + taskSettingsV1Schema.parse(settings); + + const settingsJson = JSON.stringify(settings); + const startAt = settings.initialDelayDuration + ? nowPlus(Duration.fromISO(settings.initialDelayDuration), this.knex) + : this.knex.fn.now(); + + // It's OK if the task already exists; if it does, just replace its + // settings with the new value and start the loop as usual. + await this.knex(DB_TASKS_TABLE) + .insert({ + id: this.taskId, + settings_json: settingsJson, + next_run_start_at: startAt, + }) + .onConflict('id') + .merge(['settings_json']); + } + + /** + * Check if the task is ready to run + */ + async findReadyTask(): Promise< + | { result: 'not-ready-yet' } + | { result: 'abort' } + | { result: 'ready'; settings: TaskSettingsV1 } + > { + const [row] = await this.knex(DB_TASKS_TABLE) + .where('id', '=', this.taskId) + .select({ + settingsJson: 'settings_json', + ready: this.knex.raw( + ` + CASE + WHEN next_run_start_at <= ? AND current_run_ticket IS NULL THEN TRUE + ELSE FALSE + END`, + [this.knex.fn.now()], + ), + }); + + if (!row) { + this.logger.info( + 'No longer able to find task; aborting and assuming that it has been unregistered or expired', + ); + return { result: 'abort' }; + } else if (!row.ready) { + return { result: 'not-ready-yet' }; + } + + try { + const settings = taskSettingsV1Schema.parse(JSON.parse(row.settingsJson)); + return { result: 'ready', settings }; + } catch (e) { + this.logger.info( + `Task "${this.taskId}" is no longer able to parse task settings; aborting and assuming that a ` + + `newer version of the task has been issued and being handled by other workers, ${e}`, + ); + return { result: 'abort' }; + } + } + + /** + * Attempts to claim a task that's ready for execution, on this worker's + * behalf. We should not attempt to perform the work unless the claim really + * goes through. + * + * @param ticket - A globally unique string that changes for each invocation + * @param settings - The settings of the task to claim + * @returns True if it was successfully claimed + */ + async tryClaimTask( + ticket: string, + settings: TaskSettingsV1, + ): Promise { + const startedAt = this.knex.fn.now(); + const expiresAt = settings.timeoutAfterDuration + ? nowPlus(Duration.fromISO(settings.timeoutAfterDuration), this.knex) + : this.knex.raw('null'); + + const rows = await this.knex(DB_TASKS_TABLE) + .where('id', '=', this.taskId) + .whereNull('current_run_ticket') + .update({ + current_run_ticket: ticket, + current_run_started_at: startedAt, + current_run_expires_at: expiresAt, + }); + + return rows === 1; + } + + async tryReleaseTask( + ticket: string, + settings: TaskSettingsV1, + ): Promise { + const { recurringAtMostEveryDuration } = settings; + + // We make an effort to keep the datetime calculations in the database + // layer, making sure to not have to perform conversions back and forth and + // leaning on the database as a central clock source + const dbNull = this.knex.raw('null'); + const dt = Duration.fromISO(recurringAtMostEveryDuration).as('seconds'); + const nextRun = + this.knex.client.config.client === 'sqlite3' + ? this.knex.raw('datetime(next_run_start_at, ?)', [`+${dt} seconds`]) + : this.knex.raw(`next_run_start_at + interval '${dt} seconds'`); + + const rows = await this.knex(DB_TASKS_TABLE) + .where('id', '=', this.taskId) + .where('current_run_ticket', '=', ticket) + .update({ + next_run_start_at: nextRun, + current_run_ticket: dbNull, + current_run_started_at: dbNull, + current_run_expires_at: dbNull, + }); + + return rows === 1; + } +} diff --git a/packages/backend-tasks/src/tasks/index.ts b/packages/backend-tasks/src/tasks/index.ts new file mode 100644 index 0000000000..9e0a06f71c --- /dev/null +++ b/packages/backend-tasks/src/tasks/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { TaskScheduler } from './TaskScheduler'; +export type { + PluginTaskScheduler, + TaskDefinition, + TaskFunction, +} from './types'; diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts new file mode 100644 index 0000000000..4693af7ef3 --- /dev/null +++ b/packages/backend-tasks/src/tasks/types.ts @@ -0,0 +1,138 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Duration } from 'luxon'; +import { AbortSignal } from 'node-abort-controller'; +import { z } from 'zod'; + +/** + * A function that can be called as a scheduled task. + * + * It may optionally accept an abort signal argument. When the signal triggers, + * processing should abort and return as quickly as possible. + * + * @public + */ +export type TaskFunction = + | ((abortSignal: AbortSignal) => void | Promise) + | (() => void | Promise); + +/** + * Options that apply to the invocation of a given task. + * + * @public + */ +export interface TaskDefinition { + /** + * A unique ID (within the scope of the plugin) for the task. + */ + id: string; + + /** + * The actual task function to be invoked regularly. + */ + fn: TaskFunction; + + /** + * An abort signal that, when triggered, will stop the recurring execution of + * the task. + */ + signal?: AbortSignal; + + /** + * The maximum amount of time that a single task invocation can take, before + * it's considered timed out and gets "released" such that a new invocation + * is permitted to take place (possibly, then, on a different worker). + * + * If no value is given for this field then there is no timeout. This is + * potentially dangerous. + */ + timeout: Duration; + + /** + * The amount of time that should pass between task invocation starts. + * Essentially, this equals roughly how often you want the task to run. + * + * This is a best effort value; under some circumstances there can be + * deviations. For example, if the task runtime is longer than the frequency + * and the timeout has not been given or not been exceeded yet, the next + * invocation of this task will be delayed until after the previous one + * finishes. + * + * The system does its best to avoid overlapping invocations. + * + * If no value is given for this field then the task will only be invoked + * once (on any worker) and then unscheduled automatically. + */ + frequency: Duration; + + /** + * The amount of time that should pass before the first invocation happens. + * + * This can be useful in cold start scenarios to stagger or delay some heavy + * compute jobs. + * + * If no value is given for this field then the first invocation will happen + * as soon as possible. + */ + initialDelay?: Duration; +} + +/** + * Deals with the scheduling of distributed tasks, for a given plugin. + * + * @public + */ +export interface PluginTaskScheduler { + /** + * Schedules a task function for coordinated exclusive invocation across + * workers. + * + * If the task was already scheduled since before by us or by another party, + * its options are just overwritten with the given options, and things + * continue from there. + * + * @param definition - The task definition + */ + scheduleTask(task: TaskDefinition): Promise; +} + +function isValidOptionalDurationString(d: string | undefined): boolean { + try { + return !d || Duration.fromISO(d).isValid === true; + } catch { + return false; + } +} + +export const taskSettingsV1Schema = z.object({ + version: z.literal(1), + initialDelayDuration: z + .string() + .optional() + .refine(isValidOptionalDurationString, { message: 'Invalid duration' }), + recurringAtMostEveryDuration: z + .string() + .refine(isValidOptionalDurationString, { message: 'Invalid duration' }), + timeoutAfterDuration: z + .string() + .refine(isValidOptionalDurationString, { message: 'Invalid duration' }), +}); + +/** + * The properties that control a scheduled task (version 1). + */ +export type TaskSettingsV1 = z.infer; diff --git a/packages/backend-tasks/src/tasks/util.test.ts b/packages/backend-tasks/src/tasks/util.test.ts new file mode 100644 index 0000000000..f74669a7f4 --- /dev/null +++ b/packages/backend-tasks/src/tasks/util.test.ts @@ -0,0 +1,76 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Duration } from 'luxon'; +import { AbortController } from 'node-abort-controller'; +import { delegateAbortController, sleep, validateId } from './util'; + +describe('util', () => { + describe('validateId', () => { + it.each(['a', 'a_b', 'ab123c_2'])( + 'accepts valid inputs, %p', + async input => { + expect(validateId(input)).toBeUndefined(); + }, + ); + + it.each(['', 'a!', 'A', 'a-b', 'a.b', '_a', 'a_', null, Symbol('a')])( + 'rejects invalid inputs, %p', + async input => { + expect(() => validateId(input as any)).toThrow(); + }, + ); + }); + + describe('sleep', () => { + it('finishes the wait as expected with no signal', async () => { + const ac = new AbortController(); + const start = Date.now(); + await sleep(Duration.fromObject({ seconds: 1 }), ac.signal); + expect(Date.now() - start).toBeGreaterThan(800); + }, 5_000); + + it('aborts properly on the signal', async () => { + const ac = new AbortController(); + const promise = sleep(Duration.fromObject({ seconds: 10 }), ac.signal); + ac.abort(); + await promise; + expect(true).toBe(true); + }, 1_000); + }); + + describe('delegateAbortController', () => { + it('inherits parent abort state', () => { + const parent = new AbortController(); + const child = delegateAbortController(parent.signal); + expect(parent.signal.aborted).toBe(false); + expect(child.signal.aborted).toBe(false); + parent.abort(); + expect(parent.signal.aborted).toBe(true); + expect(child.signal.aborted).toBe(true); + }); + + it('does not inherit from child to parent', () => { + const parent = new AbortController(); + const child = delegateAbortController(parent.signal); + expect(parent.signal.aborted).toBe(false); + expect(child.signal.aborted).toBe(false); + child.abort(); + expect(parent.signal.aborted).toBe(false); + expect(child.signal.aborted).toBe(true); + }); + }); +}); diff --git a/packages/backend-tasks/src/tasks/util.ts b/packages/backend-tasks/src/tasks/util.ts new file mode 100644 index 0000000000..0509f29363 --- /dev/null +++ b/packages/backend-tasks/src/tasks/util.ts @@ -0,0 +1,107 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { Knex } from 'knex'; +import { DateTime, Duration } from 'luxon'; +import { AbortController, AbortSignal } from 'node-abort-controller'; + +// Keep the IDs compatible with e.g. Prometheus +export function validateId(id: string) { + if (typeof id !== 'string' || !/^[a-z0-9]+(?:_[a-z0-9]+)*$/.test(id)) { + throw new InputError( + `${id} is not a valid ID, expected string of lowercase characters and digits separated by underscores`, + ); + } +} + +export function dbTime(t: Date | string): DateTime { + if (typeof t === 'string') { + return DateTime.fromSQL(t); + } + return DateTime.fromJSDate(t); +} + +export function nowPlus(duration: Duration | undefined, knex: Knex) { + const seconds = duration?.as('seconds') ?? 0; + if (!seconds) { + return knex.fn.now(); + } + return knex.client.config.client === 'sqlite3' + ? knex.raw(`datetime('now', ?)`, [`${seconds} seconds`]) + : knex.raw(`now() + interval '${seconds} seconds'`); +} + +/** + * Sleep for the given duration, but return sooner if the abort signal + * triggers. + * + * @param duration - The amount of time to sleep, at most + * @param abortSignal - An optional abort signal that short circuits the wait + */ +export async function sleep( + duration: Duration, + abortSignal?: AbortSignal, +): Promise { + if (abortSignal?.aborted) { + return; + } + + await new Promise(resolve => { + let timeoutHandle: NodeJS.Timeout | undefined = undefined; + + const done = () => { + if (timeoutHandle) { + clearTimeout(timeoutHandle); + } + abortSignal?.removeEventListener('abort', done); + resolve(); + }; + + timeoutHandle = setTimeout(done, duration.as('milliseconds')); + abortSignal?.addEventListener('abort', done); + }); +} + +/** + * Creates a new AbortController that, in addition to working as a regular + * standalone controller, also gets aborted if the given parent signal + * reaches aborted state. + * + * @param parent - The "parent" signal that can trigger the delegate + */ +export function delegateAbortController(parent?: AbortSignal): AbortController { + const delegate = new AbortController(); + + if (parent) { + if (parent.aborted) { + delegate.abort(); + } else { + const onParentAborted = () => { + delegate.abort(); + }; + + const onChildAborted = () => { + parent.removeEventListener('abort', onParentAborted); + }; + + parent.addEventListener('abort', onParentAborted, { once: true }); + delegate.signal.addEventListener('abort', onChildAborted, { once: true }); + } + } + + return delegate; +} diff --git a/packages/backend-test-utils/.snyk b/packages/backend-test-utils/.snyk new file mode 100644 index 0000000000..ad62599e66 --- /dev/null +++ b/packages/backend-test-utils/.snyk @@ -0,0 +1,47 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.22.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + SNYK-JS-TAR-1579155: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1579152: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1579147: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1536758: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1536531: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1536528: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z +patch: {} diff --git a/packages/backend/.snyk b/packages/backend/.snyk new file mode 100644 index 0000000000..ad62599e66 --- /dev/null +++ b/packages/backend/.snyk @@ -0,0 +1,47 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.22.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + SNYK-JS-TAR-1579155: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1579152: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1579147: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1536758: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1536531: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1536528: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z +patch: {} diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index 19e9bcdd67..7cf7d9ddaa 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,20 @@ # example-backend +## 0.2.52 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.9 + - @backstage/plugin-jenkins-backend@0.1.7 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.5 + - @backstage/plugin-scaffolder-backend@0.15.12 + - @backstage/plugin-azure-devops-backend@0.2.0 + - @backstage/catalog-client@0.5.1 + - @backstage/plugin-auth-backend@0.4.7 + - @backstage/plugin-catalog-backend@0.17.3 + - @backstage/plugin-scaffolder-backend-module-rails@0.1.7 + ## 0.2.50 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index f1d58252a4..4c854241d4 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.50", + "version": "0.2.52", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,28 +24,28 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.9.7", - "@backstage/catalog-client": "^0.5.0", + "@backstage/backend-common": "^0.9.9", + "@backstage/catalog-client": "^0.5.1", "@backstage/catalog-model": "^0.9.5", "@backstage/config": "^0.1.10", "@backstage/integration": "^0.6.8", "@backstage/plugin-app-backend": "^0.3.17", - "@backstage/plugin-auth-backend": "^0.4.5", - "@backstage/plugin-azure-devops-backend": "^0.1.3", + "@backstage/plugin-auth-backend": "^0.4.7", + "@backstage/plugin-azure-devops-backend": "^0.2.0", "@backstage/plugin-badges-backend": "^0.1.11", - "@backstage/plugin-catalog-backend": "^0.17.1", + "@backstage/plugin-catalog-backend": "^0.17.3", "@backstage/plugin-code-coverage-backend": "^0.1.14", "@backstage/plugin-graphql-backend": "^0.1.9", - "@backstage/plugin-jenkins-backend": "^0.1.6", + "@backstage/plugin-jenkins-backend": "^0.1.7", "@backstage/plugin-kubernetes-backend": "^0.3.18", "@backstage/plugin-kafka-backend": "^0.2.10", "@backstage/plugin-proxy-backend": "^0.2.13", "@backstage/plugin-rollbar-backend": "^0.1.15", - "@backstage/plugin-scaffolder-backend": "^0.15.10", - "@backstage/plugin-scaffolder-backend-module-rails": "^0.1.5", + "@backstage/plugin-scaffolder-backend": "^0.15.12", + "@backstage/plugin-scaffolder-backend-module-rails": "^0.1.7", "@backstage/plugin-search-backend": "^0.2.6", "@backstage/plugin-search-backend-node": "^0.4.2", - "@backstage/plugin-search-backend-module-elasticsearch": "^0.0.4", + "@backstage/plugin-search-backend-module-elasticsearch": "^0.0.5", "@backstage/plugin-search-backend-module-pg": "^0.2.1", "@backstage/plugin-techdocs-backend": "^0.10.5", "@backstage/plugin-tech-insights-backend": "^0.1.0", @@ -68,7 +68,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.8.0", + "@backstage/cli": "^0.8.2", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index ffdce949b7..f978e84da9 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -48,12 +48,12 @@ import scaffolder from './plugins/scaffolder'; import proxy from './plugins/proxy'; import search from './plugins/search'; import techdocs from './plugins/techdocs'; +import techInsights from './plugins/techInsights'; import todo from './plugins/todo'; import graphql from './plugins/graphql'; import app from './plugins/app'; import badges from './plugins/badges'; import jenkins from './plugins/jenkins'; -import techInsights from './plugins/techInsights'; import { PluginEnvironment } from './types'; function makeCreateEnv(config: Config) { diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 8243d09486..f5ec2116f7 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/catalog-client +## 0.5.1 + +### Patch Changes + +- 39e92897e4: Improved API documentation for catalog-client. + ## 0.5.0 ### Minor Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index d4ba2c20f6..9caac50c90 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-client", "description": "An isomorphic client for the catalog backend", - "version": "0.5.0", + "version": "0.5.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/errors": "^0.1.3", + "@backstage/errors": "^0.1.4", "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.8.0", + "@backstage/cli": "^0.8.2", "@types/jest": "^26.0.7", "msw": "^0.35.0" }, diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 48f38cbc8a..0fc702851d 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/cli +## 0.8.2 + +### Patch Changes + +- dd355bca46: Switched to dynamically determining the packages that are unsafe to repack when executing the CLI within the Backstage main repo. +- b393c4d4be: Fixed the `config:check` command that was incorrectly only validating frontend configuration. Also added a `--frontend` flag to the command which maintains that behavior. +- 0611f3b3e2: Reading app config from a remote server +- ec64d9590c: Make `ExitCodeError` call `super` early to avoid compiler warnings +- 8af66229e7: Bumped `@spotify/eslint-config-react` from `v10` to `v12`, dropping support for Node.js v12. +- a197708da9: Bumped `@spotify/eslint-config-typescript` from `v10` to `v12`, dropping support for Node.js v12. +- Updated dependencies + - @backstage/config-loader@0.7.2 + ## 0.8.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 23a2bef027..546a1597e1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.8.1", + "version": "0.8.2", "private": false, "publishConfig": { "access": "public" @@ -30,7 +30,7 @@ "dependencies": { "@backstage/cli-common": "^0.1.5", "@backstage/config": "^0.1.11", - "@backstage/config-loader": "^0.7.1", + "@backstage/config-loader": "^0.7.2", "@backstage/errors": "^0.1.4", "@backstage/types": "^0.1.1", "@hot-loader/react-dom": "^16.13.0", @@ -42,7 +42,7 @@ "@rollup/plugin-node-resolve": "^13.0.0", "@rollup/plugin-yaml": "^3.0.0", "@spotify/eslint-config-base": "^12.0.0", - "@spotify/eslint-config-react": "^10.0.0", + "@spotify/eslint-config-react": "^12.0.0", "@spotify/eslint-config-typescript": "^12.0.0", "@sucrase/jest-plugin": "^2.1.1", "@sucrase/webpack-loader": "^2.0.0", @@ -117,14 +117,14 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.9.8", + "@backstage/backend-common": "^0.9.9", "@backstage/config": "^0.1.11", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/core-app-api": "^0.1.19", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", - "@backstage/theme": "^0.2.12", + "@backstage/test-utils": "^0.1.21", + "@backstage/theme": "^0.2.13", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", "@types/fs-extra": "^9.0.1", diff --git a/packages/cli/src/commands/config/print.ts b/packages/cli/src/commands/config/print.ts index 6bd575f9bf..b640ee4f37 100644 --- a/packages/cli/src/commands/config/print.ts +++ b/packages/cli/src/commands/config/print.ts @@ -25,6 +25,7 @@ export default async (cmd: Command) => { args: cmd.config, fromPackage: cmd.package, mockEnv: cmd.lax, + fullVisibility: !cmd.frontend, }); const visibility = getVisibilityOption(cmd); const data = serializeConfigData(appConfigs, schema, visibility); diff --git a/packages/cli/src/commands/config/validate.ts b/packages/cli/src/commands/config/validate.ts index 9041272c5a..2d3ce60366 100644 --- a/packages/cli/src/commands/config/validate.ts +++ b/packages/cli/src/commands/config/validate.ts @@ -22,5 +22,6 @@ export default async (cmd: Command) => { args: cmd.config, fromPackage: cmd.package, mockEnv: cmd.lax, + fullVisibility: !cmd.frontend, }); }; diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 302f76cc5d..4e01a8ca9f 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -172,6 +172,7 @@ export function registerCommands(program: CommanderStatic) { 'Only load config schema that applies to the given package', ) .option('--lax', 'Do not require environment variables to be set') + .option('--frontend', 'Only validate the frontend configuration') .option(...configOption) .description( 'Validate that the given configuration loads and matches schema', diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index cf9e873a66..863728b8f8 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -32,6 +32,23 @@ export const transforms = (options: TransformOptions): Transforms => { const extraTransforms = isDev ? ['react-hot-loader'] : []; + // This ensures that styles inserted from the style-loader and any + // async style chunks are always given lower priority than JSS styles. + // Note that this function is stringified and executed in the browser + // after transpilation, so stick to simple syntax + function insertBeforeJssStyles(element: any) { + const head = document.head; + // This makes sure that any style elements we insert get put before the + // dynamic styles from JSS, such as the ones from `makeStyles()`. + // TODO(Rugvip): This will likely break in material-ui v5, keep an eye on it. + const firstJssNode = head.querySelector('style[data-jss]'); + if (!firstJssNode) { + head.appendChild(element); + } else { + head.insertBefore(element, firstJssNode); + } + } + const loaders = [ { test: /\.(tsx?)$/, @@ -112,7 +129,14 @@ export const transforms = (options: TransformOptions): Transforms => { { test: /\.css$/i, use: [ - isDev ? require.resolve('style-loader') : MiniCssExtractPlugin.loader, + isDev + ? { + loader: require.resolve('style-loader'), + options: { + insert: insertBeforeJssStyles, + }, + } + : MiniCssExtractPlugin.loader, { loader: require.resolve('css-loader'), options: { @@ -132,6 +156,7 @@ export const transforms = (options: TransformOptions): Transforms => { new MiniCssExtractPlugin({ filename: 'static/[name].[contenthash:8].css', chunkFilename: 'static/[name].[id].[contenthash:8].css', + insert: insertBeforeJssStyles, // Only applies to async chunks }), ); } diff --git a/packages/cli/src/lib/config.ts b/packages/cli/src/lib/config.ts index 80b374039f..6f93318ead 100644 --- a/packages/cli/src/lib/config.ts +++ b/packages/cli/src/lib/config.ts @@ -28,6 +28,7 @@ type Options = { fromPackage?: string; mockEnv?: boolean; withFilteredKeys?: boolean; + fullVisibility?: boolean; }; export async function loadCliConfig(options: Options) { @@ -70,7 +71,9 @@ export async function loadCliConfig(options: Options) { try { const frontendAppConfigs = schema.process(appConfigs, { - visibility: ['frontend'], + visibility: options.fullVisibility + ? ['frontend', 'backend', 'secret'] + : ['frontend'], withFilteredKeys: options.withFilteredKeys, }); const frontendConfig = ConfigReader.fromConfigs(frontendAppConfigs); diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index fbb2076167..1043d9718f 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/codemods +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/core-plugin-api@0.1.13 + - @backstage/core-app-api@0.1.20 + ## 0.1.20 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 0dc0e9a0d5..17e89c86f0 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.20", + "version": "0.1.21", "private": false, "publishConfig": { "access": "public", diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index dcaec00816..0f1bc27996 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/config-loader +## 0.7.2 + +### Patch Changes + +- 0611f3b3e2: Reading app config from a remote server +- 26c5659c97: Bump msw to the same version as the rest + ## 0.7.1 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 22fcedf486..2e25d91856 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.7.1", + "version": "0.7.2", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index ff75f5bf81..b9e60b7dab 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-app-api +## 0.1.20 + +### Patch Changes + +- 78c512ce8f: I have added default icons for the catalog, scaffolder, techdocs, and search. +- 8b4284cd5c: Improve API documentation for @backstage/core-plugin-api +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + ## 0.1.19 ### Patch Changes diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md index 8fb3d5c788..d98b5ef5f8 100644 --- a/packages/core-app-api/api-report.md +++ b/packages/core-app-api/api-report.md @@ -45,6 +45,7 @@ import { Observable } from '@backstage/types'; import { oktaAuthApiRef } from '@backstage/core-plugin-api'; import { oneloginAuthApiRef } from '@backstage/core-plugin-api'; import { OpenIdConnectApi } from '@backstage/core-plugin-api'; +import { OptionalAppOptions } from '@backstage/app-defaults'; import { PendingAuthRequest } from '@backstage/core-plugin-api'; import { PluginOutput } from '@backstage/core-plugin-api'; import { ProfileInfo } from '@backstage/core-plugin-api'; @@ -150,7 +151,7 @@ export type AppComponents = { Progress: ComponentType<{}>; Router: ComponentType<{}>; ErrorBoundaryFallback: ComponentType; - ThemeProvider: ComponentType<{}>; + ThemeProvider?: ComponentType<{}>; SignInPage?: ComponentType; }; @@ -164,15 +165,41 @@ export type AppContext = { getComponents(): AppComponents; }; +// @public +export type AppIcons = { + 'kind:api': IconComponent; + 'kind:component': IconComponent; + 'kind:domain': IconComponent; + 'kind:group': IconComponent; + 'kind:location': IconComponent; + 'kind:system': IconComponent; + 'kind:user': IconComponent; + brokenImage: IconComponent; + catalog: IconComponent; + chat: IconComponent; + dashboard: IconComponent; + docs: IconComponent; + email: IconComponent; + github: IconComponent; + group: IconComponent; + help: IconComponent; + scaffolder: IconComponent; + search: IconComponent; + techdocs: IconComponent; + user: IconComponent; + warning: IconComponent; +}; + // @public export type AppOptions = { apis?: Iterable; - icons?: Partial & { + defaultApis?: Iterable; + icons: AppIcons & { [key in string]: IconComponent; }; plugins?: BackstagePluginWithAnyOutput[]; - components?: Partial; - themes?: AppTheme[]; + components: AppComponents; + themes: (Partial & Omit)[]; configLoader?: AppConfigLoader; bindRoutes?(context: { bind: AppRouteBinder }): void; }; @@ -286,10 +313,11 @@ export type BootErrorPageProps = { export { ConfigReader }; -// Warning: (ae-forgotten-export) The symbol "PrivateAppImpl" needs to be exported by the entry point index.d.ts -// +// @public @deprecated +export function createApp(options?: OptionalAppOptions): BackstageApp; + // @public -export function createApp(options?: AppOptions): PrivateAppImpl; +export function createSpecializedApp(options: AppOptions): BackstageApp; // @public export const defaultConfigLoader: AppConfigLoader; @@ -608,5 +636,4 @@ export class WebStorage implements StorageApi { // Warnings were encountered during analysis: // // src/apis/system/ApiProvider.d.ts:15:5 - (ae-forgotten-export) The symbol "ApiProviderProps" needs to be exported by the entry point index.d.ts -// src/app/types.d.ts:152:5 - (ae-forgotten-export) The symbol "AppIcons" needs to be exported by the entry point index.d.ts ``` diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index f40e3d5eb7..fc6c103109 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "0.1.19", + "version": "0.1.20", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.7.2", + "@backstage/app-defaults": "^0.1.0", + "@backstage/core-components": "^0.7.3", "@backstage/config": "^0.1.11", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@backstage/types": "^0.1.1", "@backstage/version-bridge": "^0.1.0", "@material-ui/core": "^4.12.2", @@ -46,8 +47,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/test-utils": "^0.1.20", + "@backstage/cli": "^0.8.2", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts index d0f137f0be..76cf01d20a 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts @@ -15,7 +15,6 @@ */ import MockOAuthApi from './MockOAuthApi'; -import PowerIcon from '@material-ui/icons/Power'; describe('MockOAuthApi', () => { it('should trigger all requests', async () => { @@ -24,13 +23,13 @@ describe('MockOAuthApi', () => { const authHandler1 = jest.fn().mockImplementation(() => authResult); const requester1 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, + provider: { icon: () => null, title: 'Test' }, onAuthRequest: authHandler1, }); const authHandler2 = jest.fn().mockResolvedValue('other'); const requester2 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, + provider: { icon: () => null, title: 'Test' }, onAuthRequest: authHandler2, }); @@ -67,13 +66,13 @@ describe('MockOAuthApi', () => { const authHandler1 = jest.fn(); const requester1 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, + provider: { icon: () => null, title: 'Test' }, onAuthRequest: authHandler1, }); const authHandler2 = jest.fn(); const requester2 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, + provider: { icon: () => null, title: 'Test' }, onAuthRequest: authHandler2, }); diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts index d8faf90229..3110197ad9 100644 --- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts +++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import ProviderIcon from '@material-ui/icons/AcUnit'; import { OAuthRequestManager } from './OAuthRequestManager'; describe('OAuthRequestManager', () => { @@ -27,7 +26,7 @@ describe('OAuthRequestManager', () => { const requester = manager.createAuthRequester({ provider: { title: 'My Provider', - icon: ProviderIcon, + icon: () => null, }, onAuthRequest: async () => 'hello', }); diff --git a/packages/core-app-api/src/apis/implementations/auth/atlassian/AtlassianAuth.ts b/packages/core-app-api/src/apis/implementations/auth/atlassian/AtlassianAuth.ts index 227d70e494..caad40ddbf 100644 --- a/packages/core-app-api/src/apis/implementations/auth/atlassian/AtlassianAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/atlassian/AtlassianAuth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import AtlassianIcon from '@material-ui/icons/AcUnit'; import { atlassianAuthApiRef } from '@backstage/core-plugin-api'; import { OAuth2 } from '../oauth2'; import { OAuthApiCreateOptions } from '../types'; @@ -22,7 +21,7 @@ import { OAuthApiCreateOptions } from '../types'; const DEFAULT_PROVIDER = { id: 'atlassian', title: 'Atlassian', - icon: AtlassianIcon, + icon: () => null, }; /** diff --git a/packages/core-app-api/src/apis/implementations/auth/auth0/Auth0Auth.ts b/packages/core-app-api/src/apis/implementations/auth/auth0/Auth0Auth.ts index 69462942e4..0a158ccb9e 100644 --- a/packages/core-app-api/src/apis/implementations/auth/auth0/Auth0Auth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/auth0/Auth0Auth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import Auth0Icon from '@material-ui/icons/AcUnit'; import { auth0AuthApiRef } from '@backstage/core-plugin-api'; import { OAuth2 } from '../oauth2'; import { OAuthApiCreateOptions } from '../types'; @@ -22,7 +21,7 @@ import { OAuthApiCreateOptions } from '../types'; const DEFAULT_PROVIDER = { id: 'auth0', title: 'Auth0', - icon: Auth0Icon, + icon: () => null, }; /** diff --git a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts index c9db93906a..e488580c4d 100644 --- a/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/bitbucket/BitbucketAuth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import BitbucketIcon from '@material-ui/icons/FormatBold'; import { BackstageIdentity, bitbucketAuthApiRef, @@ -37,7 +36,7 @@ export type BitbucketAuthResponse = { const DEFAULT_PROVIDER = { id: 'bitbucket', title: 'Bitbucket', - icon: BitbucketIcon, + icon: () => null, }; /** diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts index 01e7e77b8e..885e80da7c 100644 --- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import GithubIcon from '@material-ui/icons/AcUnit'; import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; import { GithubSession } from './types'; import { @@ -48,7 +47,7 @@ export type GithubAuthResponse = { const DEFAULT_PROVIDER = { id: 'github', title: 'GitHub', - icon: GithubIcon, + icon: () => null, }; /** diff --git a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts index af581d0018..f46f2ef72e 100644 --- a/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import GitlabIcon from '@material-ui/icons/AcUnit'; import { gitlabAuthApiRef } from '@backstage/core-plugin-api'; import { OAuth2 } from '../oauth2'; import { OAuthApiCreateOptions } from '../types'; @@ -22,7 +21,7 @@ import { OAuthApiCreateOptions } from '../types'; const DEFAULT_PROVIDER = { id: 'gitlab', title: 'GitLab', - icon: GitlabIcon, + icon: () => null, }; /** diff --git a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts index 916c52382e..a6f37b250f 100644 --- a/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/google/GoogleAuth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import GoogleIcon from '@material-ui/icons/AcUnit'; import { googleAuthApiRef } from '@backstage/core-plugin-api'; import { OAuth2 } from '../oauth2'; import { OAuthApiCreateOptions } from '../types'; @@ -22,7 +21,7 @@ import { OAuthApiCreateOptions } from '../types'; const DEFAULT_PROVIDER = { id: 'google', title: 'Google', - icon: GoogleIcon, + icon: () => null, }; const SCOPE_PREFIX = 'https://www.googleapis.com/auth/'; diff --git a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts index 09bd7f67a2..148be66873 100644 --- a/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import MicrosoftIcon from '@material-ui/icons/AcUnit'; import { microsoftAuthApiRef } from '@backstage/core-plugin-api'; import { OAuth2 } from '../oauth2'; import { OAuthApiCreateOptions } from '../types'; @@ -22,7 +21,7 @@ import { OAuthApiCreateOptions } from '../types'; const DEFAULT_PROVIDER = { id: 'microsoft', title: 'Microsoft', - icon: MicrosoftIcon, + icon: () => null, }; /** 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 6c0759d967..45937a68f8 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,7 +14,6 @@ * limitations under the License. */ -import OAuth2Icon from '@material-ui/icons/AcUnit'; import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; @@ -51,7 +50,7 @@ export type OAuth2Response = { const DEFAULT_PROVIDER = { id: 'oauth2', title: 'Your Identity Provider', - icon: OAuth2Icon, + icon: () => null, }; /** diff --git a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts index 294fb496af..465a124051 100644 --- a/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/okta/OktaAuth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import OktaIcon from '@material-ui/icons/AcUnit'; import { oktaAuthApiRef } from '@backstage/core-plugin-api'; import { OAuth2 } from '../oauth2'; import { OAuthApiCreateOptions } from '../types'; @@ -22,7 +21,7 @@ import { OAuthApiCreateOptions } from '../types'; const DEFAULT_PROVIDER = { id: 'okta', title: 'Okta', - icon: OktaIcon, + icon: () => null, }; const OKTA_OIDC_SCOPES: Set = new Set([ diff --git a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts index f49ea4cde8..5f933b9c6e 100644 --- a/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import OneLoginIcon from '@material-ui/icons/AcUnit'; import { oneloginAuthApiRef, OAuthRequestApi, @@ -33,7 +32,7 @@ type CreateOptions = { const DEFAULT_PROVIDER = { id: 'onelogin', title: 'onelogin', - icon: OneLoginIcon, + icon: () => null, }; const OIDC_SCOPES: Set = new Set([ diff --git a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts index f2668dedd2..63985979de 100644 --- a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import SamlIcon from '@material-ui/icons/AcUnit'; import { DirectAuthConnector } from '../../../../lib/AuthConnector'; import { SessionManager } from '../../../../lib/AuthSessionManager/types'; import { @@ -42,7 +41,7 @@ export type SamlAuthResponse = { const DEFAULT_PROVIDER = { id: 'saml', title: 'SAML', - icon: SamlIcon, + icon: () => null, }; /** diff --git a/packages/core-app-api/src/app/App.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx similarity index 87% rename from packages/core-app-api/src/app/App.test.tsx rename to packages/core-app-api/src/app/AppManager.test.tsx index ab287a3ece..07401660e8 100644 --- a/packages/core-app-api/src/app/App.test.tsx +++ b/packages/core-app-api/src/app/AppManager.test.tsx @@ -20,11 +20,9 @@ import { renderWithEffects, withLogCollector, } from '@backstage/test-utils'; -import { lightTheme } from '@backstage/theme'; import { render, screen } from '@testing-library/react'; import React, { PropsWithChildren } from 'react'; import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; -import { defaultAppIcons } from './icons'; import { configApiRef, createApiFactory, @@ -37,8 +35,8 @@ import { createRoutableExtension, analyticsApiRef, } from '@backstage/core-plugin-api'; -import { generateBoundRoutes, PrivateAppImpl } from './App'; -import { AppThemeProvider } from './AppThemeProvider'; +import { generateBoundRoutes, AppManager } from './AppManager'; +import { AppComponents, AppIcons } from './types'; describe('generateBoundRoutes', () => { it('runs happy path', () => { @@ -128,7 +126,7 @@ describe('Integration Test', () => { const HiddenComponent = plugin2.provide( createRoutableExtension({ name: 'HiddenComponent', - component: () => Promise.resolve((_: { path?: string }) =>
), + component: () => Promise.resolve(() =>
), mountPoint: plugin2RouteRef, }), ); @@ -137,7 +135,7 @@ describe('Integration Test', () => { createRoutableExtension({ name: 'ExposedComponent', component: () => - Promise.resolve((_: PropsWithChildren<{ path?: string }>) => { + Promise.resolve(() => { const link1 = useRouteRef(plugin1RouteRef); const link2 = useRouteRef(plugin2RouteRef); const subLink1 = useRouteRef(subRouteRef1); @@ -169,6 +167,7 @@ describe('Integration Test', () => { const NavigateComponent = plugin1.provide( createRoutableExtension({ + name: 'NavigateComponent', component: () => Promise.resolve((_: PropsWithChildren<{ path?: string }>) => { return ; @@ -177,17 +176,19 @@ describe('Integration Test', () => { }), ); - const components = { + const components: AppComponents = { NotFoundErrorPage: () => null, BootErrorPage: () => null, Progress: () => null, Router: BrowserRouter, ErrorBoundaryFallback: () => null, - ThemeProvider: AppThemeProvider, + ThemeProvider: ({ children }) => <>{children}, }; + const icons = {} as AppIcons; + it('runs happy paths', async () => { - const app = new PrivateAppImpl({ + const app = new AppManager({ apis: [noOpAnalyticsApi], defaultApis: [], themes: [ @@ -195,12 +196,13 @@ describe('Integration Test', () => { id: 'light', title: 'Light Theme', variant: 'light', - theme: lightTheme, + Provider: ({ children }) => <>{children}, }, ], - icons: defaultAppIcons, + icons, plugins: [], components, + configLoader: async () => [], bindRoutes: ({ bind }) => { bind(plugin1.externalRoutes, { extRouteRef1: plugin1RouteRef, @@ -218,8 +220,8 @@ describe('Integration Test', () => { - - + } /> + } /> , @@ -241,7 +243,7 @@ describe('Integration Test', () => { }); it('runs happy paths without optional routes', async () => { - const app = new PrivateAppImpl({ + const app = new AppManager({ apis: [noOpAnalyticsApi], defaultApis: [], themes: [ @@ -249,12 +251,13 @@ describe('Integration Test', () => { id: 'light', title: 'Light Theme', variant: 'light', - theme: lightTheme, + Provider: ({ children }) => <>{children}, }, ], - icons: defaultAppIcons, + icons, plugins: [], components, + configLoader: async () => [], bindRoutes: ({ bind }) => { bind(plugin1.externalRoutes, { extRouteRef1: plugin1RouteRef, @@ -270,8 +273,8 @@ describe('Integration Test', () => { - - + } /> + } /> , @@ -298,7 +301,7 @@ describe('Integration Test', () => { }), ]; - const app = new PrivateAppImpl({ + const app = new AppManager({ apis, defaultApis: [], themes: [ @@ -306,10 +309,10 @@ describe('Integration Test', () => { id: 'light', title: 'Light Theme', variant: 'light', - theme: lightTheme, + Provider: ({ children }) => <>{children}, }, ], - icons: defaultAppIcons, + icons, plugins: [ createPlugin({ id: 'test', @@ -317,6 +320,7 @@ describe('Integration Test', () => { }), ], components, + configLoader: async () => [], bindRoutes: ({ bind }) => { bind(plugin1.externalRoutes, { extRouteRef1: plugin1RouteRef, @@ -332,8 +336,8 @@ describe('Integration Test', () => { - - + } /> + } /> , @@ -348,7 +352,7 @@ describe('Integration Test', () => { it('should track route changes via analytics api', async () => { const mockAnalyticsApi = new MockAnalyticsApi(); const apis = [createApiFactory(analyticsApiRef, mockAnalyticsApi)]; - const app = new PrivateAppImpl({ + const app = new AppManager({ apis, defaultApis: [], themes: [ @@ -356,12 +360,13 @@ describe('Integration Test', () => { id: 'light', title: 'Light Theme', variant: 'light', - theme: lightTheme, + Provider: ({ children }) => <>{children}, }, ], - icons: defaultAppIcons, + icons, plugins: [], components, + configLoader: async () => [], bindRoutes: ({ bind }) => { bind(plugin1.externalRoutes, { extRouteRef1: plugin1RouteRef, @@ -378,7 +383,7 @@ describe('Integration Test', () => { } /> - } /> + } /> , @@ -408,7 +413,7 @@ describe('Integration Test', () => { }); it('should throw some error when the route has duplicate params', () => { - const app = new PrivateAppImpl({ + const app = new AppManager({ apis: [], defaultApis: [], themes: [ @@ -416,12 +421,13 @@ describe('Integration Test', () => { id: 'light', title: 'Light Theme', variant: 'light', - theme: lightTheme, + Provider: ({ children }) => <>{children}, }, ], - icons: defaultAppIcons, + icons, plugins: [], components, + configLoader: async () => [], bindRoutes: ({ bind }) => { bind(plugin1.externalRoutes, { extRouteRef1: plugin1RouteRef, @@ -438,9 +444,9 @@ describe('Integration Test', () => { - - - + }> + } /> + , diff --git a/packages/core-app-api/src/app/App.tsx b/packages/core-app-api/src/app/AppManager.tsx similarity index 94% rename from packages/core-app-api/src/app/App.tsx rename to packages/core-app-api/src/app/AppManager.tsx index 8cb3f167fb..950f57bb7d 100644 --- a/packages/core-app-api/src/app/App.tsx +++ b/packages/core-app-api/src/app/AppManager.tsx @@ -77,6 +77,8 @@ import { SignInPageProps, SignInResult, } from './types'; +import { AppThemeProvider } from './AppThemeProvider'; +import { defaultConfigLoader } from './defaultConfigLoader'; export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) { const result = new Map(); @@ -121,17 +123,6 @@ function getBasePath(configApi: Config) { return pathname; } -type FullAppOptions = { - apis: Iterable; - icons: NonNullable; - plugins: BackstagePlugin[]; - components: AppComponents; - themes: AppTheme[]; - configLoader?: AppConfigLoader; - defaultApis: Iterable; - bindRoutes?: AppOptions['bindRoutes']; -}; - function useConfigLoader( configLoader: AppConfigLoader | undefined, components: AppComponents, @@ -151,7 +142,7 @@ function useConfigLoader( noConfigNode = ; } - const { ThemeProvider } = components; + const { ThemeProvider = AppThemeProvider } = components; // Before the config is loaded we can't use a router, so exit early if (noConfigNode) { @@ -170,7 +161,7 @@ function useConfigLoader( } class AppContextImpl implements AppContext { - constructor(private readonly app: PrivateAppImpl) {} + constructor(private readonly app: AppManager) {} getPlugins(): BackstagePlugin[] { return this.app.getPlugins(); @@ -185,7 +176,7 @@ class AppContextImpl implements AppContext { } } -export class PrivateAppImpl implements BackstageApp { +export class AppManager implements BackstageApp { private apiHolder?: ApiHolder; private configApi?: ConfigApi; @@ -201,14 +192,16 @@ export class PrivateAppImpl implements BackstageApp { private readonly identityApi = new AppIdentity(); private readonly apiFactoryRegistry: ApiFactoryRegistry; - constructor(options: FullAppOptions) { - this.apis = options.apis; + constructor(options: AppOptions) { + this.apis = options.apis ?? []; this.icons = options.icons; - this.plugins = new Set(options.plugins); + this.plugins = new Set( + (options.plugins as BackstagePlugin[]) ?? [], + ); this.components = options.components; - this.themes = options.themes; - this.configLoader = options.configLoader; - this.defaultApis = options.defaultApis; + this.themes = options.themes as AppTheme[]; + this.configLoader = options.configLoader ?? defaultConfigLoader; + this.defaultApis = options.defaultApis ?? []; this.bindRoutes = options.bindRoutes; this.apiFactoryRegistry = new ApiFactoryRegistry(); } @@ -307,7 +300,7 @@ export class PrivateAppImpl implements BackstageApp { return loadedConfig.node; } - const { ThemeProvider } = this.components; + const { ThemeProvider = AppThemeProvider } = this.components; return ( diff --git a/packages/core-app-api/src/app/AppThemeProvider.tsx b/packages/core-app-api/src/app/AppThemeProvider.tsx index 4e3a8487a6..2e145025d5 100644 --- a/packages/core-app-api/src/app/AppThemeProvider.tsx +++ b/packages/core-app-api/src/app/AppThemeProvider.tsx @@ -90,6 +90,17 @@ export function AppThemeProvider({ children }: PropsWithChildren<{}>) { throw new Error('App has no themes'); } + if (appTheme.Provider) { + return ; + } + + // eslint-disable-next-line no-console + console.warn( + "DEPRECATION WARNING: A provided app theme is using the deprecated 'theme' property " + + 'and should be migrated to use a Provider instead. ' + + 'See https://backstage.io/docs/api/deprecations#app-theme for more info.', + ); + return ( {children} diff --git a/packages/core-app-api/src/app/createApp.tsx b/packages/core-app-api/src/app/createApp.tsx index 65aa15b50c..e11076099b 100644 --- a/packages/core-app-api/src/app/createApp.tsx +++ b/packages/core-app-api/src/app/createApp.tsx @@ -14,171 +14,25 @@ * limitations under the License. */ -import { AppConfig } from '@backstage/config'; -import { JsonObject } from '@backstage/types'; -import { Button } from '@material-ui/core'; -import { ErrorPage, ErrorPanel, Progress } from '@backstage/core-components'; -import { darkTheme, lightTheme } from '@backstage/theme'; -import DarkIcon from '@material-ui/icons/Brightness2'; -import LightIcon from '@material-ui/icons/WbSunny'; -import React, { PropsWithChildren } from 'react'; import { - BrowserRouter, - MemoryRouter, - useInRouterContext, -} from 'react-router-dom'; -import { PrivateAppImpl } from './App'; -import { AppThemeProvider } from './AppThemeProvider'; -import { defaultApis } from './defaultApis'; -import { defaultAppIcons } from './icons'; -import { - AppConfigLoader, - AppOptions, - BootErrorPageProps, - ErrorBoundaryFallbackProps, -} from './types'; -import { BackstagePlugin } from '@backstage/core-plugin-api'; - -/** - * The default config loader, which expects that config is available at compile-time - * in `process.env.APP_CONFIG`. APP_CONFIG should be an array of config objects as - * returned by the config loader. - * - * It will also load runtime config from the __APP_INJECTED_RUNTIME_CONFIG__ string, - * which can be rewritten at runtime to contain an additional JSON config object. - * If runtime config is present, it will be placed first in the config array, overriding - * other config values. - * - * @public - */ -export const defaultConfigLoader: AppConfigLoader = async ( - // This string may be replaced at runtime to provide additional config. - // It should be replaced by a JSON-serialized config object. - // It's a param so we can test it, but at runtime this will always fall back to default. - runtimeConfigJson: string = '__APP_INJECTED_RUNTIME_CONFIG__', -) => { - const appConfig = process.env.APP_CONFIG; - if (!appConfig) { - throw new Error('No static configuration provided'); - } - if (!Array.isArray(appConfig)) { - throw new Error('Static configuration has invalid format'); - } - const configs = appConfig.slice() as unknown as AppConfig[]; - - // Avoiding this string also being replaced at runtime - if ( - runtimeConfigJson !== - '__app_injected_runtime_config__'.toLocaleUpperCase('en-US') - ) { - try { - const data = JSON.parse(runtimeConfigJson) as JsonObject; - if (Array.isArray(data)) { - configs.push(...data); - } else { - configs.push({ data, context: 'env' }); - } - } catch (error) { - throw new Error(`Failed to load runtime configuration, ${error}`); - } - } - - const windowAppConfig = (window as any).__APP_CONFIG__; - if (windowAppConfig) { - configs.push({ - context: 'window', - data: windowAppConfig, - }); - } - return configs; -}; - -export function OptionallyWrapInRouter({ children }: PropsWithChildren<{}>) { - if (useInRouterContext()) { - return <>{children}; - } - return {children}; -} + createApp as createDefaultApp, + OptionalAppOptions, +} from '@backstage/app-defaults'; /** * Creates a new Backstage App. * + * @deprecated Use {@link @backstage/app-defaults#createApp} from `@backstage/app-defaults` instead + * @param options - A set of options for creating the app * @public */ -export function createApp(options?: AppOptions) { - const DefaultNotFoundPage = () => ( - +export function createApp(options?: OptionalAppOptions) { + // eslint-disable-next-line no-console + console.warn( + 'DEPRECATION WARNING: The createApp function from @backstage/core-app-api will soon be removed, ' + + 'migrate to importing createApp from the @backstage/app-defaults package instead. ' + + 'If you do not wish to use a standard app configuration but instead supply all options yourself ' + + ' you can use createSpecializedApp from @backstage/core-app-api instead.', ); - const DefaultBootErrorPage = ({ step, error }: BootErrorPageProps) => { - let message = ''; - if (step === 'load-config') { - message = `The configuration failed to load, someone should have a look at this error: ${error.message}`; - } else if (step === 'load-chunk') { - message = `Lazy loaded chunk failed to load, try to reload the page: ${error.message}`; - } - // TODO: figure out a nicer way to handle routing on the error page, when it can be done. - return ( - - - - ); - }; - const DefaultErrorBoundaryFallback = ({ - error, - resetError, - plugin, - }: ErrorBoundaryFallbackProps) => { - return ( - - - - ); - }; - - const apis = options?.apis ?? []; - const icons = { ...defaultAppIcons, ...options?.icons }; - const plugins = options?.plugins ?? []; - const components = { - NotFoundErrorPage: DefaultNotFoundPage, - BootErrorPage: DefaultBootErrorPage, - Progress: Progress, - Router: BrowserRouter, - ErrorBoundaryFallback: DefaultErrorBoundaryFallback, - ThemeProvider: AppThemeProvider, - ...options?.components, - }; - const themes = options?.themes ?? [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - icon: , - }, - { - id: 'dark', - title: 'Dark Theme', - variant: 'dark', - theme: darkTheme, - icon: , - }, - ]; - const configLoader = options?.configLoader ?? defaultConfigLoader; - - return new PrivateAppImpl({ - apis, - icons, - plugins: plugins as BackstagePlugin[], - components, - themes, - configLoader, - defaultApis, - bindRoutes: options?.bindRoutes, - }); + return createDefaultApp(options); } diff --git a/packages/core-app-api/src/app/createSpecializedApp.tsx b/packages/core-app-api/src/app/createSpecializedApp.tsx new file mode 100644 index 0000000000..9bda223722 --- /dev/null +++ b/packages/core-app-api/src/app/createSpecializedApp.tsx @@ -0,0 +1,29 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AppManager } from './AppManager'; +import { AppOptions, BackstageApp } from './types'; + +/** + * Creates a new Backstage App where the full set of options are required. + * + * @public + * @param options - A set of options for creating the app + * @returns + */ +export function createSpecializedApp(options: AppOptions): BackstageApp { + return new AppManager(options); +} diff --git a/packages/core-app-api/src/app/createApp.test.tsx b/packages/core-app-api/src/app/defaultConfigLoader.test.ts similarity index 66% rename from packages/core-app-api/src/app/createApp.test.tsx rename to packages/core-app-api/src/app/defaultConfigLoader.test.ts index 0c3f62b14d..aebba92c43 100644 --- a/packages/core-app-api/src/app/createApp.test.tsx +++ b/packages/core-app-api/src/app/defaultConfigLoader.test.ts @@ -14,15 +14,7 @@ * limitations under the License. */ -import { render, screen } from '@testing-library/react'; -import { renderWithEffects } from '@backstage/test-utils'; -import React, { PropsWithChildren } from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { - defaultConfigLoader, - OptionallyWrapInRouter, - createApp, -} from './createApp'; +import { defaultConfigLoader } from './defaultConfigLoader'; (process as any).env = { NODE_ENV: 'test' }; const anyEnv = process.env as any; @@ -100,44 +92,3 @@ describe('defaultConfigLoader', () => { ]); }); }); - -describe('OptionallyWrapInRouter', () => { - it('should wrap with router if not yet inside a router', async () => { - const { getByText } = render( - Test, - ); - - expect(getByText('Test')).toBeInTheDocument(); - }); - - it('should not wrap with router if already inside a router', async () => { - const { getByText } = render( - - Test - , - ); - - expect(getByText('Test')).toBeInTheDocument(); - }); -}); - -describe('Optional ThemeProvider', () => { - it('should render app with user-provided ThemeProvider', async () => { - const components = { - NotFoundErrorPage: () => null, - BootErrorPage: () => null, - Progress: () => null, - Router: MemoryRouter, - ErrorBoundaryFallback: () => null, - ThemeProvider: ({ children }: PropsWithChildren<{}>) => ( -
{children}
- ), - }; - - const App = createApp({ components }).getProvider(); - - await renderWithEffects(); - - expect(screen.getByRole('main')).toBeInTheDocument(); - }); -}); diff --git a/packages/core-app-api/src/app/defaultConfigLoader.ts b/packages/core-app-api/src/app/defaultConfigLoader.ts new file mode 100644 index 0000000000..00aee2d367 --- /dev/null +++ b/packages/core-app-api/src/app/defaultConfigLoader.ts @@ -0,0 +1,73 @@ +/* + * 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 { AppConfig } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { AppConfigLoader } from './types'; + +/** + * The default config loader, which expects that config is available at compile-time + * in `process.env.APP_CONFIG`. APP_CONFIG should be an array of config objects as + * returned by the config loader. + * + * It will also load runtime config from the __APP_INJECTED_RUNTIME_CONFIG__ string, + * which can be rewritten at runtime to contain an additional JSON config object. + * If runtime config is present, it will be placed first in the config array, overriding + * other config values. + * + * @public + */ +export const defaultConfigLoader: AppConfigLoader = async ( + // This string may be replaced at runtime to provide additional config. + // It should be replaced by a JSON-serialized config object. + // It's a param so we can test it, but at runtime this will always fall back to default. + runtimeConfigJson: string = '__APP_INJECTED_RUNTIME_CONFIG__', +) => { + const appConfig = process.env.APP_CONFIG; + if (!appConfig) { + throw new Error('No static configuration provided'); + } + if (!Array.isArray(appConfig)) { + throw new Error('Static configuration has invalid format'); + } + const configs = appConfig.slice() as unknown as AppConfig[]; + + // Avoiding this string also being replaced at runtime + if ( + runtimeConfigJson !== + '__app_injected_runtime_config__'.toLocaleUpperCase('en-US') + ) { + try { + const data = JSON.parse(runtimeConfigJson) as JsonObject; + if (Array.isArray(data)) { + configs.push(...data); + } else { + configs.push({ data, context: 'env' }); + } + } catch (error) { + throw new Error(`Failed to load runtime configuration, ${error}`); + } + } + + const windowAppConfig = (window as any).__APP_CONFIG__; + if (windowAppConfig) { + configs.push({ + context: 'window', + data: windowAppConfig, + }); + } + return configs; +}; diff --git a/packages/core-app-api/src/app/index.ts b/packages/core-app-api/src/app/index.ts index ab82774cad..5ea5405632 100644 --- a/packages/core-app-api/src/app/index.ts +++ b/packages/core-app-api/src/app/index.ts @@ -14,5 +14,7 @@ * limitations under the License. */ -export { createApp, defaultConfigLoader } from './createApp'; +export { createApp } from './createApp'; +export { createSpecializedApp } from './createSpecializedApp'; +export { defaultConfigLoader } from './defaultConfigLoader'; export * from './types'; diff --git a/packages/core-app-api/src/app/types.ts b/packages/core-app-api/src/app/types.ts index 407018111b..f2d1976f87 100644 --- a/packages/core-app-api/src/app/types.ts +++ b/packages/core-app-api/src/app/types.ts @@ -27,7 +27,6 @@ import { PluginOutput, } from '@backstage/core-plugin-api'; import { AppConfig } from '@backstage/config'; -import { AppIcons } from './icons'; /** * Props for the `BootErrorPage` component of {@link AppComponents}. @@ -97,7 +96,7 @@ export type AppComponents = { Progress: ComponentType<{}>; Router: ComponentType<{}>; ErrorBoundaryFallback: ComponentType; - ThemeProvider: ComponentType<{}>; + ThemeProvider?: ComponentType<{}>; /** * An optional sign-in page that will be rendered instead of the AppRouter at startup. @@ -111,6 +110,36 @@ export type AppComponents = { SignInPage?: ComponentType; }; +/** + * A set of well-known icons that should be available within an app. + * + * @public + */ +export type AppIcons = { + 'kind:api': IconComponent; + 'kind:component': IconComponent; + 'kind:domain': IconComponent; + 'kind:group': IconComponent; + 'kind:location': IconComponent; + 'kind:system': IconComponent; + 'kind:user': IconComponent; + + brokenImage: IconComponent; + catalog: IconComponent; + chat: IconComponent; + dashboard: IconComponent; + docs: IconComponent; + email: IconComponent; + github: IconComponent; + group: IconComponent; + help: IconComponent; + scaffolder: IconComponent; + search: IconComponent; + techdocs: IconComponent; + user: IconComponent; + warning: IconComponent; +}; + /** * A function that loads in the App config that will be accessible via the ConfigApi. * @@ -196,14 +225,23 @@ export type BackstagePluginWithAnyOutput = Omit< export type AppOptions = { /** * A collection of ApiFactories to register in the application to either - * add add new ones, or override factories provided by default or by plugins. + * add new ones, or override factories provided by default or by plugins. */ apis?: Iterable; + /** + * A collection of ApiFactories to register in the application as default APIs. + * Theses APIs can not be overridden by plugin factories, but can be overridden + * by plugin APIs provided through the + * A collection of ApiFactories to register in the application to either + * add new ones, or override factories provided by default or by plugins. + */ + defaultApis?: Iterable; + /** * Supply icons to override the default ones. */ - icons?: Partial & { [key in string]: IconComponent }; + icons: AppIcons & { [key in string]: IconComponent }; /** * A list of all plugins to include in the app. @@ -213,7 +251,7 @@ export type AppOptions = { /** * Supply components to the app to override the default ones. */ - components?: Partial; + components: AppComponents; /** * Themes provided as a part of the app. By default two themes are included, one @@ -226,18 +264,26 @@ export type AppOptions = { * id: 'light', * title: 'Light Theme', * variant: 'light', - * theme: lightTheme, * icon: , + * Provider: ({ children }) => ( + * + * {children} + * + * ), * }, { * id: 'dark', * title: 'Dark Theme', * variant: 'dark', - * theme: darkTheme, * icon: , + * Provider: ({ children }) => ( + * + * {children} + * + * ), * }] * ``` */ - themes?: AppTheme[]; + themes: (Partial & Omit)[]; /** * A function that loads in App configuration that will be accessible via 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 7e448586e3..baf89a59d8 100644 --- a/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ b/packages/core-app-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import ProviderIcon from '@material-ui/icons/AcUnit'; import { DefaultAuthConnector } from './DefaultAuthConnector'; import MockOAuthApi from '../../apis/implementations/OAuthRequestApi/MockOAuthApi'; import * as loginPopup from '../loginPopup'; @@ -29,7 +28,7 @@ const defaultOptions = { provider: { id: 'my-provider', title: 'My Provider', - icon: ProviderIcon, + icon: () => null, }, oauthRequestApi: new MockOAuthApi(), sessionTransform: ({ expiresInSeconds, ...res }: any) => ({ diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index af03762094..1e62df337d 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/core-components +## 0.7.3 + +### Patch Changes + +- c11a37710a: Added a warning variant to `DismissableBanner` component. If you are using a + custom theme, you will need to add the optional `palette.banner.warning` color, + otherwise this variant will fall back to the `palette.banner.error` color. +- 5826c17b7d: Allow for `cellStyle` property on `TableColumn` to be a function as well as `React.CSSProperties` as per the Material UI Table component +- e0861b92ff: Add new way to override color selection to progress bar/gauge components. + + `Gauge`, `LinearGauge` and `GaugeCard` all accept a `getColor` prop, + which is a function of the type: + + ```ts + export type GaugePropsGetColor = (args: { + palette: Palette; + value: number; + inverse?: boolean; + max?: number; + }) => string; + ``` + + Return a standard CSS color string (e.g. "red", "#f02020"), and the gauge will + be set to that color. + + If the prop is omitted, the default implementation is unchanged from previous + versions. + +- 021986e8a3: fixed route resolving (issue #7741) when user cannot select a tab in any of the tabbed pages (like the Catalog page) if it shares the same initial letters as a preceding tab. (i.e. where tab with a path of /ci is followed by a path of /ci-2, user cannot select /ci-2 as /ci will always be selected first). +- a39a2105ef: Add Theme Overrides for Sidebar +- Updated dependencies + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + ## 0.7.2 ### Patch Changes diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index cc884c9e8f..0f1e3510ae 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -7,6 +7,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstageIdentityApi } from '@backstage/core-plugin-api'; +import { BackstagePalette } from '@backstage/theme'; import { BackstageTheme } from '@backstage/theme'; import { ButtonProps as ButtonProps_2 } from '@material-ui/core/Button'; import { CardHeaderProps } from '@material-ui/core/CardHeader'; @@ -100,7 +101,7 @@ export type BottomLinkProps = { // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export function Breadcrumbs(props: Props_21): JSX.Element; +export function Breadcrumbs(props: Props_20): JSX.Element; // @public (undocumented) export type BreadcrumbsClickableTextClassKey = 'root'; @@ -157,7 +158,7 @@ export interface CodeSnippetProps { // Warning: (ae-missing-release-tag) "Content" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function Content(props: PropsWithChildren): JSX.Element; +export function Content(props: PropsWithChildren): JSX.Element; // Warning: (ae-forgotten-export) The symbol "ContentHeaderProps" needs to be exported by the entry point index.d.ts // @@ -368,10 +369,8 @@ export function FeatureCalloutCircular( // @public (undocumented) export type FiltersContainerClassKey = 'root' | 'title'; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// // @public (undocumented) -export function Gauge(props: Props_11): JSX.Element; +export function Gauge(props: GaugeProps): JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // @@ -384,6 +383,27 @@ export type GaugeCardClassKey = 'root'; // @public (undocumented) export type GaugeClassKey = 'root' | 'overlay' | 'circle' | 'colorUnknown'; +// @public (undocumented) +export type GaugeProps = { + value: number; + fractional?: boolean; + inverse?: boolean; + unit?: string; + max?: number; + getColor?: GaugePropsGetColor; +}; + +// @public (undocumented) +export type GaugePropsGetColor = (args: GaugePropsGetColorOptions) => string; + +// @public (undocumented) +export type GaugePropsGetColorOptions = { + palette: BackstagePalette; + value: number; + inverse?: boolean; + max?: number; +}; + // @public (undocumented) export function GitHubIcon(props: IconComponentProps): JSX.Element; @@ -393,7 +413,7 @@ export function GroupIcon(props: IconComponentProps): JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export function Header(props: PropsWithChildren): JSX.Element; +export function Header(props: PropsWithChildren): JSX.Element; // @public (undocumented) export type HeaderClassKey = @@ -484,7 +504,7 @@ export type IconLinkVerticalProps = { // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export function InfoCard(props: Props_16): JSX.Element; +export function InfoCard(props: Props_15): JSX.Element; // @public (undocumented) export type InfoCardClassKey = @@ -565,7 +585,7 @@ export type LifecycleClassKey = 'alpha' | 'beta'; // Warning: (ae-missing-release-tag) "LinearGauge" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function LinearGauge(props: Props_12): JSX.Element | null; +export function LinearGauge(props: Props_11): JSX.Element | null; // Warning: (ae-missing-release-tag) "LinkType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -666,7 +686,7 @@ export type OverflowTooltipClassKey = 'container'; // Warning: (ae-missing-release-tag) "Page" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function Page(props: PropsWithChildren): JSX.Element; +export function Page(props: PropsWithChildren): JSX.Element; // Warning: (ae-missing-release-tag) "PageClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -771,7 +791,7 @@ export type SelectInputBaseClassKey = 'root' | 'input'; // Warning: (ae-missing-release-tag) "Sidebar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function Sidebar(props: PropsWithChildren): JSX.Element; +export function Sidebar(props: PropsWithChildren): JSX.Element; // Warning: (ae-missing-release-tag) "SIDEBAR_INTRO_LOCAL_STORAGE" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -1961,7 +1981,7 @@ export const SidebarSpacer: React_2.ComponentType< // Warning: (ae-missing-release-tag) "SignInPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function SignInPage(props: Props_19): JSX.Element; +export function SignInPage(props: Props_18): JSX.Element; // Warning: (ae-missing-release-tag) "SignInPageClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -2050,7 +2070,7 @@ export function StatusWarning(props: PropsWithChildren<{}>): JSX.Element; // Warning: (ae-missing-release-tag) "StructuredMetadataTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function StructuredMetadataTable(props: Props_13): JSX.Element; +export function StructuredMetadataTable(props: Props_12): JSX.Element; // Warning: (ae-missing-release-tag) "StructuredMetadataTableListClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -2132,7 +2152,7 @@ export type TabBarClassKey = 'indicator' | 'flexContainer' | 'root'; // Warning: (ae-missing-release-tag) "TabbedCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function TabbedCard(props: PropsWithChildren): JSX.Element; +export function TabbedCard(props: PropsWithChildren): JSX.Element; // Warning: (ae-missing-release-tag) "TabbedCardClassKey" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 7f50af7488..ffbde0072a 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.7.2", + "version": "0.7.3", "private": false, "publishConfig": { "access": "public", @@ -30,9 +30,9 @@ }, "dependencies": { "@backstage/config": "^0.1.11", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.4", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-table/core": "^3.1.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -67,9 +67,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/core-app-api": "^0.1.19", - "@backstage/cli": "^0.8.1", - "@backstage/test-utils": "^0.1.20", + "@backstage/core-app-api": "^0.1.20", + "@backstage/cli": "^0.8.2", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx b/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx index 3481c48cef..401f502697 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.stories.tsx @@ -53,3 +53,9 @@ export const AbsoluteProgress = () => (
); + +export const StaticColor = () => ( +
+ '#f0f'} value={0.5} /> +
+); diff --git a/packages/core-components/src/components/ProgressBars/Gauge.test.tsx b/packages/core-components/src/components/ProgressBars/Gauge.test.tsx index 066e1233a0..2d9e852e6c 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.test.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.test.tsx @@ -56,16 +56,26 @@ describe('', () => { }; it('colors the progress correctly', () => { - expect(getProgressColor(palette, 'Not a Number' as any)).toBe('#ddd'); - expect(getProgressColor(palette, 10)).toBe(error); - expect(getProgressColor(palette, 50)).toBe(warning); - expect(getProgressColor(palette, 90)).toBe(ok); + expect(getProgressColor({ palette, value: 'Not a Number' as any })).toBe( + '#ddd', + ); + expect(getProgressColor({ palette, value: 10 })).toBe(error); + expect(getProgressColor({ palette, value: 50 })).toBe(warning); + expect(getProgressColor({ palette, value: 90 })).toBe(ok); }); it('colors the inverse progress correctly', () => { - expect(getProgressColor(palette, 'Not a Number' as any)).toBe('#ddd'); - expect(getProgressColor(palette, 10, true)).toBe(ok); - expect(getProgressColor(palette, 50, true)).toBe(warning); - expect(getProgressColor(palette, 90, true)).toBe(error); + expect( + getProgressColor({ + palette, + value: 'Not a Number' as any, + inverse: true, + }), + ).toBe('#ddd'); + expect(getProgressColor({ palette, value: 10, inverse: true })).toBe(ok); + expect(getProgressColor({ palette, value: 50, inverse: true })).toBe( + warning, + ); + expect(getProgressColor({ palette, value: 90, inverse: true })).toBe(error); }); }); diff --git a/packages/core-components/src/components/ProgressBars/Gauge.tsx b/packages/core-components/src/components/ProgressBars/Gauge.tsx index 83e8d05b16..00e7b3257b 100644 --- a/packages/core-components/src/components/ProgressBars/Gauge.tsx +++ b/packages/core-components/src/components/ProgressBars/Gauge.tsx @@ -15,7 +15,7 @@ */ import { makeStyles, useTheme } from '@material-ui/core/styles'; -import { BackstageTheme } from '@backstage/theme'; +import { BackstagePalette, BackstageTheme } from '@backstage/theme'; import { Circle } from 'rc-progress'; import React from 'react'; @@ -46,32 +46,45 @@ const useStyles = makeStyles( { name: 'BackstageGauge' }, ); -type Props = { +/** @public */ +export type GaugeProps = { value: number; fractional?: boolean; inverse?: boolean; unit?: string; max?: number; + getColor?: GaugePropsGetColor; }; -const defaultProps = { +/** @public */ +export type GaugePropsGetColorOptions = { + palette: BackstagePalette; + value: number; + inverse?: boolean; + max?: number; +}; + +/** @public */ +export type GaugePropsGetColor = (args: GaugePropsGetColorOptions) => string; + +const defaultGaugeProps = { fractional: true, inverse: false, unit: '%', max: 100, }; -export function getProgressColor( - palette: BackstageTheme['palette'], - value: number, - inverse?: boolean, - max?: number, -) { +export const getProgressColor: GaugePropsGetColor = ({ + palette, + value, + inverse, + max, +}) => { if (isNaN(value)) { return '#ddd'; } - const actualMax = max ? max : defaultProps.max; + const actualMax = max ? max : defaultGaugeProps.max; const actualValue = inverse ? actualMax - value : value; if (actualValue < actualMax / 3) { @@ -81,14 +94,15 @@ export function getProgressColor( } return palette.status.ok; -} +}; /** @public */ -export function Gauge(props: Props) { +export function Gauge(props: GaugeProps) { + const { getColor = getProgressColor } = props; const classes = useStyles(props); - const theme = useTheme(); + const { palette } = useTheme(); const { value, fractional, inverse, unit, max } = { - ...defaultProps, + ...defaultGaugeProps, ...props, }; @@ -102,7 +116,7 @@ export function Gauge(props: Props) { percent={asPercentage} strokeWidth={12} trailWidth={12} - strokeColor={getProgressColor(theme.palette, asActual, inverse, max)} + strokeColor={getColor({ palette, value: asActual, inverse, max })} className={classes.circle} />
diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx index ad3323f8d3..64a6cc1b16 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.stories.tsx @@ -97,3 +97,24 @@ export const LinkInFooter = () => ( ); + +export const StaticColor = () => ( + + + '#f00'} title="Red" progress={0.5} /> + + + '#0f0'} title="Green" progress={0.5} /> + + + '#00f'} title="Blue" progress={0.5} /> + + + palette.status.error} + title="palette.status.error" + progress={0.5} + /> + + +); diff --git a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx index fe17de30cd..0fa51b3919 100644 --- a/packages/core-components/src/components/ProgressBars/GaugeCard.tsx +++ b/packages/core-components/src/components/ProgressBars/GaugeCard.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { makeStyles } from '@material-ui/core/styles'; import { InfoCard, InfoCardVariants } from '../../layout/InfoCard'; import { BottomLinkProps } from '../../layout/BottomLink'; -import { Gauge } from './Gauge'; +import { Gauge, GaugePropsGetColor } from './Gauge'; type Props = { title: string; @@ -28,6 +28,7 @@ type Props = { progress: number; inverse?: boolean; deepLink?: BottomLinkProps; + getColor?: GaugePropsGetColor; }; /** @public */ @@ -46,7 +47,14 @@ const useStyles = makeStyles( /** @public */ export function GaugeCard(props: Props) { const classes = useStyles(props); - const { title, subheader, progress, inverse, deepLink, variant } = props; + const { title, subheader, progress, inverse, deepLink, variant, getColor } = + props; + + const gaugeProps = { + inverse, + getColor, + value: progress, + }; return (
@@ -56,7 +64,7 @@ export function GaugeCard(props: Props) { deepLink={deepLink} variant={variant} > - +
); diff --git a/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx b/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx index ee2ed2eae6..5d36ea9178 100644 --- a/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx +++ b/packages/core-components/src/components/ProgressBars/LinearGauge.stories.tsx @@ -41,3 +41,9 @@ export const LowProgress = () => (
); + +export const StaticColor = () => ( +
+ '#f0f'} value={0.5} /> +
+); diff --git a/packages/core-components/src/components/ProgressBars/LinearGauge.tsx b/packages/core-components/src/components/ProgressBars/LinearGauge.tsx index afc93fd337..f1e4fb3334 100644 --- a/packages/core-components/src/components/ProgressBars/LinearGauge.tsx +++ b/packages/core-components/src/components/ProgressBars/LinearGauge.tsx @@ -20,18 +20,19 @@ import Tooltip from '@material-ui/core/Tooltip'; // @ts-ignore import { Line } from 'rc-progress'; import { BackstageTheme } from '@backstage/theme'; -import { getProgressColor } from './Gauge'; +import { getProgressColor, GaugePropsGetColor } from './Gauge'; type Props = { /** * Progress value between 0.0 - 1.0. */ value: number; + getColor?: GaugePropsGetColor; }; export function LinearGauge(props: Props) { - const { value } = props; - const theme = useTheme(); + const { value, getColor = getProgressColor } = props; + const { palette } = useTheme(); if (isNaN(value)) { return null; } @@ -39,7 +40,12 @@ export function LinearGauge(props: Props) { if (percent > 100) { percent = 100; } - const strokeColor = getProgressColor(theme.palette, percent, false, 100); + const strokeColor = getColor({ + palette, + value: percent, + inverse: false, + max: 100, + }); return ( diff --git a/packages/core-components/src/components/ProgressBars/index.ts b/packages/core-components/src/components/ProgressBars/index.ts index 3d821eaac2..5e588ea696 100644 --- a/packages/core-components/src/components/ProgressBars/index.ts +++ b/packages/core-components/src/components/ProgressBars/index.ts @@ -17,5 +17,10 @@ export { GaugeCard } from './GaugeCard'; export type { GaugeCardClassKey } from './GaugeCard'; export { Gauge } from './Gauge'; +export type { + GaugeProps, + GaugePropsGetColor, + GaugePropsGetColorOptions, +} from './Gauge'; export type { GaugeClassKey } from './Gauge'; export { LinearGauge } from './LinearGauge'; diff --git a/packages/core-components/src/layout/Page/Page.stories.tsx b/packages/core-components/src/layout/Page/Page.stories.tsx index 7386d198a3..12ddd51d04 100644 --- a/packages/core-components/src/layout/Page/Page.stories.tsx +++ b/packages/core-components/src/layout/Page/Page.stories.tsx @@ -20,8 +20,7 @@ import Grid from '@material-ui/core/Grid'; import Link from '@material-ui/core/Link'; import Typography from '@material-ui/core/Typography'; import React, { useState } from 'react'; -import { MemoryRouter } from 'react-router'; -import { createApp } from '@backstage/core-app-api'; +import { wrapInTestApp } from '@backstage/test-utils'; import { Content, ContentHeader, @@ -197,53 +196,44 @@ const ExampleContentHeader = ({ selectedTab }: { selectedTab?: number }) => ( ); -const app = createApp({ configLoader: async () => [] }); -const AppProvider = app.getProvider(); - export const PluginWithData = () => { const [selectedTab, setSelectedTab] = useState(2); - return ( - - -
- - - setSelectedTab(index)} - tabs={tabs.map(({ label }, index) => ({ - id: index.toString(), - label, - }))} - /> - - - - - -
-
-
- ); + return wrapInTestApp(() => ( +
+ + + setSelectedTab(index)} + tabs={tabs.map(({ label }, index) => ({ + id: index.toString(), + label, + }))} + /> + + + + + +
+ )); }; export const PluginWithTable = () => { - return ( - -
- - - - - - - - - - ); + return wrapInTestApp(() => ( +
+ + + + +
+ + + + )); }; diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 9d8246f931..d92922ddb4 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/core-plugin-api +## 0.1.13 + +### Patch Changes + +- 4a336fd292: Deprecate use of extensions without name. Adds a warning to the developer console to prompt integrators to provide names for extensions. +- 8b4284cd5c: Improve API documentation for @backstage/core-plugin-api +- e059aea7b9: Deprecate unused ApiRef types +- Updated dependencies + - @backstage/theme@0.2.13 + ## 0.1.12 ### Patch Changes diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index acfc8d4a45..2765b02bca 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -5,11 +5,14 @@ ```ts /// +import { BackstagePlugin as BackstagePlugin_2 } from '@backstage/core-plugin-api'; import { BackstageTheme } from '@backstage/theme'; import { ComponentType } from 'react'; import { Config } from '@backstage/config'; +import { IconComponent as IconComponent_2 } from '@backstage/core-plugin-api'; import { Observable as Observable_2 } from '@backstage/types'; import { Observer as Observer_2 } from '@backstage/types'; +import { ProfileInfo as ProfileInfo_2 } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { ReactElement } from 'react'; import { ReactNode } from 'react'; @@ -161,13 +164,14 @@ export type AppComponents = { Progress: ComponentType<{}>; Router: ComponentType<{}>; ErrorBoundaryFallback: ComponentType; + ThemeProvider?: ComponentType<{}>; SignInPage?: ComponentType; }; // @public export type AppContext = { - getPlugins(): BackstagePlugin[]; - getSystemIcon(key: string): IconComponent | undefined; + getPlugins(): BackstagePlugin_2[]; + getSystemIcon(key: string): IconComponent_2 | undefined; getComponents(): AppComponents; }; @@ -178,6 +182,7 @@ export type AppTheme = { variant: 'light' | 'dark'; theme: BackstageTheme; icon?: React.ReactElement; + Provider?(props: { children: ReactNode }): JSX.Element | null; }; // @public @@ -421,7 +426,7 @@ export const errorApiRef: ApiRef; // @public export type ErrorBoundaryFallbackProps = { - plugin?: BackstagePlugin; + plugin?: BackstagePlugin_2; error: Error; resetError: () => void; }; @@ -741,7 +746,7 @@ export type SignInPageProps = { // @public export type SignInResult = { userId: string; - profile: ProfileInfo; + profile: ProfileInfo_2; getIdToken?: () => Promise; signOut?: () => Promise; }; @@ -799,9 +804,6 @@ export function useElementFilter( dependencies?: any[], ): T; -// @public -export type UserFlags = {}; - // @public export function useRouteRef( routeRef: ExternalRouteRef, diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 23bf0fc8b0..6679e89645 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "0.1.12", + "version": "0.1.13", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.11", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@backstage/types": "^0.1.1", "@backstage/version-bridge": "^0.1.0", "@material-ui/core": "^4.12.2", @@ -43,9 +43,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", - "@backstage/test-utils": "^0.1.20", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts index de523663f2..8053c449a1 100644 --- a/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/AppThemeApi.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ReactNode } from 'react'; import { ApiRef, createApiRef } from '../system'; import { BackstageTheme } from '@backstage/theme'; import { Observable } from '@backstage/types'; @@ -41,6 +42,7 @@ export type AppTheme = { /** * The specialized MaterialUI theme instance. + * @deprecated use Provider instead, see https://backstage.io/docs/api/deprecations#app-theme */ theme: BackstageTheme; @@ -48,6 +50,8 @@ export type AppTheme = { * An Icon for the theme mode setting. */ icon?: React.ReactElement; + + Provider?(props: { children: ReactNode }): JSX.Element | null; }; /** diff --git a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts index c669710895..ac43bbebe8 100644 --- a/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts +++ b/packages/core-plugin-api/src/apis/definitions/FeatureFlagsApi.ts @@ -61,13 +61,6 @@ export type FeatureFlagsSaveOptions = { merge?: boolean; }; -/** - * User flags alias. - * - * @public - */ -export type UserFlags = {}; - /** * The feature flags API is used to toggle functionality to users across plugins and Backstage. * diff --git a/packages/core-plugin-api/src/app/types.ts b/packages/core-plugin-api/src/app/types.ts index d072f0a2e6..df78728a21 100644 --- a/packages/core-plugin-api/src/app/types.ts +++ b/packages/core-plugin-api/src/app/types.ts @@ -14,110 +14,15 @@ * limitations under the License. */ -import { ComponentType } from 'react'; -import { ProfileInfo } from '../apis/definitions'; -import { IconComponent } from '../icons'; -import { BackstagePlugin } from '../plugin/types'; - -/** - * Props for the BootErrorPage. - * - * @public - */ -export type BootErrorPageProps = { - step: 'load-config' | 'load-chunk'; - error: Error; -}; - -/** - * Data and handlers associated with the user sign in event. - * - * @public - */ -export type SignInResult = { - /** - * User ID that will be returned by the IdentityApi - */ - userId: string; - - profile: ProfileInfo; - - /** - * Function used to retrieve an ID token for the signed in user. - */ - getIdToken?: () => Promise; - - /** - * Sign out handler that will be called if the user requests to sign out. - */ - signOut?: () => Promise; -}; - -/** - * Props for the SignInPage. - * - * @public - */ -export type SignInPageProps = { - /** - * Set the sign-in result for the app. This should only be called once. - */ - onResult(result: SignInResult): void; -}; - -/** - * Props for the ErrorBoundaryFallback. - * - * @public - */ -export type ErrorBoundaryFallbackProps = { - plugin?: BackstagePlugin; - error: Error; - resetError: () => void; -}; - -/** - * Basic app components. - * - * @public - */ -export type AppComponents = { - NotFoundErrorPage: ComponentType<{}>; - BootErrorPage: ComponentType; - Progress: ComponentType<{}>; - Router: ComponentType<{}>; - ErrorBoundaryFallback: ComponentType; - - /** - * An optional sign-in page that will be rendered instead of the AppRouter at startup. - * - * If a sign-in page is set, it will always be shown before the app, and it is up - * to the sign-in page to handle e.g. saving of login methods for subsequent visits. - * - * The sign-in page will be displayed until it has passed up a result to the parent, - * and which point the AppRouter and all of its children will be rendered instead. - */ - SignInPage?: ComponentType; -}; - -/** - * Provides plugins and components registered in the app. - * - * @public - */ -export type AppContext = { - /** - * Get a list of all plugins that are installed in the app. - */ - getPlugins(): BackstagePlugin[]; - - /** - * Get a common or custom icon for this app. - */ - getSystemIcon(key: string): IconComponent | undefined; - - /** - * Get the components registered for various purposes in the app. - */ - getComponents(): AppComponents; -}; +// This is a bit of a hack that we use to avoid having to redeclare these types +// within this package or have an explicit dependency on core-app-api. +// These types end up being inlined and duplicated into this package at build time. +// eslint-disable-next-line no-restricted-imports +export type { + BootErrorPageProps, + SignInResult, + SignInPageProps, + ErrorBoundaryFallbackProps, + AppComponents, + AppContext, +} from '../../../core-app-api/src/app/types'; diff --git a/packages/core-plugin-api/src/extensions/extensions.test.tsx b/packages/core-plugin-api/src/extensions/extensions.test.tsx index 29c696c71f..dcc92d9d47 100644 --- a/packages/core-plugin-api/src/extensions/extensions.test.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.test.tsx @@ -41,6 +41,7 @@ describe('extensions', () => { const Component = () =>
; const extension = createReactExtension({ + name: 'Extension', component: { sync: Component, }, @@ -67,6 +68,7 @@ describe('extensions', () => { }); const extension2 = createRoutableExtension({ + name: 'Extension2', component: () => Promise.resolve(Component), mountPoint: routeRef, }); diff --git a/packages/core-plugin-api/src/extensions/extensions.tsx b/packages/core-plugin-api/src/extensions/extensions.tsx index f85570a35b..ca5ada8478 100644 --- a/packages/core-plugin-api/src/extensions/extensions.tsx +++ b/packages/core-plugin-api/src/extensions/extensions.tsx @@ -55,7 +55,7 @@ export function createRoutableExtension< mountPoint: RouteRef; name?: string; }): Extension { - const { component, mountPoint } = options; + const { component, mountPoint, name } = options; return createReactExtension({ component: { lazy: () => @@ -85,7 +85,7 @@ export function createRoutableExtension< }; const componentName = - options.name || + name || (InnerComponent as { displayName?: string }).displayName || InnerComponent.name || 'LazyComponent'; @@ -108,7 +108,7 @@ export function createRoutableExtension< data: { 'core.mountPoint': mountPoint, }, - name: options.name, + name, }); } @@ -152,7 +152,14 @@ export function createReactExtension< data?: Record; name?: string; }): Extension { - const { data = {} } = options; + const { data = {}, name } = options; + if (!name) { + // eslint-disable-next-line no-console + console.warn( + 'Declaring extensions without name is DEPRECATED. ' + + 'Make sure that all usages of createReactExtension, createComponentExtension and createRoutableExtension provide a name.', + ); + } let Component: T; if ('lazy' in options.component) { @@ -164,7 +171,7 @@ export function createReactExtension< Component = options.component.sync; } const componentName = - options.name || + name || (Component as { displayName?: string }).displayName || Component.name || 'Component'; @@ -186,7 +193,7 @@ export function createReactExtension< diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 95d99d0532..7e53d0cde5 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/create-app +## 0.4.3 + +### Patch Changes + +- 5dcea2586c: Integrated `SidebarSearchModal` component into default-app to use the `SearchModal`. + + The `SidebarSearchModal` component can also be used in other generated apps: + + ```diff + import { + - SidebarSearch, + + SidebarSearchModal + } from '@backstage/plugin-search'; + ... + + + + - + + + + ... + ``` + + If you only want to use the `SearchModal` you can import it from `'@backstage/plugin-search'`: + + ```js + import { SearchModal } from '@backstage/plugin-search'; + ``` + +- 5725f87e4c: Updated the app template to no longer include the `--no-private` flag for the `create-plugin` command. + + To apply this change to an existing application, remove the `--no-private` flag from the `create-plugin` command in the root `package.json`: + + ```diff + "prettier:check": "prettier --check .", + - "create-plugin": "backstage-cli create-plugin --scope internal --no-private", + + "create-plugin": "backstage-cli create-plugin --scope internal", + "remove-plugin": "backstage-cli remove-plugin" + ``` + +- 1921f70aa7: Removed the version pinning of the packages `graphql-language-service-interface` and `graphql-language-service-parser`. This should no longer be necessary. + + You can apply the same change in your repository by ensuring that the following does _NOT_ appear in your root `package.json`. + + ```json + "resolutions": { + "graphql-language-service-interface": "2.8.2", + "graphql-language-service-parser": "1.9.0" + }, + ``` + ## 0.4.2 ## 0.4.1 diff --git a/packages/create-app/package.json b/packages/create-app/package.json index ad4ab8dabd..2820d41ed6 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.4.2", + "version": "0.4.3", "private": false, "publishConfig": { "access": "public" diff --git a/packages/create-app/src/lib/versions.ts b/packages/create-app/src/lib/versions.ts index 97904cc15b..6c85652c64 100644 --- a/packages/create-app/src/lib/versions.ts +++ b/packages/create-app/src/lib/versions.ts @@ -30,6 +30,7 @@ Rollup will extract the value of the version field in each package at build time leaving any imports in place. */ +import { version as appDefaults } from '../../../app-defaults/package.json'; import { version as backendCommon } from '../../../backend-common/package.json'; import { version as catalogClient } from '../../../catalog-client/package.json'; import { version as catalogModel } from '../../../catalog-model/package.json'; @@ -68,6 +69,7 @@ import { version as pluginTechdocsBackend } from '../../../../plugins/techdocs-b import { version as pluginUserSettings } from '../../../../plugins/user-settings/package.json'; export const packageVersions = { + '@backstage/app-defaults': appDefaults, '@backstage/backend-common': backendCommon, '@backstage/catalog-client': catalogClient, '@backstage/catalog-model': catalogModel, diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index c443b45dc7..9e15608a01 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -4,6 +4,7 @@ "private": true, "bundled": true, "dependencies": { + "@backstage/app-defaults": "^{{version '@backstage/app-defaults'}}", "@backstage/catalog-model": "^{{version '@backstage/catalog-model'}}", "@backstage/cli": "^{{version '@backstage/cli'}}", "@backstage/core-app-api": "^{{version '@backstage/core-app-api'}}", diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 4cd83685a6..8a535835b4 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -26,7 +26,8 @@ import { searchPage } from './components/search/SearchPage'; import { Root } from './components/Root'; import { AlertDisplay, OAuthRequestDialog } from '@backstage/core-components'; -import { createApp, FlatRoutes } from '@backstage/core-app-api'; +import { createApp } from '@backstage/app-defaults'; +import { FlatRoutes } from '@backstage/core-app-api'; const app = createApp({ apis, diff --git a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx index 198e7ec2b1..ec59b0b116 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx @@ -25,7 +25,7 @@ import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; import { NavLink } from 'react-router-dom'; import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; -import { SidebarSearch } from '@backstage/plugin-search'; +import { SidebarSearchModal } from '@backstage/plugin-search'; import { Sidebar, SidebarPage, @@ -74,7 +74,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( - + {/* Global nav, not org-specific */} diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index e01390e169..974329e8d4 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -29,6 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { + "@backstage/app-defaults": "^0.1.0", "@backstage/core-app-api": "^0.1.18", "@backstage/core-components": "^0.7.1", "@backstage/core-plugin-api": "^0.1.11", @@ -44,10 +45,12 @@ "@testing-library/user-event": "^13.1.8", "@types/react": "*", "react": "^16.12.0", + "react-use": "^17.2.4", "react-dom": "^16.12.0", "react-hot-loader": "^4.12.21", "react-router": "6.0.0-beta.0", - "react-router-dom": "6.0.0-beta.0" + "react-router-dom": "6.0.0-beta.0", + "zen-observable": "^0.8.15" }, "devDependencies": { "@backstage/cli": "^0.8.0", diff --git a/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx b/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx new file mode 100644 index 0000000000..3907dc1007 --- /dev/null +++ b/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { AppThemeApi, appThemeApiRef } from '@backstage/core-plugin-api'; +import { renderInTestApp } from '@backstage/test-utils'; +import { BackstageTheme } from '@backstage/theme'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import ObservableImpl from 'zen-observable'; +import { SidebarThemeSwitcher } from './SidebarThemeSwitcher'; + +describe('SidebarThemeSwitcher', () => { + let appThemeApi: jest.Mocked; + let apiRegistry: ApiRegistry; + + beforeEach(() => { + appThemeApi = { + activeThemeId$: jest.fn(), + getActiveThemeId: jest.fn(), + getInstalledThemes: jest.fn(), + setActiveThemeId: jest.fn(), + }; + + appThemeApi.activeThemeId$.mockReturnValue( + ObservableImpl.of('dark'), + ); + appThemeApi.getInstalledThemes.mockReturnValue([ + { + id: 'dark', + title: 'Dark Theme', + variant: 'dark', + theme: {} as unknown as BackstageTheme, + }, + { + id: 'light', + title: 'Light Theme', + variant: 'light', + theme: {} as unknown as BackstageTheme, + }, + ]); + + apiRegistry = ApiRegistry.with(appThemeApiRef, appThemeApi); + }); + + it('should display current theme', async () => { + const { getByLabelText, getByRole, getByText } = await renderInTestApp( + + + , + ); + + const button = getByLabelText('Switch Theme'); + expect(button).toBeInTheDocument(); + + userEvent.click(button); + + expect(getByRole('listbox')).toBeInTheDocument(); + expect(getByText('Dark Theme')).toBeInTheDocument(); + expect( + getByText('Dark Theme').parentElement?.parentElement, + ).toHaveAttribute('aria-selected', 'true'); + }); + + it('should select different theme', async () => { + const { getByLabelText, getByRole, getByText } = await renderInTestApp( + + + , + ); + + const button = getByLabelText('Switch Theme'); + expect(button).toBeInTheDocument(); + + userEvent.click(button); + + expect(getByRole('listbox')).toBeInTheDocument(); + + userEvent.click(getByText('Light Theme')); + + expect(appThemeApi.setActiveThemeId).toHaveBeenCalledWith('light'); + }); +}); diff --git a/packages/dev-utils/src/devApp/SidebarThemeSwitcher.tsx b/packages/dev-utils/src/devApp/SidebarThemeSwitcher.tsx new file mode 100644 index 0000000000..e13554bbf7 --- /dev/null +++ b/packages/dev-utils/src/devApp/SidebarThemeSwitcher.tsx @@ -0,0 +1,125 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { SidebarItem } from '@backstage/core-components'; +import { appThemeApiRef, useApi } from '@backstage/core-plugin-api'; +import { ListItemIcon, ListItemText, Menu, MenuItem } from '@material-ui/core'; +import AutoIcon from '@material-ui/icons/BrightnessAuto'; +import React, { cloneElement, useCallback, useState } from 'react'; +import { useObservable } from 'react-use'; + +type ThemeIconProps = { + active?: boolean; + icon: JSX.Element | undefined; +}; + +const ThemeIcon = ({ active, icon }: ThemeIconProps) => + icon ? ( + cloneElement(icon, { + color: active ? 'primary' : undefined, + }) + ) : ( + + ); + +export const SidebarThemeSwitcher = () => { + const appThemeApi = useApi(appThemeApiRef); + const themeId = useObservable( + appThemeApi.activeThemeId$(), + appThemeApi.getActiveThemeId(), + ); + const themeIds = appThemeApi.getInstalledThemes(); + const activeTheme = themeIds.find(t => t.id === themeId); + + const [anchorEl, setAnchorEl] = useState(); + const open = Boolean(anchorEl); + + const handleOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleSelectTheme = (newThemeId: string | undefined) => { + if (themeIds.some(t => t.id === newThemeId)) { + appThemeApi.setActiveThemeId(newThemeId); + } else { + appThemeApi.setActiveThemeId(undefined); + } + + setAnchorEl(undefined); + }; + + const handleClose = () => { + setAnchorEl(undefined); + }; + + const ActiveIcon = useCallback( + () => , + [activeTheme], + ); + + return ( + <> + + + + Choose a theme + handleSelectTheme(undefined)} + > + + + + Auto + + + {themeIds.map(theme => { + const active = theme.id === themeId; + return ( + handleSelectTheme(theme.id)} + > + + + + {theme.title} + + ); + })} + + + ); +}; diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 755289a060..adea9e14f2 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -14,6 +14,30 @@ * limitations under the License. */ +import { createApp } from '@backstage/app-defaults'; +import { FlatRoutes } from '@backstage/core-app-api'; +import { + AlertDisplay, + OAuthRequestDialog, + Sidebar, + SidebarDivider, + SidebarItem, + SidebarPage, + SidebarSpace, + SidebarSpacer, +} from '@backstage/core-components'; +import { + AnyApiFactory, + ApiFactory, + AppTheme, + attachComponentData, + BackstagePlugin, + configApiRef, + createApiFactory, + createRouteRef, + IconComponent, + RouteRef, +} from '@backstage/core-plugin-api'; import { ScmIntegrationsApi, scmIntegrationsApiRef, @@ -24,30 +48,7 @@ import React, { ComponentType, ReactNode } from 'react'; import ReactDOM from 'react-dom'; import { hot } from 'react-hot-loader'; import { Route } from 'react-router'; - -import { - AlertDisplay, - OAuthRequestDialog, - Sidebar, - SidebarItem, - SidebarPage, - SidebarSpacer, -} from '@backstage/core-components'; - -import { - AnyApiFactory, - ApiFactory, - AppTheme, - attachComponentData, - configApiRef, - createApiFactory, - createRouteRef, - IconComponent, - RouteRef, - BackstagePlugin, -} from '@backstage/core-plugin-api'; - -import { createApp, FlatRoutes } from '@backstage/core-app-api'; +import { SidebarThemeSwitcher } from './SidebarThemeSwitcher'; const GatheringRoute: (props: { path: string; @@ -202,6 +203,9 @@ export class DevAppBuilder { {this.sidebarItems} + + + {this.routes} diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 1b8e321edb..6c941cda58 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -25,7 +25,7 @@ }, "devDependencies": { "@backstage/cli-common": "^0.1.1", - "@backstage/errors": "^0.1.2", + "@backstage/errors": "^0.1.4", "@types/fs-extra": "^9.0.1", "@types/node": "^14.14.32", "@types/puppeteer": "^5.4.4", diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js index 5bdc6b3a2f..72ff42311d 100644 --- a/packages/storybook/.storybook/main.js +++ b/packages/storybook/.storybook/main.js @@ -1,23 +1,30 @@ const path = require('path'); const WebpackPluginFailBuildOnWarning = require('./webpack-plugin-fail-build-on-warning'); +/** + * This set of stories are the ones that we publish to backstage.io. + */ +const BACKSTAGE_CORE_STORIES = [ + 'packages/core-components', + 'plugins/org', + 'plugins/search', +]; + module.exports = ({ args }) => { // Calling storybook with no args causes our default list of stories to be used. // This set of stories are the ones that we publish to backstage.io // // If it's called with args, each arg should be the path to a package that we will // show the stories from, for example `yarn storybook plugins/catalog`. - let stories; - if (args.length === 0) { - stories = [ - '../../core-components/src/**/*.stories.tsx', - '../../../plugins/org/src/**/*.stories.tsx', - '../../../plugins/search/src/**/*.stories.tsx', - ]; - } else { - const rootDir = path.resolve(__dirname, '../../..'); - stories = args.map(arg => path.join(rootDir, arg, 'src/**/*.stories.tsx')); - } + + const rootPath = '../../..'; + const storiesSrcGlob = 'src/**/*.stories.tsx'; + + const getStoriesPath = package => + path.posix.join(rootPath, package, storiesSrcGlob); + + const packages = args.length === 0 ? BACKSTAGE_CORE_STORIES : args; + const stories = packages.map(getStoriesPath); return { stories, diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index b540f74549..87e9e12193 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/techdocs-common +## 0.10.6 + +### Patch Changes + +- a2d4389587: 1. Techdocs publisher constructors now use parameter objects when being + instantiated + + 2. Internal refactor of `LocalPublish` publisher to use `fromConfig` for + creation to be aligned with other publishers; this does not impact + `LocalPublish` usage. + + ```diff + - const publisher = new LocalPublish(config, logger, discovery); + + const publisher = LocalPublish.fromConfig(config, logger, discovery); + ``` + +- 6129c89a47: Default TechDocs container used at docs generation-time is now [v0.3.5](https://github.com/backstage/techdocs-container/releases/tag/v0.3.5). +- f3c7eec64b: Updated to properly join URL segments under any OS for both AWS S3 and GCP +- Updated dependencies + - @backstage/backend-common@0.9.9 + ## 0.10.5 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 152b9b04f1..25577df1dd 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.10.5", + "version": "0.10.6", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -38,7 +38,7 @@ "dependencies": { "@azure/identity": "^1.5.0", "@azure/storage-blob": "^12.5.0", - "@backstage/backend-common": "^0.9.8", + "@backstage/backend-common": "^0.9.9", "@backstage/catalog-model": "^0.9.6", "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.4", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.8.1", + "@backstage/cli": "^0.8.2", "@types/fs-extra": "^9.0.5", "@types/js-yaml": "^4.0.0", "@types/mime-types": "^2.1.0", diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index a1fa9d3b6b..6fcacc30fa 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/test-utils +## 0.1.21 + +### Patch Changes + +- 71fd5cd735: Update Keyboard deprecation with a link to the recommended successor +- Updated dependencies + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/core-app-api@0.1.20 + ## 0.1.20 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 269b7b2d38..6f5f78de8c 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.20", + "version": "0.1.21", "private": false, "publishConfig": { "access": "public", @@ -29,11 +29,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-app-api": "^0.1.19", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-app-api": "^0.1.20", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@backstage/types": "^0.1.1", "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.11.2", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", @@ -45,7 +46,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.8.1", + "@backstage/cli": "^0.8.2", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx index 6f55cc6190..cad1df4ecc 100644 --- a/packages/test-utils/src/testUtils/appWrappers.tsx +++ b/packages/test-utils/src/testUtils/appWrappers.tsx @@ -18,7 +18,10 @@ import React, { ComponentType, ReactNode, ReactElement } from 'react'; import { MemoryRouter } from 'react-router'; import { Route } from 'react-router-dom'; import { lightTheme } from '@backstage/theme'; -import { createApp } from '@backstage/core-app-api'; +import { ThemeProvider } from '@material-ui/core/styles'; +import { CssBaseline } from '@material-ui/core'; +import MockIcon from '@material-ui/icons/AcUnit'; +import { createSpecializedApp } from '@backstage/core-app-api'; import { BootErrorPageProps, RouteRef, @@ -28,8 +31,34 @@ import { } from '@backstage/core-plugin-api'; import { RenderResult } from '@testing-library/react'; import { renderWithEffects } from './testingLibrary'; +import { defaultApis } from './defaultApis'; import { mockApis } from './mockApis'; +const mockIcons = { + 'kind:api': MockIcon, + 'kind:component': MockIcon, + 'kind:domain': MockIcon, + 'kind:group': MockIcon, + 'kind:location': MockIcon, + 'kind:system': MockIcon, + 'kind:user': MockIcon, + + brokenImage: MockIcon, + catalog: MockIcon, + scaffolder: MockIcon, + techdocs: MockIcon, + search: MockIcon, + chat: MockIcon, + dashboard: MockIcon, + docs: MockIcon, + email: MockIcon, + github: MockIcon, + group: MockIcon, + help: MockIcon, + user: MockIcon, + warning: MockIcon, +}; + const ErrorBoundaryFallback = ({ error }: { error: Error }) => { throw new Error(`Reached ErrorBoundaryFallback Page with error, ${error}`); }; @@ -90,8 +119,9 @@ export function wrapInTestApp( const { routeEntries = ['/'] } = options; const boundRoutes = new Map(); - const app = createApp({ + const app = createSpecializedApp({ apis: mockApis, + defaultApis, // Bit of a hack to make sure that the default config loader isn't used // as that would force every single test to wait for config loading. configLoader: false as unknown as undefined, @@ -104,13 +134,18 @@ export function wrapInTestApp( ), }, + icons: mockIcons, plugins: [], themes: [ { id: 'light', - theme: lightTheme, title: 'Test App Theme', variant: 'light', + Provider: ({ children }) => ( + + {children} + + ), }, ], bindRoutes: ({ bind }) => { diff --git a/packages/core-app-api/src/app/defaultApis.ts b/packages/test-utils/src/testUtils/defaultApis.ts similarity index 96% rename from packages/core-app-api/src/app/defaultApis.ts rename to packages/test-utils/src/testUtils/defaultApis.ts index fb02274cf0..f06e1ba6b7 100644 --- a/packages/core-app-api/src/app/defaultApis.ts +++ b/packages/test-utils/src/testUtils/defaultApis.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * Copyright 2021 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ import { OneLoginAuth, UnhandledErrorForwarder, AtlassianAuth, -} from '../apis'; +} from '@backstage/core-app-api'; import { createApiFactory, @@ -59,8 +59,8 @@ import { atlassianAuthApiRef, } from '@backstage/core-plugin-api'; -import OAuth2Icon from '@material-ui/icons/AcUnit'; - +// TODO(Rugvip): This is just a copy of the createApp default APIs for now, but +// we should clean up this list a bit move more things over to mocks. export const defaultApis = [ createApiFactory({ api: discoveryApiRef, @@ -226,7 +226,7 @@ export const defaultApis = [ provider: { id: 'oidc', title: 'Your Identity Provider', - icon: OAuth2Icon, + icon: () => null, }, environment: configApi.getOptionalString('auth.environment'), }), diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index 8709484c79..1c8661d7cc 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/theme +## 0.2.13 + +### Patch Changes + +- c11a37710a: Added a warning variant to `DismissableBanner` component. If you are using a + custom theme, you will need to add the optional `palette.banner.warning` color, + otherwise this variant will fall back to the `palette.banner.error` color. + ## 0.2.12 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index 1acc91de79..d9e89c969f 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/theme", "description": "material-ui theme for use with Backstage.", - "version": "0.2.12", + "version": "0.2.13", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "@material-ui/core": "^4.12.2" }, "devDependencies": { - "@backstage/cli": "^0.8.1" + "@backstage/cli": "^0.8.2" }, "files": [ "dist" diff --git a/plugins/allure/package.json b/plugins/allure/package.json index 63864a7239..b157795eec 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -23,10 +23,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -36,10 +36,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 25534e9577..4970f50c04 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -22,9 +22,9 @@ }, "dependencies": { "@backstage/config": "^0.1.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -34,10 +34,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 188d81fcd1..78f4b08bd7 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-api-docs +## 0.6.13 + +### Patch Changes + +- 044c38e739: Lazy load all API definition widgets. The widgets use libraries like + `swagger-ui`, `graphiql`, and `@asyncapi/react-component` which are quite heavy + weight. To improve initial load times, the widgets are only loaded once used. +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.6.12 ### Patch Changes diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index de36857945..111660ae07 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -84,11 +84,19 @@ export const ApiTypeTitle: ({ apiEntity: ApiEntity; }) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "AsyncApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const AsyncApiDefinitionWidget: ({ definition }: Props_5) => JSX.Element; +export const AsyncApiDefinitionWidget: ( + props: AsyncApiDefinitionWidgetProps, +) => JSX.Element; + +// Warning: (ae-missing-release-tag) "AsyncApiDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type AsyncApiDefinitionWidgetProps = { + definition: string; +}; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ConsumedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -100,7 +108,7 @@ export const ConsumedApisCard: ({ variant }: Props_2) => JSX.Element; // Warning: (ae-missing-release-tag) "ConsumingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ConsumingComponentsCard: ({ variant }: Props_6) => JSX.Element; +export const ConsumingComponentsCard: ({ variant }: Props_5) => JSX.Element; // Warning: (ae-missing-release-tag) "defaultDefinitionWidgets" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -169,20 +177,34 @@ export const EntityProvidingComponentsCard: ({ // @public (undocumented) export const HasApisCard: ({ variant }: Props_3) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "OpenApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const OpenApiDefinitionWidget: ({ definition }: Props_8) => JSX.Element; +export const OpenApiDefinitionWidget: ( + props: OpenApiDefinitionWidgetProps, +) => JSX.Element; + +// Warning: (ae-missing-release-tag) "OpenApiDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type OpenApiDefinitionWidgetProps = { + definition: string; +}; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "PlainApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const PlainApiDefinitionWidget: ({ - definition, - language, -}: Props_9) => JSX.Element; +export const PlainApiDefinitionWidget: ( + props: PlainApiDefinitionWidgetProps, +) => JSX.Element; + +// Warning: (ae-missing-release-tag) "PlainApiDefinitionWidgetProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type PlainApiDefinitionWidgetProps = { + definition: any; + language: string; +}; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ProvidedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -194,5 +216,5 @@ export const ProvidedApisCard: ({ variant }: Props_4) => JSX.Element; // Warning: (ae-missing-release-tag) "ProvidingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ProvidingComponentsCard: ({ variant }: Props_7) => JSX.Element; +export const ProvidingComponentsCard: ({ variant }: Props_6) => JSX.Element; ``` diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 67045c2043..61ae26c776 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-api-docs", "description": "A Backstage plugin that helps represent API entities in the frontend", - "version": "0.6.12", + "version": "0.6.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,11 +32,11 @@ "dependencies": { "@asyncapi/react-component": "^0.23.0", "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog": "^0.7.2", - "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -53,10 +53,10 @@ "swagger-ui-react": "^4.0.0-rc.3" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.test.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.test.tsx similarity index 87% rename from plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.test.tsx rename to plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.test.tsx index 0170ada180..cb89476c03 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.test.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.test.tsx @@ -16,9 +16,9 @@ import { renderInTestApp } from '@backstage/test-utils'; import React from 'react'; -import { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget'; +import { AsyncApiDefinition } from './AsyncApiDefinition'; -describe('', () => { +describe('', () => { it('renders asyncapi spec', async () => { const definition = ` asyncapi: 2.0.0 @@ -40,7 +40,7 @@ components: type: string `; const { getByText, getAllByText } = await renderInTestApp( - , + , ); expect(getByText(/Account Service/i)).toBeInTheDocument(); @@ -51,7 +51,7 @@ components: it('renders error if definition is missing', async () => { const { getByText } = await renderInTestApp( - , + , ); expect(getByText(/Error/i)).toBeInTheDocument(); expect(getByText(/Document can't be null or falsey/i)).toBeInTheDocument(); diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx new file mode 100644 index 0000000000..1c993f67d7 --- /dev/null +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinition.tsx @@ -0,0 +1,145 @@ +/* + * 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 AsyncApi from '@asyncapi/react-component'; +import '@asyncapi/react-component/lib/styles/fiori.css'; +import { alpha, makeStyles } from '@material-ui/core/styles'; +import React from 'react'; + +const useStyles = makeStyles(theme => ({ + root: { + '& .asyncapi': { + 'font-family': 'inherit', + background: 'none', + }, + '& h2': { + ...theme.typography.h6, + }, + '& .text-teal': { + color: theme.palette.primary.main, + }, + '& button': { + ...theme.typography.button, + background: 'none', + boxSizing: 'border-box', + minWidth: 64, + borderRadius: theme.shape.borderRadius, + transition: theme.transitions.create( + ['background-color', 'box-shadow', 'border'], + { + duration: theme.transitions.duration.short, + }, + ), + padding: '5px 15px', + color: theme.palette.primary.main, + border: `1px solid ${alpha(theme.palette.primary.main, 0.5)}`, + '&:hover': { + textDecoration: 'none', + '&.Mui-disabled': { + backgroundColor: 'transparent', + }, + border: `1px solid ${theme.palette.primary.main}`, + backgroundColor: alpha( + theme.palette.primary.main, + theme.palette.action.hoverOpacity, + ), + // Reset on touch devices, it doesn't add specificity + '@media (hover: none)': { + backgroundColor: 'transparent', + }, + }, + '&.Mui-disabled': { + color: theme.palette.action.disabled, + }, + }, + '& .asyncapi__collapse-button:hover': { + color: theme.palette.primary.main, + }, + '& button.asyncapi__toggle-button': { + 'min-width': 'inherit', + }, + '& .asyncapi__info-list li': { + 'border-color': theme.palette.primary.main, + '&:hover': { + color: theme.palette.text.primary, + 'border-color': theme.palette.primary.main, + 'background-color': theme.palette.primary.main, + }, + }, + '& .asyncapi__info-list li a': { + color: theme.palette.primary.main, + '&:hover': { + color: theme.palette.getContrastText(theme.palette.primary.main), + }, + }, + '& .asyncapi__enum': { + color: theme.palette.secondary.main, + }, + '& .asyncapi__info, .asyncapi__channel, .asyncapi__channels > div, .asyncapi__schema, .asyncapi__channel-operations-list .asyncapi__messages-list-item .asyncapi__message, .asyncapi__message, .asyncapi__server, .asyncapi__servers > div, .asyncapi__messages > div, .asyncapi__schemas > div': + { + 'background-color': 'inherit', + }, + '& .asyncapi__channel-parameters-header, .asyncapi__channel-operations-header, .asyncapi__channel-operation-oneOf-subscribe-header, .asyncapi__channel-operation-oneOf-publish-header, .asyncapi__channel-operation-message-header, .asyncapi__message-header, .asyncapi__message-header-title, .asyncapi__message-header-title > h3, .asyncapi__bindings, .asyncapi__bindings-header, .asyncapi__bindings-header > h4': + { + 'background-color': 'inherit', + color: theme.palette.text.primary, + }, + '& .asyncapi__additional-properties-notice': { + color: theme.palette.text.hint, + }, + '& .asyncapi__code, .asyncapi__code-pre': { + background: theme.palette.background.default, + }, + '& .asyncapi__schema-example-header-title': { + color: theme.palette.text.secondary, + }, + '& .asyncapi__message-headers-header, .asyncapi__message-payload-header, .asyncapi__server-variables-header, .asyncapi__server-security-header': + { + 'background-color': 'inherit', + color: theme.palette.text.secondary, + }, + '& .asyncapi__table-header': { + background: theme.palette.background.default, + }, + '& .asyncapi__table-body': { + color: theme.palette.text.primary, + }, + '& .asyncapi__server-security-flow': { + background: theme.palette.background.default, + border: 'none', + }, + '& .asyncapi__server-security-flows-list a': { + color: theme.palette.primary.main, + }, + '& .asyncapi__table-row--nested': { + color: theme.palette.text.secondary, + }, + }, +})); + +type Props = { + definition: string; +}; + +export const AsyncApiDefinition = ({ definition }: Props) => { + const classes = useStyles(); + + return ( +
+ +
+ ); +}; diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx index f32b5d01b5..03bc6fa8a6 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/AsyncApiDefinitionWidget.tsx @@ -14,132 +14,27 @@ * limitations under the License. */ -import AsyncApi from '@asyncapi/react-component'; -import '@asyncapi/react-component/lib/styles/fiori.css'; -import { fade, makeStyles } from '@material-ui/core/styles'; -import React from 'react'; +import { Progress } from '@backstage/core-components'; +import React, { Suspense } from 'react'; -const useStyles = makeStyles(theme => ({ - root: { - '& .asyncapi': { - 'font-family': 'inherit', - background: 'none', - }, - '& h2': { - ...theme.typography.h6, - }, - '& .text-teal': { - color: theme.palette.primary.main, - }, - '& button': { - ...theme.typography.button, - background: 'none', - boxSizing: 'border-box', - minWidth: 64, - borderRadius: theme.shape.borderRadius, - transition: theme.transitions.create( - ['background-color', 'box-shadow', 'border'], - { - duration: theme.transitions.duration.short, - }, - ), - padding: '5px 15px', - color: theme.palette.primary.main, - border: `1px solid ${fade(theme.palette.primary.main, 0.5)}`, - '&:hover': { - textDecoration: 'none', - '&.Mui-disabled': { - backgroundColor: 'transparent', - }, - border: `1px solid ${theme.palette.primary.main}`, - backgroundColor: fade( - theme.palette.primary.main, - theme.palette.action.hoverOpacity, - ), - // Reset on touch devices, it doesn't add specificity - '@media (hover: none)': { - backgroundColor: 'transparent', - }, - }, - '&.Mui-disabled': { - color: theme.palette.action.disabled, - }, - }, - '& .asyncapi__collapse-button:hover': { - color: theme.palette.primary.main, - }, - '& button.asyncapi__toggle-button': { - 'min-width': 'inherit', - }, - '& .asyncapi__info-list li': { - 'border-color': theme.palette.primary.main, - '&:hover': { - color: theme.palette.text.primary, - 'border-color': theme.palette.primary.main, - 'background-color': theme.palette.primary.main, - }, - }, - '& .asyncapi__info-list li a': { - color: theme.palette.primary.main, - '&:hover': { - color: theme.palette.getContrastText(theme.palette.primary.main), - }, - }, - '& .asyncapi__enum': { - color: theme.palette.secondary.main, - }, - '& .asyncapi__info, .asyncapi__channel, .asyncapi__channels > div, .asyncapi__schema, .asyncapi__channel-operations-list .asyncapi__messages-list-item .asyncapi__message, .asyncapi__message, .asyncapi__server, .asyncapi__servers > div, .asyncapi__messages > div, .asyncapi__schemas > div': - { - 'background-color': 'inherit', - }, - '& .asyncapi__channel-parameters-header, .asyncapi__channel-operations-header, .asyncapi__channel-operation-oneOf-subscribe-header, .asyncapi__channel-operation-oneOf-publish-header, .asyncapi__channel-operation-message-header, .asyncapi__message-header, .asyncapi__message-header-title, .asyncapi__message-header-title > h3, .asyncapi__bindings, .asyncapi__bindings-header, .asyncapi__bindings-header > h4': - { - 'background-color': 'inherit', - color: theme.palette.text.primary, - }, - '& .asyncapi__additional-properties-notice': { - color: theme.palette.text.hint, - }, - '& .asyncapi__code, .asyncapi__code-pre': { - background: theme.palette.background.default, - }, - '& .asyncapi__schema-example-header-title': { - color: theme.palette.text.secondary, - }, - '& .asyncapi__message-headers-header, .asyncapi__message-payload-header, .asyncapi__server-variables-header, .asyncapi__server-security-header': - { - 'background-color': 'inherit', - color: theme.palette.text.secondary, - }, - '& .asyncapi__table-header': { - background: theme.palette.background.default, - }, - '& .asyncapi__table-body': { - color: theme.palette.text.primary, - }, - '& .asyncapi__server-security-flow': { - background: theme.palette.background.default, - border: 'none', - }, - '& .asyncapi__server-security-flows-list a': { - color: theme.palette.primary.main, - }, - '& .asyncapi__table-row--nested': { - color: theme.palette.text.secondary, - }, - }, -})); +// The asyncapi component and related CSS has a significant size, only load it +// if the element is actually used. +const LazyAsyncApiDefinition = React.lazy(() => + import('./AsyncApiDefinition').then(m => ({ + default: m.AsyncApiDefinition, + })), +); -type Props = { +export type AsyncApiDefinitionWidgetProps = { definition: string; }; -export const AsyncApiDefinitionWidget = ({ definition }: Props) => { - const classes = useStyles(); - +export const AsyncApiDefinitionWidget = ( + props: AsyncApiDefinitionWidgetProps, +) => { return ( -
- -
+ }> + + ); }; diff --git a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts index dcca901be2..7cc49e3138 100644 --- a/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts +++ b/plugins/api-docs/src/components/AsyncApiDefinitionWidget/index.ts @@ -15,3 +15,4 @@ */ export { AsyncApiDefinitionWidget } from './AsyncApiDefinitionWidget'; +export type { AsyncApiDefinitionWidgetProps } from './AsyncApiDefinitionWidget'; diff --git a/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.test.tsx b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinition.test.tsx similarity index 89% rename from plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.test.tsx rename to plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinition.test.tsx index 63e5a73692..fd78fa9204 100644 --- a/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.test.tsx +++ b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinition.test.tsx @@ -16,9 +16,9 @@ import { renderInTestApp } from '@backstage/test-utils'; import React from 'react'; -import { GraphQlDefinitionWidget } from './GraphQlDefinitionWidget'; +import { GraphQlDefinition } from './GraphQlDefinition'; -describe('', () => { +describe('', () => { it('renders graphql schema', async () => { const definition = ` """Hello World!""" @@ -53,7 +53,7 @@ type Film { }; const { getByText } = await renderInTestApp( - , + , ); expect(getByText(/Film/i)).toBeInTheDocument(); diff --git a/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinition.tsx b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinition.tsx new file mode 100644 index 0000000000..d3fddf70cf --- /dev/null +++ b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinition.tsx @@ -0,0 +1,63 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BackstageTheme } from '@backstage/theme'; +import { makeStyles } from '@material-ui/core/styles'; +import GraphiQL from 'graphiql'; +import 'graphiql/graphiql.css'; +import { buildSchema } from 'graphql'; +import React from 'react'; + +const useStyles = makeStyles(() => ({ + root: { + height: '100%', + display: 'flex', + flexFlow: 'column nowrap', + }, + graphiQlWrapper: { + flex: 1, + '@global': { + '.graphiql-container': { + boxSizing: 'initial', + height: '100%', + minHeight: '600px', + flex: '1 1 auto', + }, + }, + }, +})); + +type Props = { + definition: string; +}; + +export const GraphQlDefinition = ({ definition }: Props) => { + const classes = useStyles(); + const schema = buildSchema(definition); + + return ( +
+
+ Promise.resolve(null) as any} + schema={schema} + docExplorerOpen + defaultSecondaryEditorOpen={false} + /> +
+
+ ); +}; diff --git a/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx index 8d15ba08a2..1f8a796249 100644 --- a/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/GraphQlDefinitionWidget/GraphQlDefinitionWidget.tsx @@ -14,54 +14,27 @@ * limitations under the License. */ -import { BackstageTheme } from '@backstage/theme'; -import { makeStyles } from '@material-ui/core/styles'; -import 'graphiql/graphiql.css'; -import { buildSchema } from 'graphql'; -import React, { Suspense } from 'react'; import { Progress } from '@backstage/core-components'; +import React, { Suspense } from 'react'; -const GraphiQL = React.lazy(() => import('graphiql')); +// The graphql component, graphql and related CSS has a significant size, only +// load it if the element is actually used. +const LazyGraphQlDefinition = React.lazy(() => + import('./GraphQlDefinition').then(m => ({ + default: m.GraphQlDefinition, + })), +); -const useStyles = makeStyles(() => ({ - root: { - height: '100%', - display: 'flex', - flexFlow: 'column nowrap', - }, - graphiQlWrapper: { - flex: 1, - '@global': { - '.graphiql-container': { - boxSizing: 'initial', - height: '100%', - minHeight: '600px', - flex: '1 1 auto', - }, - }, - }, -})); - -type Props = { - definition: any; +export type GraphQlDefinitionWidgetProps = { + definition: string; }; -export const GraphQlDefinitionWidget = ({ definition }: Props) => { - const classes = useStyles(); - const schema = buildSchema(definition); - +export const GraphQlDefinitionWidget = ( + props: GraphQlDefinitionWidgetProps, +) => { return ( }> -
-
- Promise.resolve(null) as any} - schema={schema} - docExplorerOpen - defaultSecondaryEditorOpen={false} - /> -
-
+
); }; diff --git a/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts b/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts index 9173f492b0..8645b3c372 100644 --- a/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts +++ b/plugins/api-docs/src/components/GraphQlDefinitionWidget/index.ts @@ -15,3 +15,4 @@ */ export { GraphQlDefinitionWidget } from './GraphQlDefinitionWidget'; +export type { GraphQlDefinitionWidgetProps } from './GraphQlDefinitionWidget'; diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.test.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx similarity index 87% rename from plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.test.tsx rename to plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx index 080aa11a3a..735fba86f7 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.test.tsx +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.test.tsx @@ -17,9 +17,9 @@ import { renderInTestApp } from '@backstage/test-utils'; import { waitFor } from '@testing-library/react'; import React from 'react'; -import { OpenApiDefinitionWidget } from './OpenApiDefinitionWidget'; +import { OpenApiDefinition } from './OpenApiDefinition'; -describe('', () => { +describe('', () => { it('renders openapi spec', async () => { const definition = ` openapi: "3.0.0" @@ -39,7 +39,7 @@ paths: description: Success `; const { getByText } = await renderInTestApp( - , + , ); // swagger-ui loads the documentation asynchronously @@ -51,7 +51,7 @@ paths: it('renders error if definition is missing', async () => { const { getByText } = await renderInTestApp( - , + , ); expect(getByText(/No API definition provided/i)).toBeInTheDocument(); }); diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx new file mode 100644 index 0000000000..4a74b238f7 --- /dev/null +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx @@ -0,0 +1,132 @@ +/* + * 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 { makeStyles } from '@material-ui/core/styles'; +import React, { useEffect, useState } from 'react'; +import SwaggerUI from 'swagger-ui-react'; +import 'swagger-ui-react/swagger-ui.css'; + +const useStyles = makeStyles(theme => ({ + root: { + '& .swagger-ui': { + fontFamily: 'inherit', + color: theme.palette.text.primary, + + [`& .info h1, + .info h2, + .info h3, + .info h4, + .info h5, + .info h6`]: { + fontFamily: 'inherit', + color: theme.palette.text.primary, + }, + [`& .scheme-container`]: { + backgroundColor: theme.palette.background.default, + }, + [`& .opblock-tag, + .opblock-tag small, + table thead tr td, + table thead tr th`]: { + fontFamily: 'inherit', + color: theme.palette.text.primary, + borderColor: theme.palette.divider, + }, + [`& section.models, + section.models.is-open h4`]: { + borderColor: theme.palette.divider, + }, + [`& .opblock .opblock-summary-description, + .parameter__type, + table.headers td, + .model-title, + .model .property.primitive, + section h3`]: { + fontFamily: 'inherit', + color: theme.palette.text.secondary, + }, + [`& .opblock .opblock-summary-operation-id, + .opblock .opblock-summary-path, + .opblock .opblock-summary-path__deprecated, + .opblock .opblock-section-header h4, + .parameter__name, + .response-col_status, + .response-col_links, + .responses-inner h4, + .responses-inner h5, + .opblock-section-header .btn, + .tab li, + .info li, + .info p, + .info table, + section.models h4, + .info .title, + table.model tr.description, + .property-row`]: { + fontFamily: 'inherit', + color: theme.palette.text.primary, + }, + [`& .opblock .opblock-section-header, + .model-box, + section.models .model-container`]: { + background: theme.palette.background.default, + }, + [`& .prop-format, + .parameter__in`]: { + color: theme.palette.text.disabled, + }, + [`& .opblock-description-wrapper p, + .opblock-external-docs-wrapper p, + .opblock-title_normal p, + .response-control-media-type__accept-message, + .opblock .opblock-section-header>label, + .scheme-container .schemes>label, + .info .base-url, + .model`]: { + color: theme.palette.text.hint, + }, + [`& .parameter__name.required:after`]: { + color: theme.palette.warning.dark, + }, + [`& .prop-type`]: { + color: theme.palette.primary.main, + }, + }, + }, +})); + +export type OpenApiDefinitionProps = { + definition: string; +}; + +export const OpenApiDefinition = ({ definition }: OpenApiDefinitionProps) => { + const classes = useStyles(); + + // Due to a bug in the swagger-ui-react component, the component needs + // to be created without content first. + const [def, setDef] = useState(''); + + useEffect(() => { + const timer = setTimeout(() => setDef(definition), 0); + return () => clearTimeout(timer); + }, [definition, setDef]); + + return ( +
+ +
+ ); +}; diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx index 4c22d29bc2..606e476454 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinitionWidget.tsx @@ -14,81 +14,27 @@ * limitations under the License. */ -import { makeStyles } from '@material-ui/core/styles'; -import React, { useEffect, useState } from 'react'; -import SwaggerUI from 'swagger-ui-react'; -import 'swagger-ui-react/swagger-ui.css'; +import { Progress } from '@backstage/core-components'; +import React, { Suspense } from 'react'; -// TODO: Schemas +// The swagger-ui component and related CSS has a significant size, only load it +// if the element is actually used. +const LazyOpenApiDefinition = React.lazy(() => + import('./OpenApiDefinition').then(m => ({ + default: m.OpenApiDefinition, + })), +); -const useStyles = makeStyles(theme => ({ - root: { - '& .swagger-ui, .info h1, .info h2, .info h3, .info h4, .info h': { - 'font-family': 'inherit', - color: theme.palette.text.primary, - }, - '& .scheme-container': { - 'background-color': theme.palette.background.default, - }, - '& .opblock-tag, .opblock-tag small, table thead tr td, table thead tr th': - { - color: theme.palette.text.primary, - 'border-color': theme.palette.divider, - }, - '& section.models, section.models.is-open h4': { - 'border-color': theme.palette.divider, - }, - '& .opblock .opblock-summary-description, .parameter__type, table.headers td, .model-title, .model .property.primitive, section h3': - { - color: theme.palette.text.secondary, - }, - '& .opblock .opblock-summary-operation-id, .opblock .opblock-summary-path, .opblock .opblock-summary-path__deprecated, .opblock .opblock-section-header h4, .parameter__name, .response-col_status, .response-col_links, .responses-inner h4, .swagger-ui .responses-inner h5, .opblock-section-header .btn, .tab li, .info li, .info p, .info table, section.models h4, .info .title, table.model tr.description, .property-row': - { - color: theme.palette.text.primary, - }, - '& .opblock .opblock-section-header, .model-box, section.models .model-container': - { - background: theme.palette.background.default, - }, - '& .prop-format, .parameter__in': { - color: theme.palette.text.disabled, - }, - '& ': { - color: theme.palette.text.primary, - 'border-color': theme.palette.divider, - }, - '& .opblock-description-wrapper p, .opblock-external-docs-wrapper p, .opblock-title_normal p, .response-control-media-type__accept-message, .opblock .opblock-section-header>label, .scheme-container .schemes>label, .info .base-url, .model': - { - color: theme.palette.text.hint, - }, - '& .parameter__name.required:after': { - color: theme.palette.warning.dark, - }, - '& .prop-type': { - color: theme.palette.primary.main, - }, - }, -})); - -type Props = { +export type OpenApiDefinitionWidgetProps = { definition: string; }; -export const OpenApiDefinitionWidget = ({ definition }: Props) => { - const classes = useStyles(); - - // Due to a bug in the swagger-ui-react component, the component needs - // to be created without content first. - const [def, setDef] = useState(''); - - useEffect(() => { - const timer = setTimeout(() => setDef(definition), 0); - return () => clearTimeout(timer); - }, [definition, setDef]); - +export const OpenApiDefinitionWidget = ( + props: OpenApiDefinitionWidgetProps, +) => { return ( -
- -
+ }> + + ); }; diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/index.ts b/plugins/api-docs/src/components/OpenApiDefinitionWidget/index.ts index 94401c92a1..e317d42a59 100644 --- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/index.ts +++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/index.ts @@ -15,3 +15,4 @@ */ export { OpenApiDefinitionWidget } from './OpenApiDefinitionWidget'; +export type { OpenApiDefinitionWidgetProps } from './OpenApiDefinitionWidget'; diff --git a/plugins/api-docs/src/components/PlainApiDefinitionWidget/PlainApiDefinitionWidget.tsx b/plugins/api-docs/src/components/PlainApiDefinitionWidget/PlainApiDefinitionWidget.tsx index efdd882564..b7f1e8afc0 100644 --- a/plugins/api-docs/src/components/PlainApiDefinitionWidget/PlainApiDefinitionWidget.tsx +++ b/plugins/api-docs/src/components/PlainApiDefinitionWidget/PlainApiDefinitionWidget.tsx @@ -17,13 +17,19 @@ import React from 'react'; import { CodeSnippet } from '@backstage/core-components'; -type Props = { +export type PlainApiDefinitionWidgetProps = { definition: any; language: string; }; -export const PlainApiDefinitionWidget = ({ definition, language }: Props) => { +export const PlainApiDefinitionWidget = ( + props: PlainApiDefinitionWidgetProps, +) => { return ( - + ); }; diff --git a/plugins/api-docs/src/components/PlainApiDefinitionWidget/index.ts b/plugins/api-docs/src/components/PlainApiDefinitionWidget/index.ts index c233652981..562a3e67c1 100644 --- a/plugins/api-docs/src/components/PlainApiDefinitionWidget/index.ts +++ b/plugins/api-docs/src/components/PlainApiDefinitionWidget/index.ts @@ -15,3 +15,4 @@ */ export { PlainApiDefinitionWidget } from './PlainApiDefinitionWidget'; +export type { PlainApiDefinitionWidgetProps } from './PlainApiDefinitionWidget'; diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index c1ba2038aa..a11f561986 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend +## 0.4.7 + +### Patch Changes + +- 5ee31f860b: Only use settings that have a value when creating a new FirestoreKeyStore instance +- 3e0e2f09d5: Added forwarding of the `audience` option for the SAML provider, making it possible to enable `audience` verification. +- Updated dependencies + - @backstage/backend-common@0.9.9 + - @backstage/test-utils@0.1.21 + - @backstage/catalog-client@0.5.1 + ## 0.4.6 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 5c9d70ea10..e9ec29970b 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend", "description": "A Backstage backend plugin that handles authentication", - "version": "0.4.6", + "version": "0.4.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,12 +30,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.9.8", - "@backstage/catalog-client": "^0.5.0", + "@backstage/backend-common": "^0.9.9", + "@backstage/catalog-client": "^0.5.1", "@backstage/catalog-model": "^0.9.6", "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.4", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@google-cloud/firestore": "^4.15.1", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", @@ -73,7 +73,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.8.1", + "@backstage/cli": "^0.8.2", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index b756e7ce4f..d2fe981eb0 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-azure-devops-backend +## 0.2.0 + +### Minor Changes + +- b85acc8c35: refactor(`@backstage/plugin-azure-devops`): Consume types from `@backstage/plugin-azure-devops-common`. + Stop re-exporting types from `@backstage/plugin-azure-devops-backend`. + Added new types to `@backstage/plugin-azure-devops-common`. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.9.9 + - @backstage/plugin-azure-devops-common@0.0.2 + ## 0.1.4 ### Patch Changes diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 9dd79e9f8e..901ad0b960 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-backend", - "version": "0.1.4", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.9.8", + "@backstage/backend-common": "^0.9.9", "@backstage/config": "^0.1.11", - "@backstage/plugin-azure-devops-common": "^0.0.1", + "@backstage/plugin-azure-devops-common": "^0.0.2", "@types/express": "^4.17.6", "azure-devops-node-api": "^11.0.1", "express": "^4.17.1", @@ -31,7 +31,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.8.1", + "@backstage/cli": "^0.8.2", "@types/supertest": "^2.0.8", "supertest": "^4.0.2", "msw": "^0.35.0" diff --git a/plugins/azure-devops-common/CHANGELOG.md b/plugins/azure-devops-common/CHANGELOG.md new file mode 100644 index 0000000000..b414cd3ce6 --- /dev/null +++ b/plugins/azure-devops-common/CHANGELOG.md @@ -0,0 +1,9 @@ +# @backstage/plugin-azure-devops-common + +## 0.0.2 + +### Patch Changes + +- b85acc8c35: refactor(`@backstage/plugin-azure-devops`): Consume types from `@backstage/plugin-azure-devops-common`. + Stop re-exporting types from `@backstage/plugin-azure-devops-backend`. + Added new types to `@backstage/plugin-azure-devops-common`. diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json index 05f70f6596..a1ddddcbe0 100644 --- a/plugins/azure-devops-common/package.json +++ b/plugins/azure-devops-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-common", - "version": "0.0.1", + "version": "0.0.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "devDependencies": { - "@backstage/cli": "^0.8.1" + "@backstage/cli": "^0.8.2" }, "files": [ "dist" diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 71904265dd..2f75f64236 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-azure-devops +## 0.1.3 + +### Patch Changes + +- b85acc8c35: refactor(`@backstage/plugin-azure-devops`): Consume types from `@backstage/plugin-azure-devops-common`. + Stop re-exporting types from `@backstage/plugin-azure-devops-backend`. + Added new types to `@backstage/plugin-azure-devops-common`. +- 84ace9a29c: Simplified queue time calculation in `BuildTable`. +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/plugin-azure-devops-common@0.0.2 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.1.2 ### Patch Changes diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index d44f67c73a..3f1876b974 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,12 +28,12 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.6", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.4", - "@backstage/plugin-azure-devops-common": "^0.0.1", - "@backstage/plugin-catalog-react": "^0.6.2", - "@backstage/theme": "^0.2.12", + "@backstage/plugin-azure-devops-common": "^0.0.2", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -45,10 +45,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 0cc3a06509..0c792808bc 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -28,11 +28,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.3", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index 3cd13cfa8a..731f62b9e9 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-bazaar +## 0.1.3 + +### Patch Changes + +- 4a336fd292: Name extension to remove deprecation warning +- Updated dependencies + - @backstage/cli@0.8.2 + - @backstage/core-components@0.7.3 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.1.2 ### Patch Changes diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index 5a98abfc3a..4d18784d2d 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,11 +22,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/cli": "^0.8.0", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/cli": "^0.8.2", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog": "^0.7.2", - "@backstage/plugin-catalog-react": "^0.6.1", + "@backstage/plugin-catalog-react": "^0.6.3", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -39,7 +39,7 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", + "@backstage/cli": "^0.8.2", "@backstage/dev-utils": "^0.2.12", "@testing-library/jest-dom": "^5.10.1", "cross-fetch": "^3.0.6" diff --git a/plugins/bazaar/src/plugin.ts b/plugins/bazaar/src/plugin.ts index f96eaf522d..5eb5e2aacd 100644 --- a/plugins/bazaar/src/plugin.ts +++ b/plugins/bazaar/src/plugin.ts @@ -44,6 +44,7 @@ export const bazaarPlugin = createPlugin({ export const BazaarPage = bazaarPlugin.provide( createRoutableExtension({ + name: 'BazaarPage', component: () => import('./components/HomePage').then(m => m.HomePage), mountPoint: rootRouteRef, }), diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index c4403830f1..5ebfd453a2 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -39,10 +39,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 7f3b8657f9..ee8aa49065 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.2.9 + +### Patch Changes + +- 779d7a2304: Tweak logic for msgraph catalog ingesting for display names with security groups + + Previously security groups that weren't mail enabled were imported with UUIDs, now they use the display name. + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.17.3 + ## 0.2.8 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 68e58d922d..b3c9494f78 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", "description": "A Backstage catalog backend modules that helps integrate towards Microsoft Graph", - "version": "0.2.8", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ "@azure/msal-node": "^1.1.0", "@backstage/catalog-model": "^0.9.5", "@backstage/config": "^0.1.10", - "@backstage/plugin-catalog-backend": "^0.17.1", + "@backstage/plugin-catalog-backend": "^0.17.3", "@microsoft/microsoft-graph-types": "^2.6.0", "cross-fetch": "^3.0.6", "lodash": "^4.17.21", @@ -41,9 +41,9 @@ "qs": "^6.9.4" }, "devDependencies": { - "@backstage/backend-common": "^0.9.7", - "@backstage/cli": "^0.8.0", - "@backstage/test-utils": "^0.1.19", + "@backstage/backend-common": "^0.9.9", + "@backstage/cli": "^0.8.2", + "@backstage/test-utils": "^0.1.21", "@types/lodash": "^4.14.151", "msw": "^0.35.0" }, diff --git a/plugins/catalog-backend/.snyk b/plugins/catalog-backend/.snyk new file mode 100644 index 0000000000..ad62599e66 --- /dev/null +++ b/plugins/catalog-backend/.snyk @@ -0,0 +1,47 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.22.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + SNYK-JS-TAR-1579155: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1579152: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1579147: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1536758: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1536531: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z + SNYK-JS-TAR-1536528: + - 'sqlite3 > node-gyp > tar': + reason: >- + The only usage is via node-gyp; there is no unpacking of untrusted tar + files + expires: 2022-11-11T14:30:05.581Z + created: 2021-11-11T14:30:05.582Z +patch: {} diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index a6bdc8407d..abb50a345c 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend +## 0.17.3 + +### Patch Changes + +- 86bef79ad1: Allow singleton and flexibly nested EntityFilters +- Updated dependencies + - @backstage/backend-common@0.9.9 + - @backstage/catalog-client@0.5.1 + ## 0.17.2 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 121dd933f9..f13ff2d66c 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "0.17.2", + "version": "0.17.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.9.8", - "@backstage/catalog-client": "^0.5.0", + "@backstage/backend-common": "^0.9.9", + "@backstage/catalog-client": "^0.5.1", "@backstage/catalog-model": "^0.9.6", "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.4", @@ -63,8 +63,8 @@ }, "devDependencies": { "@backstage/backend-test-utils": "^0.1.8", - "@backstage/cli": "^0.8.1", - "@backstage/test-utils": "^0.1.20", + "@backstage/cli": "^0.8.2", + "@backstage/test-utils": "^0.1.21", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index d5cacc12a6..e58d66b1f2 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -23,10 +23,10 @@ "dependencies": { "@backstage/catalog-client": "^0.5.0", "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -41,10 +41,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 0287be8e06..242231bf81 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -33,8 +33,8 @@ "dependencies": { "@backstage/catalog-client": "^0.5.0", "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.3", "@backstage/integration": "^0.6.8", "@backstage/integration-react": "^0.1.12", @@ -55,10 +55,10 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index cdb80ceac4..79dfa5e72a 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-react +## 0.6.3 + +### Patch Changes + +- 03b47a476d: export `loadIdentityOwnerRefs` and `loadCatalogOwnerRefs` all the way +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/catalog-client@0.5.1 + - @backstage/core-plugin-api@0.1.13 + - @backstage/core-app-api@0.1.20 + ## 0.6.2 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 40c0d9c739..9778163391 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "0.6.2", + "version": "0.6.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,11 +29,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.5.0", + "@backstage/catalog-client": "^0.5.1", "@backstage/catalog-model": "^0.9.6", - "@backstage/core-app-api": "^0.1.19", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-app-api": "^0.1.20", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.4", "@backstage/integration": "^0.6.9", "@backstage/types": "^0.1.1", @@ -51,8 +51,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/test-utils": "^0.1.20", + "@backstage/cli": "^0.8.2", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 0bc7ac4034..9697741dde 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -33,12 +33,12 @@ "dependencies": { "@backstage/catalog-client": "^0.5.0", "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.3", "@backstage/integration-react": "^0.1.12", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -51,10 +51,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index a501d77ff3..78cd6d9711 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-circleci +## 0.2.28 + +### Patch Changes + +- e849f9cc0a: Use `` instead of `` in combination with `Suspense`. +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.2.27 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 9a7c428d33..c57d19ea00 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.2.27", + "version": "0.2.28", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -52,10 +52,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx b/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx index bbfc1e468c..a4c2af241f 100644 --- a/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx +++ b/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx @@ -14,11 +14,11 @@ * limitations under the License. */ +import { Progress } from '@backstage/core-components'; import { Accordion, AccordionDetails, AccordionSummary, - LinearProgress, Typography, } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; @@ -85,7 +85,7 @@ export const ActionOutput = ({ {messages.length === 0 ? ( 'Nothing here...' ) : ( - }> + }>
diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 376dd9f091..6521816f73 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -32,10 +32,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index 8a8c91a111..572e0f7800 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-code-coverage +## 0.1.17 + +### Patch Changes + +- a5512851a0: Change represented test date from epoch to something more human friendly. Round test coverage to 2 decimal places. +- 5a85b802f3: Make dates in X-Axis sort in ascending order +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.1.16 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index d01e12dff5..0d6e998b69 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.1.16", + "version": "0.1.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,11 +23,11 @@ "dependencies": { "@backstage/catalog-model": "^0.9.6", "@backstage/config": "^0.1.11", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.4", - "@backstage/plugin-catalog-react": "^0.6.2", - "@backstage/theme": "^0.2.12", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -42,10 +42,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index e460878691..0708115270 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/config": "^0.1.11", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.4", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@backstage/types": "^0.1.1", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -37,10 +37,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 010498a272..064b24bd0c 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -32,9 +32,9 @@ }, "dependencies": { "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -55,10 +55,10 @@ "yup": "^0.32.9" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 66bd3fdf7a..30890c7176 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -32,11 +32,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", "@backstage/plugin-explore-react": "^0.0.6", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 0738980613..4e3fbdd3c8 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -22,10 +22,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index c5b6b8b62f..94528a3126 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -33,11 +33,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.3", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 65458ea88b..471b9d80f1 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -43,10 +43,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gcp-projects/src/components/NewProjectPage/NewProjectPage.tsx b/plugins/gcp-projects/src/components/NewProjectPage/NewProjectPage.tsx index 584cffc50c..5ecc7f15d4 100644 --- a/plugins/gcp-projects/src/components/NewProjectPage/NewProjectPage.tsx +++ b/plugins/gcp-projects/src/components/NewProjectPage/NewProjectPage.tsx @@ -28,6 +28,10 @@ import { StructuredMetadataTable, SupportButton, } from '@backstage/core-components'; +import { Link as RouterLink } from 'react-router-dom'; + +import { useRouteRef } from '@backstage/core-plugin-api'; +import { rootRouteRef } from '../../routes'; export const Project = () => { const [projectName, setProjectName] = useState(''); @@ -79,18 +83,20 @@ export const Project = () => {
@@ -127,12 +130,24 @@ const DetailsPage = () => { > {details?.name && ( )} {details?.name && ( )} @@ -151,16 +166,20 @@ const labels = ( ); -export const ProjectDetailsPage = () => ( - -
- {labels} -
- - - Support Button - - - -
-); +export const ProjectDetailsPage = () => { + const docsRootLink = useRouteRef(rootRouteRef)(); + + return ( + +
+ {labels} +
+ + + Support Button + + + +
+ ); +}; diff --git a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx index db3999272a..b37dc2fa50 100644 --- a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx @@ -15,19 +15,9 @@ */ // NEEDS WORK -import { - Button, - LinearProgress, - Paper, - Table, - TableBody, - TableCell, - TableHead, - TableRow, - Tooltip, - Typography, -} from '@material-ui/core'; +import { Button, LinearProgress, Tooltip, Typography } from '@material-ui/core'; import React from 'react'; + import { useAsync } from 'react-use'; import { gcpApiRef, Project } from '../../api'; @@ -38,12 +28,15 @@ import { HeaderLabel, Link, Page, + Table, SupportButton, WarningPanel, } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; +import { Link as RouterLink } from 'react-router-dom'; + const LongText = ({ text, max }: { text: string; max: number }) => { if (text.length < max) { return {text}; @@ -77,60 +70,59 @@ const PageContents = () => { ); } + function renderLink(id: string) { + return ( + + + + + + ); + } + return ( -
-
- - - Name - Project Number - Project ID - State - Creation Time - - - - {value?.map((project: Project) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ))} - -
- +
+ renderLink(rowData.id), + }, + { + field: 'state', + title: 'State', + }, + { + field: 'creationTime', + title: 'Creation Time', + }, + ]} + data={ + value?.map((project: Project) => ({ + id: project.projectId, + name: project.name, + projectNumber: project?.projectNumber || 'Error', + projectID: project.projectId, + state: project?.lifecycleState || 'Error', + creationTime: project?.createTime || 'Error', + })) || [] + } + options={{ + pageSize: 5, + pageSizeOptions: [5, 10, 25, 50, 100], + }} + /> + ); }; @@ -141,7 +133,12 @@ export const ProjectListPage = () => ( - All your software catalog entities diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 11f6655c5d..b1b2c11732 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/integration": "^0.6.8", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -39,10 +39,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index 222bc4c851..c8fce8e501 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-github-actions +## 0.4.23 + +### Patch Changes + +- e849f9cc0a: Use `` instead of `` in combination with `Suspense`. +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.4.22 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 087a596c64..1a4079a5ca 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-actions", "description": "A Backstage plugin that integrates towards GitHub Actions", - "version": "0.4.22", + "version": "0.4.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,11 +34,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/integration": "^0.6.8", - "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -52,10 +52,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx index ce33ae3781..399fed9557 100644 --- a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx +++ b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx @@ -14,12 +14,15 @@ * limitations under the License. */ +import { Entity } from '@backstage/catalog-model'; +import { Progress } from '@backstage/core-components'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import { readGitHubIntegrationConfigs } from '@backstage/integration'; import { Accordion, AccordionSummary, CircularProgress, Fade, - LinearProgress, makeStyles, Modal, Theme, @@ -27,15 +30,11 @@ import { Typography, Zoom, } from '@material-ui/core'; - -import React, { Suspense } from 'react'; -import { useDownloadWorkflowRunLogs } from './useDownloadWorkflowRunLogs'; -import { useProjectName } from '../useProjectName'; -import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import DescriptionIcon from '@material-ui/icons/Description'; -import { Entity } from '@backstage/catalog-model'; -import { readGitHubIntegrationConfigs } from '@backstage/integration'; -import { configApiRef, useApi } from '@backstage/core-plugin-api'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import React, { Suspense } from 'react'; +import { useProjectName } from '../useProjectName'; +import { useDownloadWorkflowRunLogs } from './useDownloadWorkflowRunLogs'; const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog')); const LinePart = React.lazy(() => import('react-lazylog/build/LinePart')); @@ -72,7 +71,7 @@ const DisplayLog = ({ className: string; }) => { return ( - }> + }>
({ title, components, + name, }: { title: string; components: () => Promise; + name?: string; }): Extension< ({ Renderer, diff --git a/plugins/home/package.json b/plugins/home/package.json index e100dba9ca..8020d7af03 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home", "description": "A Backstage plugin that helps you build a home page", - "version": "0.4.4", + "version": "0.4.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/home/src/extensions.tsx b/plugins/home/src/extensions.tsx index 93fe4ceb4f..82394cf1ea 100644 --- a/plugins/home/src/extensions.tsx +++ b/plugins/home/src/extensions.tsx @@ -37,11 +37,14 @@ type RendererProps = { title: string } & ComponentParts; export function createCardExtension({ title, components, + name, }: { title: string; components: () => Promise; + name?: string; }) { return createReactExtension({ + name, component: { lazy: () => components().then(({ Content, Actions, Settings, ContextProvider }) => { diff --git a/plugins/home/src/plugin.ts b/plugins/home/src/plugin.ts index d67ea846a5..b4b9f9b6ee 100644 --- a/plugins/home/src/plugin.ts +++ b/plugins/home/src/plugin.ts @@ -71,6 +71,7 @@ export const ComponentTab = homePlugin.provide( */ export const WelcomeTitle = homePlugin.provide( createComponentExtension({ + name: 'WelcomeTitle', component: { lazy: () => import('./homePageComponents/WelcomeTitle').then(m => m.WelcomeTitle), @@ -80,6 +81,7 @@ export const WelcomeTitle = homePlugin.provide( export const HomePageRandomJoke = homePlugin.provide( createCardExtension<{ defaultCategory?: 'any' | 'programming' }>({ + name: 'HomePageRandomJoke', title: 'Random Joke', components: () => import('./homePageComponents/RandomJoke'), }), diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index efa2d5d8f6..f43f2add09 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-ilert +## 0.1.16 + +### Patch Changes + +- fa325fd89d: Change the version of `@date-io/luxon` from 2.x to 1.x to make it compatible with material-ui-pickers +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.1.15 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index f93a5b4870..69c4025181 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-ilert", "description": "A Backstage plugin that integrates towards iLert", - "version": "0.1.15", + "version": "0.1.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,11 +22,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.3", - "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/theme": "^0.2.13", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -39,10 +39,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md index 635fa080ce..48f4461f7f 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-jenkins-backend +## 0.1.7 + +### Patch Changes + +- e8a1c1afe2: Don't require a validation pattern for the Jenkins base URL. +- Updated dependencies + - @backstage/backend-common@0.9.9 + - @backstage/catalog-client@0.5.1 + ## 0.1.6 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 93f5fa84d2..7049fc0a32 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins-backend", "description": "A Backstage backend plugin that integrates towards Jenkins", - "version": "0.1.6", + "version": "0.1.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,8 +22,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.9.7", - "@backstage/catalog-client": "^0.5.0", + "@backstage/backend-common": "^0.9.9", + "@backstage/catalog-client": "^0.5.1", "@backstage/catalog-model": "^0.9.5", "@backstage/config": "^0.1.10", "@types/express": "^4.17.6", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.8.0", + "@backstage/cli": "^0.8.2", "@types/jenkins": "^0.23.1", "@types/supertest": "^2.0.8", "msw": "^0.35.0", diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 1bd6388fba..80d8b40322 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -33,10 +33,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 3697f8de28..2fe42565da 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-kafka +## 0.2.20 + +### Patch Changes + +- 91f8644f17: Use IdentityApi to provide Auth Token for KafkaBackendClient Api calls +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.2.19 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index e9fe6bc4bf..888c79db59 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka", "description": "A Backstage plugin that integrates towards Kafka", - "version": "0.2.19", + "version": "0.2.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index ac7b19a1d9..a757041b32 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-kubernetes +## 0.4.19 + +### Patch Changes + +- 9f3dd76bc7: fix: kubernetes plugin shall pass id token on get clusters request if possible +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.4.18 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 5bce077278..9dbca8aff8 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.4.18", + "version": "0.4.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,11 +33,11 @@ "dependencies": { "@backstage/catalog-model": "^0.9.6", "@backstage/config": "^0.1.11", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/plugin-catalog-react": "^0.6.2", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/plugin-catalog-react": "^0.6.3", "@backstage/plugin-kubernetes-common": "^0.1.5", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@kubernetes/client-node": "^0.15.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -51,10 +51,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 1f3dd91d32..f0dca639fc 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -34,10 +34,10 @@ "dependencies": { "@backstage/catalog-model": "^0.9.5", "@backstage/config": "^0.1.10", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index c60f1ee9a5..271daa566e 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -32,9 +32,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -43,10 +43,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/org/package.json b/plugins/org/package.json index dc91b59816..5348f67523 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -22,10 +22,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -37,10 +37,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 2efc824746..7b42a15bc6 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -32,10 +32,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 38e295a37b..2e2da0a284 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -33,10 +33,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 9e870a9e0b..43a522482f 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.1.4 + +### Patch Changes + +- 290fbb3ec2: Add missing API docs to scaffolder action plugins +- Updated dependencies + - @backstage/backend-common@0.9.9 + - @backstage/plugin-scaffolder-backend@0.15.12 + ## 0.1.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 38c2eb493c..36a6310aa2 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.9.8", + "@backstage/backend-common": "^0.9.9", "@backstage/errors": "^0.1.4", "@backstage/integration": "^0.6.9", - "@backstage/plugin-scaffolder-backend": "^0.15.11", + "@backstage/plugin-scaffolder-backend": "^0.15.12", "@backstage/config": "^0.1.11", "@backstage/types": "^0.1.1", "command-exists": "^1.2.9", @@ -33,7 +33,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.8.1", + "@backstage/cli": "^0.8.2", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/jest": "^26.0.7", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index f1e53abc5b..9dcd6bcb88 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.1.7 + +### Patch Changes + +- 290fbb3ec2: Add missing API docs to scaffolder action plugins +- Updated dependencies + - @backstage/backend-common@0.9.9 + - @backstage/plugin-scaffolder-backend@0.15.12 + ## 0.1.6 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index e79b7fb818..f4ace02eca 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", "description": "A module for the scaffolder backend that lets you template projects using Rails", - "version": "0.1.6", + "version": "0.1.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.9.8", - "@backstage/plugin-scaffolder-backend": "^0.15.11", + "@backstage/backend-common": "^0.9.9", + "@backstage/plugin-scaffolder-backend": "^0.15.12", "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.4", "@backstage/integration": "^0.6.9", @@ -31,7 +31,7 @@ "fs-extra": "^9.0.0" }, "devDependencies": { - "@backstage/cli": "^0.8.1", + "@backstage/cli": "^0.8.2", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/command-exists": "^1.2.0", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index b15614b0ba..9373564b1c 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.1.2 + +### Patch Changes + +- 290fbb3ec2: Add missing API docs to scaffolder action plugins +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.15.12 + ## 0.1.1 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index b853309c56..8d17df0239 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.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,13 +21,13 @@ }, "dependencies": { "@backstage/config": "^0.1.11", - "@backstage/plugin-scaffolder-backend": "^0.15.11", + "@backstage/plugin-scaffolder-backend": "^0.15.12", "@backstage/types": "^0.1.1", "winston": "^3.2.1", "yeoman-environment": "^3.6.0" }, "devDependencies": { - "@backstage/backend-common": "^0.9.8", + "@backstage/backend-common": "^0.9.9", "@types/jest": "^26.0.7" }, "files": [ diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 689cb51d24..de033eabbd 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-scaffolder-backend +## 0.15.12 + +### Patch Changes + +- 9990df8a1f: Expose some classes and interfaces public so TaskWorkers can run externally from the scaffolder API. +- b45a34fb15: Adds a new endpoint for consuming logs from the Scaffolder that uses long polling instead of Server Sent Events. + + This is useful if Backstage is accessed from an environment that doesn't support SSE correctly, which happens in combination with certain enterprise HTTP Proxy servers. + + It is intended to switch the endpoint globally for the whole instance. + If you want to use it, you can provide a reconfigured API to the `scaffolderApiRef`: + + ```tsx + // packages/app/src/apis.ts + + // ... + import { + scaffolderApiRef, + ScaffolderClient, + } from '@backstage/plugin-scaffolder'; + + export const apis: AnyApiFactory[] = [ + // ... + + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + }, + factory: ({ discoveryApi, identityApi, scmIntegrationsApi }) => + new ScaffolderClient({ + discoveryApi, + identityApi, + scmIntegrationsApi, + // use long polling instead of an eventsource + useLongPollingLogs: true, + }), + }), + ]; + ``` + +- a794c341ca: Fix a bug where only file mode 775 is considered an executable +- Updated dependencies + - @backstage/backend-common@0.9.9 + - @backstage/catalog-client@0.5.1 + - @backstage/plugin-catalog-backend@0.17.3 + - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.1.4 + ## 0.15.11 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index fe5d61d4d3..e5d13801ba 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "0.15.11", + "version": "0.15.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,15 +30,15 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.9.8", - "@backstage/catalog-client": "^0.5.0", + "@backstage/backend-common": "^0.9.9", + "@backstage/catalog-client": "^0.5.1", "@backstage/catalog-model": "^0.9.6", "@backstage/config": "^0.1.11", "@backstage/errors": "^0.1.4", "@backstage/integration": "^0.6.9", - "@backstage/plugin-catalog-backend": "^0.17.2", + "@backstage/plugin-catalog-backend": "^0.17.3", "@backstage/plugin-scaffolder-common": "^0.1.1", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.3", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.4", "@backstage/types": "^0.1.1", "@gitbeaker/core": "^30.2.0", "@gitbeaker/node": "^30.2.0", @@ -71,8 +71,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/test-utils": "^0.1.20", + "@backstage/cli": "^0.8.2", + "@backstage/test-utils": "^0.1.21", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index b9873b5357..dd5653240a 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-scaffolder +## 0.11.10 + +### Patch Changes + +- fe5738fe1c: Lazy load `LazyLog` as it is rarely used. +- b45a34fb15: Adds a new endpoint for consuming logs from the Scaffolder that uses long polling instead of Server Sent Events. + + This is useful if Backstage is accessed from an environment that doesn't support SSE correctly, which happens in combination with certain enterprise HTTP Proxy servers. + + It is intended to switch the endpoint globally for the whole instance. + If you want to use it, you can provide a reconfigured API to the `scaffolderApiRef`: + + ```tsx + // packages/app/src/apis.ts + + // ... + import { + scaffolderApiRef, + ScaffolderClient, + } from '@backstage/plugin-scaffolder'; + + export const apis: AnyApiFactory[] = [ + // ... + + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + }, + factory: ({ discoveryApi, identityApi, scmIntegrationsApi }) => + new ScaffolderClient({ + discoveryApi, + identityApi, + scmIntegrationsApi, + // use long polling instead of an eventsource + useLongPollingLogs: true, + }), + }), + ]; + ``` + +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/catalog-client@0.5.1 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.11.9 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 58bee32201..1d8bc4a0ea 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "0.11.9", + "version": "0.11.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,16 +31,16 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.5.0", + "@backstage/catalog-client": "^0.5.1", "@backstage/catalog-model": "^0.9.6", "@backstage/config": "^0.1.11", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.4", "@backstage/integration": "^0.6.9", "@backstage/integration-react": "^0.1.13", - "@backstage/plugin-catalog-react": "^0.6.2", - "@backstage/theme": "^0.2.12", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/theme": "^0.2.13", "@backstage/types": "^0.1.1", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -66,10 +66,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index aeb47b2789..11d12282d2 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -14,38 +14,40 @@ * limitations under the License. */ -import React, { useState, useEffect, memo, useMemo } from 'react'; -import { makeStyles, Theme, createStyles } from '@material-ui/core/styles'; -import Stepper from '@material-ui/core/Stepper'; -import Step from '@material-ui/core/Step'; -import StepLabel from '@material-ui/core/StepLabel'; -import Grid from '@material-ui/core/Grid'; -import Typography from '@material-ui/core/Typography'; -import { useParams } from 'react-router'; -import { useTaskEventStream } from '../hooks/useEventStream'; -import LazyLog from 'react-lazylog/build/LazyLog'; +import { + Content, + ErrorPage, + Header, + Lifecycle, + Page, + Progress, +} from '@backstage/core-components'; +import { BackstageTheme } from '@backstage/theme'; import { CircularProgress, Paper, StepButton, StepIconProps, } from '@material-ui/core'; -import { Status, TaskOutput } from '../../types'; -import { DateTime, Interval } from 'luxon'; -import { useInterval } from 'react-use'; -import Check from '@material-ui/icons/Check'; +import Grid from '@material-ui/core/Grid'; +import Step from '@material-ui/core/Step'; +import StepLabel from '@material-ui/core/StepLabel'; +import Stepper from '@material-ui/core/Stepper'; +import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; import Cancel from '@material-ui/icons/Cancel'; +import Check from '@material-ui/icons/Check'; import FiberManualRecordIcon from '@material-ui/icons/FiberManualRecord'; import classNames from 'classnames'; -import { BackstageTheme } from '@backstage/theme'; +import { DateTime, Interval } from 'luxon'; +import React, { memo, Suspense, useEffect, useMemo, useState } from 'react'; +import { useParams } from 'react-router'; +import { useInterval } from 'react-use'; +import { Status, TaskOutput } from '../../types'; +import { useTaskEventStream } from '../hooks/useEventStream'; import { TaskPageLinks } from './TaskPageLinks'; -import { - Page, - Header, - Lifecycle, - Content, - ErrorPage, -} from '@backstage/core-components'; + +const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog')); // typings are wrong for this library, so fallback to not parsing types. const humanizeDuration = require('humanize-duration'); @@ -213,9 +215,17 @@ export const TaskStatusStepper = memo( const TaskLogger = memo(({ log }: { log: string }) => { return ( -
- -
+ }> +
+ +
+
); }); diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index e950678cef..6bd39200c0 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 0.0.5 + +### Patch Changes + +- 36350bf8b3: Pinning version of elastic search client to 7.13.0 to prevent breaking change towards third party ElasticSearch clusters on 7.14.0. + ## 0.0.4 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 2d5cabf725..df0a783396 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", "description": "A module for the search backend that implements search using ElasticSearch", - "version": "0.0.4", + "version": "0.0.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-common": "^0.9.7", - "@backstage/cli": "^0.8.0", + "@backstage/backend-common": "^0.9.9", + "@backstage/cli": "^0.8.2", "@elastic/elasticsearch-mock": "^0.3.0" }, "files": [ diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 66c4a61ead..808c847914 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-search +## 0.4.17 + +### Patch Changes + +- 5dcea2586c: Added `SearchModal` component. + + Now you can import `SearchModal` in your apps: + + ```js + import { SearchModal } from '@backstage/plugin-search'; + ``` + + You can also use the `SidebarSearchModal` component to integrate it into the sidebar of your sample apps: + + ```js + import { SidebarSearchModal } from '@backstage/plugin-search'; + ``` + +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.4.16 ### Patch Changes diff --git a/plugins/search/api-report.md b/plugins/search/api-report.md index c638fb4af6..0e0ad01fca 100644 --- a/plugins/search/api-report.md +++ b/plugins/search/api-report.md @@ -126,6 +126,24 @@ export const SearchFilterNext: { Select(props: Omit & Component): JSX.Element; }; +// Warning: (ae-missing-release-tag) "SearchModal" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const SearchModal: ({ + open, + toggleModal, +}: SearchModalProps) => JSX.Element; + +// Warning: (ae-missing-release-tag) "SearchModalProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface SearchModalProps { + // (undocumented) + open?: boolean; + // (undocumented) + toggleModal: () => void; +} + // Warning: (ae-missing-release-tag) "SearchPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -179,6 +197,11 @@ export const SearchType: ({ // @public (undocumented) export const SidebarSearch: () => JSX.Element; +// Warning: (ae-missing-release-tag) "SidebarSearchModal" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const SidebarSearchModal: () => JSX.Element; + // Warning: (ae-forgotten-export) The symbol "SearchContextValue" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "useSearch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/search/package.json b/plugins/search/package.json index 60e8a8eb3c..9d25ec723d 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search", "description": "The Backstage plugin that provides your backstage app with search", - "version": "0.4.16", + "version": "0.4.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,12 +32,12 @@ "dependencies": { "@backstage/catalog-model": "^0.9.6", "@backstage/config": "^0.1.11", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.4", - "@backstage/plugin-catalog-react": "^0.6.2", + "@backstage/plugin-catalog-react": "^0.6.3", "@backstage/search-common": "^0.2.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@backstage/types": "^0.1.1", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -51,10 +51,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/search/src/components/SearchModal/SearchModal.test.tsx b/plugins/search/src/components/SearchModal/SearchModal.test.tsx new file mode 100644 index 0000000000..d7f873abf3 --- /dev/null +++ b/plugins/search/src/components/SearchModal/SearchModal.test.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { screen } from '@testing-library/react'; +import { renderInTestApp } from '@backstage/test-utils'; +import userEvent from '@testing-library/user-event'; +import { configApiRef } from '@backstage/core-plugin-api'; +import { + ApiProvider, + ApiRegistry, + ConfigReader, +} from '@backstage/core-app-api'; +import { rootRouteRef } from '../../plugin'; +import { searchApiRef } from '../../apis'; + +import { SearchModal } from './SearchModal'; + +jest.mock('../SearchContext', () => ({ + ...jest.requireActual('../SearchContext'), + useSearch: jest.fn().mockReturnValue({ + result: {}, + }), +})); + +describe('SearchModal', () => { + const query = jest.fn().mockResolvedValue({}); + + const apiRegistry = ApiRegistry.from([ + [configApiRef, new ConfigReader({ app: { title: 'Mock app' } })], + [searchApiRef, { query }], + ]); + + const toggleModal = jest.fn(); + + it('Should render the Modal correctly', async () => { + await renderInTestApp( + + + , + { + mountedRoutes: { + '/search': rootRouteRef, + }, + }, + ); + + expect(screen.getByRole('dialog')).toBeInTheDocument(); + }); + + it('Calls toggleModal handler', async () => { + await renderInTestApp( + + + , + { + mountedRoutes: { + '/search': rootRouteRef, + }, + }, + ); + userEvent.keyboard('{esc}'); + expect(toggleModal).toHaveBeenCalledTimes(1); + }); +}); diff --git a/plugins/search/src/components/SearchModal/SearchModal.tsx b/plugins/search/src/components/SearchModal/SearchModal.tsx new file mode 100644 index 0000000000..feabbc55af --- /dev/null +++ b/plugins/search/src/components/SearchModal/SearchModal.tsx @@ -0,0 +1,162 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useEffect, useState } from 'react'; +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + Grid, + List, + Paper, +} from '@material-ui/core'; +import { Launch } from '@material-ui/icons/'; +import { makeStyles } from '@material-ui/core/styles'; +import { SearchBarBase } from '../SearchBar'; +import { DefaultResultListItem } from '../DefaultResultListItem'; +import { SearchResult } from '../SearchResult'; +import { SearchContextProvider, useSearch } from '../SearchContext'; +import { SearchResultPager } from '../SearchResultPager'; +import { useRouteRef } from '@backstage/core-plugin-api'; +import { Link } from '@backstage/core-components'; +import { rootRouteRef } from '../../plugin'; + +import { useDebounce } from 'react-use'; + +export interface SearchModalProps { + open?: boolean; + toggleModal: () => void; +} + +const useStyles = makeStyles(theme => ({ + container: { + borderRadius: 30, + display: 'flex', + height: '2.4em', + }, + input: { + flex: 1, + }, + // Reduces default height of the modal, keeping a gap of 128px between the top and bottom of the page. + paperFullWidth: { height: 'calc(100% - 128px)' }, + dialogActionsContainer: { padding: theme.spacing(1, 3) }, + viewResultsLink: { verticalAlign: '0.5em' }, +})); + +export const Modal = ({ open = true, toggleModal }: SearchModalProps) => { + const getSearchLink = useRouteRef(rootRouteRef); + const classes = useStyles(); + + const { term, setTerm } = useSearch(); + const [value, setValue] = useState(term); + + useEffect(() => { + setValue(prevValue => (prevValue !== term ? term : prevValue)); + }, [term]); + + useDebounce(() => setTerm(value), 1000, [value]); + + const handleQuery = (newValue: string) => { + setValue(newValue); + }; + + const handleClear = () => setValue(''); + + const handleResultClick = () => { + toggleModal(); + handleClear(); + }; + + const handleKeyPress = () => { + handleResultClick(); + }; + + return ( + + + + + + + + + + + View Full Results + + + + + + + {({ results }) => ( + + {results.map(({ document }) => ( +
+ +
+ ))} +
+ )} +
+
+ + + + + + + +
+ ); +}; + +export const SearchModal = ({ open = true, toggleModal }: SearchModalProps) => { + return ( + + + + ); +}; diff --git a/plugins/search/src/components/SearchModal/index.tsx b/plugins/search/src/components/SearchModal/index.tsx new file mode 100644 index 0000000000..ec0053951d --- /dev/null +++ b/plugins/search/src/components/SearchModal/index.tsx @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { SearchModal } from './SearchModal'; +export type { SearchModalProps } from './SearchModal'; diff --git a/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx new file mode 100644 index 0000000000..4d4fa1a9fc --- /dev/null +++ b/plugins/search/src/components/SidebarSearchModal/SidebarSearchModal.tsx @@ -0,0 +1,36 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { useState } from 'react'; +import SearchIcon from '@material-ui/icons/Search'; +import { SearchModal } from '../SearchModal'; +import { SidebarItem } from '@backstage/core-components'; + +export const SidebarSearchModal = () => { + const [open, setOpen] = useState(false); + const toggleModal = (): void => setOpen(prevState => !prevState); + + return ( + <> + + + + ); +}; diff --git a/plugins/search/src/components/SidebarSearchModal/index.ts b/plugins/search/src/components/SidebarSearchModal/index.ts new file mode 100644 index 0000000000..849180878d --- /dev/null +++ b/plugins/search/src/components/SidebarSearchModal/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export { SidebarSearchModal } from './SidebarSearchModal'; diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx index 8b6493ae9b..6a4eae4001 100644 --- a/plugins/search/src/components/index.tsx +++ b/plugins/search/src/components/index.tsx @@ -19,8 +19,10 @@ export * from './Filters'; export * from './SearchBar'; export * from './SearchContext'; export * from './SearchFilter'; +export * from './SearchModal'; export * from './SearchPage'; export * from './SearchResult'; export * from './SearchResultPager'; export * from './SearchType'; export * from './SidebarSearch'; +export * from './SidebarSearchModal'; diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts index 07e4443f04..09cf204017 100644 --- a/plugins/search/src/index.ts +++ b/plugins/search/src/index.ts @@ -28,20 +28,23 @@ export { SearchContextProvider, SearchFilter, SearchFilterNext, + SearchModal, SearchPage as Router, SearchResultPager, SearchType, SidebarSearch, useSearch, } from './components'; +export type { SearchModalProps } from './components'; export type { FiltersState } from './components'; export { DefaultResultListItem, + HomePageSearchBar, SearchBarNext, SearchPage, SearchPageNext, searchPlugin as plugin, searchPlugin, SearchResult, - HomePageSearchBar, + SidebarSearchModal, } from './plugin'; diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts index 491582aead..b8c016fb36 100644 --- a/plugins/search/src/plugin.ts +++ b/plugins/search/src/plugin.ts @@ -122,6 +122,18 @@ export const SearchResultNext = searchPlugin.provide( }), ); +export const SidebarSearchModal = searchPlugin.provide( + createComponentExtension({ + name: 'SidebarSearchModal', + component: { + lazy: () => + import('./components/SidebarSearchModal').then( + m => m.SidebarSearchModal, + ), + }, + }), +); + export const DefaultResultListItem = searchPlugin.provide( createComponentExtension({ name: 'DefaultResultListItem', diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 2f44314100..b0bcaad261 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-sentry +## 0.3.28 + +### Patch Changes + +- 33d7bd0baa: fix: sentry-plugin can forward identity token to backend (for case when it requires authorization) +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.3.27 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 005bfb2b3a..ee2421bf7f 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry", "description": "A Backstage plugin that integrates towards Sentry", - "version": "0.3.27", + "version": "0.3.28", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.6", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/plugin-catalog-react": "^0.6.2", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 8b6f57bc52..e73e047bc1 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -21,9 +21,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@backstage/types": "^0.1.1", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -38,10 +38,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 30cdd83368..bb55e68954 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -34,10 +34,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 632de7e413..9d9ffdb370 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -32,10 +32,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 31b12a97a9..839ad1b399 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -31,9 +31,9 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -45,10 +45,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index a825b9a008..be100b9dea 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-techdocs +## 0.12.5 + +### Patch Changes + +- fe5738fe1c: Lazy load `LazyLog` as it is rarely used. +- 53c9ad7e04: Update font weight for headings in TechDocs +- Updated dependencies + - @backstage/core-components@0.7.3 + - @backstage/theme@0.2.13 + - @backstage/plugin-search@0.4.17 + - @backstage/core-plugin-api@0.1.13 + - @backstage/plugin-catalog-react@0.6.3 + ## 0.12.4 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index c72bcab499..79841a0621 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs", "description": "The Backstage plugin that renders technical documentation for your components", - "version": "0.12.4", + "version": "0.12.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,15 +34,15 @@ "dependencies": { "@backstage/catalog-model": "^0.9.6", "@backstage/config": "^0.1.11", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.4", "@backstage/integration": "^0.6.9", "@backstage/integration-react": "^0.1.13", "@backstage/plugin-catalog": "^0.7.2", - "@backstage/plugin-catalog-react": "^0.6.2", - "@backstage/plugin-search": "^0.4.16", - "@backstage/theme": "^0.2.12", + "@backstage/plugin-catalog-react": "^0.6.3", + "@backstage/plugin-search": "^0.4.17", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -61,10 +61,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index 9bb0a7bbad..4e12ac18d1 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -198,6 +198,7 @@ export const useTechDocsReaderDom = (): Element | null => { .md-footer-nav__link { width: 20rem;} .md-content { margin-left: 20rem; max-width: calc(100% - 20rem * 2 - 3rem); } .md-typeset { font-size: 1rem; } + .md-typeset h1, .md-typeset h2, .md-typeset h3 { font-weight: bold; } .md-nav { font-size: 1rem; } .md-grid { max-width: 90vw; margin: 0 } .md-typeset table:not([class]) { diff --git a/plugins/techdocs/src/reader/components/TechDocsBuildLogs.test.tsx b/plugins/techdocs/src/reader/components/TechDocsBuildLogs.test.tsx index d1bb99b76c..fa82527282 100644 --- a/plugins/techdocs/src/reader/components/TechDocsBuildLogs.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsBuildLogs.test.tsx @@ -23,9 +23,9 @@ import { // react-lazylog is based on a react-virtualized component which doesn't // write the content to the dom, so we mock it. -jest.mock('react-lazylog', () => { +jest.mock('react-lazylog/build/LazyLog', () => { return { - LazyLog: ({ text }: { text: string }) => { + default: ({ text }: { text: string }) => { return

{text}

; }, }; @@ -46,18 +46,20 @@ describe('', () => { }); describe('', () => { - it('should render with empty log', () => { + it('should render with empty log', async () => { const onClose = jest.fn(); const rendered = render( , ); expect(rendered.getByText(/Build Details/i)).toBeInTheDocument(); - expect(rendered.getByText(/Waiting for logs.../i)).toBeInTheDocument(); + expect( + await rendered.findByText(/Waiting for logs.../i), + ).toBeInTheDocument(); expect(onClose).toBeCalledTimes(0); }); - it('should render with empty logs', () => { + it('should render logs', async () => { const onClose = jest.fn(); const rendered = render( ', () => { />, ); expect(rendered.getByText(/Build Details/i)).toBeInTheDocument(); - expect( - rendered.queryByText(/Waiting for logs.../i), - ).not.toBeInTheDocument(); - expect(rendered.getByText(/Line 1/i)).toBeInTheDocument(); - expect(rendered.getByText(/Line 2/i)).toBeInTheDocument(); + expect(await rendered.findByText(/Line 1/i)).toBeInTheDocument(); + expect(await rendered.findByText(/Line 2/i)).toBeInTheDocument(); expect(onClose).toBeCalledTimes(0); }); @@ -80,7 +79,7 @@ describe('', () => { const rendered = render( , ); - rendered.getByRole('button').click(); + rendered.getByTitle('Close the drawer').click(); expect(onClose).toBeCalledTimes(1); }); diff --git a/plugins/techdocs/src/reader/components/TechDocsBuildLogs.tsx b/plugins/techdocs/src/reader/components/TechDocsBuildLogs.tsx index 4283932fb3..8c739a9bcf 100644 --- a/plugins/techdocs/src/reader/components/TechDocsBuildLogs.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsBuildLogs.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Progress } from '@backstage/core-components'; import { Button, createStyles, @@ -25,9 +26,9 @@ import { Typography, } from '@material-ui/core'; import Close from '@material-ui/icons/Close'; -import * as React from 'react'; -import { useState } from 'react'; -import { LazyLog } from 'react-lazylog'; +import React, { Suspense, useState } from 'react'; + +const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog')); const useDrawerStyles = makeStyles((theme: Theme) => createStyles({ @@ -83,15 +84,17 @@ export const TechDocsBuildLogsDrawerContent = ({ - + }> + + ); }; diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 9f27679615..5e32d84497 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -28,11 +28,11 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.5", - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.3", "@backstage/plugin-catalog-react": "^0.6.1", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -41,10 +41,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 9ec642892b..dc63957cb9 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", - "@backstage/theme": "^0.2.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -43,10 +43,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index 1ef31ecd6d..bbab66e455 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.7.2", - "@backstage/core-plugin-api": "^0.1.12", + "@backstage/core-components": "^0.7.3", + "@backstage/core-plugin-api": "^0.1.13", "@backstage/errors": "^0.1.3", - "@backstage/theme": "^0.2.12", + "@backstage/theme": "^0.2.13", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -36,10 +36,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.8.1", - "@backstage/core-app-api": "^0.1.19", + "@backstage/cli": "^0.8.2", + "@backstage/core-app-api": "^0.1.20", "@backstage/dev-utils": "^0.2.12", - "@backstage/test-utils": "^0.1.20", + "@backstage/test-utils": "^0.1.21", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/scripts/snyk-github-issue-sync.ts b/scripts/snyk-github-issue-sync.ts index d7f64cf1ff..f1015f8139 100644 --- a/scripts/snyk-github-issue-sync.ts +++ b/scripts/snyk-github-issue-sync.ts @@ -82,9 +82,9 @@ const fetchSnykGithubIssueMap = async (): Promise> => { const generateIssueBody = (vulnerability: Vulnerability) => ` ## Affecting Packages/Plugins -${Array.from(vulnerability.packages).map( - ({ name, target }) => `* [${name}](${target})\n`, -)} +${Array.from(vulnerability.packages) + .map(({ name, target }) => `* [${name}](${target})`) + .join('\n')} ${vulnerability.description} `; diff --git a/yarn.lock b/yarn.lock index 42c43fe4b7..1b1a658a1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4714,22 +4714,7 @@ "@gar/promisify" "^1.0.1" semver "^7.3.5" -"@npmcli/git@^2.0.1": - version "2.0.6" - resolved "https://registry.npmjs.org/@npmcli/git/-/git-2.0.6.tgz#47b97e96b2eede3f38379262fa3bdfa6eae57bf2" - integrity sha512-a1MnTfeRPBaKbFY07fd+6HugY1WAkKJzdiJvlRub/9o5xz2F/JtPacZZapx5zRJUQFIzSL677vmTSxEcDMrDbg== - dependencies: - "@npmcli/promise-spawn" "^1.1.0" - lru-cache "^6.0.0" - mkdirp "^1.0.3" - npm-pick-manifest "^6.0.0" - promise-inflight "^1.0.1" - promise-retry "^2.0.1" - semver "^7.3.2" - unique-filename "^1.1.1" - which "^2.0.2" - -"@npmcli/git@^2.1.0": +"@npmcli/git@^2.0.1", "@npmcli/git@^2.1.0": version "2.1.0" resolved "https://registry.npmjs.org/@npmcli/git/-/git-2.1.0.tgz#2fbd77e147530247d37f325930d457b3ebe894f6" integrity sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw== @@ -4802,7 +4787,7 @@ dependencies: json-parse-even-better-errors "^2.3.1" -"@npmcli/promise-spawn@^1.1.0", "@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": +"@npmcli/promise-spawn@^1.2.0", "@npmcli/promise-spawn@^1.3.2": version "1.3.2" resolved "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz#42d4e56a8e9274fba180dabc0aea6e38f29274f5" integrity sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg== @@ -5481,10 +5466,10 @@ resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-12.0.0.tgz#0b1e41bb436d5c1c20714703629514d64c3c0f06" integrity sha512-5Uud/TmzakqmdUNCZpD8JFQRa2VG3dVd3DanSMpU/nVdu6K5LyX8EMU3Tz1vGP18Wih8iAu/sBSJhntNzw7e6w== -"@spotify/eslint-config-react@^10.0.0": - version "10.0.0" - resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-10.0.0.tgz#6f83ada05f79b49c1f9def5b8815e3231ed24969" - integrity sha512-MozX6W3aMp7EQPliuUQYI58Ni5vh65mItXMG0CgZBj0v1ZEeZVM5XS/nqhsCaIHYXskmZM2O1qqLFaEg5PqGdg== +"@spotify/eslint-config-react@^12.0.0": + version "12.0.0" + resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-12.0.0.tgz#5b8d4bc3b81a8ec2824648f482f1f6c3cf711893" + integrity sha512-lNHZRtJesNA273OJHBVUGAg2JYyVDZ+bsT7h3OwnX1HYgejJ3YcKPSziPM8TGFAN8DruH3tHFfaM63uAIA1+uw== "@spotify/eslint-config-typescript@^12.0.0": version "12.0.0" @@ -7303,9 +7288,9 @@ pretty-format "^26.0.0" "@types/jquery@^3.3.34": - version "3.5.5" - resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.5.tgz#2c63f47c9c8d96693d272f5453602afd8338c903" - integrity sha512-6RXU9Xzpc6vxNrS6FPPapN1SxSHgQ336WC6Jj/N8q30OiaBZ00l1GBgeP7usjVZPivSkGUfL1z/WW6TX989M+w== + version "3.5.8" + resolved "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.8.tgz#83bfbcdf4e625c5471590f92703c06aadb052a09" + integrity sha512-cXk6NwqjDYg+UI9p2l3x0YmPa4m7RrXqmbK4IpVVpRJiYXU/QTo+UZrn54qfE1+9Gao4qpYqUnxm5ZCy2FTXAw== dependencies: "@types/sizzle" "*" @@ -7575,9 +7560,9 @@ integrity sha512-4QQmOF5KlwfxJ5IGXFIudkeLCdMABz03RcUXu+LCb24zmln8QW6aDjuGl4d4XPVLf2j+FnjelHTP7dvceAFbhA== "@types/nunjucks@^3.1.4": - version "3.1.5" - resolved "https://registry.npmjs.org/@types/nunjucks/-/nunjucks-3.1.5.tgz#e0782333b75b4ff9ac9213aa160ca8c5132eaf57" - integrity sha512-0zEdmQNNvQ+xyV9kqQvAV93UVroTwhE78toVUDT0GBnGcW2jQBZnB4al9qq2LqI5qHOqROy/DvvAY/UwrbvV1A== + version "3.2.0" + resolved "https://registry.npmjs.org/@types/nunjucks/-/nunjucks-3.2.0.tgz#2303629c91cdd39de103af979046d4a8590351e4" + integrity sha512-1FM36Hm3EdidJmWlZZafkg/kZME0UZ/0vQ46JE8R7R0JqQafah0r+d4i6d/MJg5DnKxEeAOAeifEVkzo7fEvGg== "@types/oauth@*": version "0.9.1" @@ -11309,7 +11294,7 @@ commander@7.1.0: resolved "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff" integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg== -commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.7.1, commander@~2.20.3: +commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.7.1: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -12000,16 +11985,6 @@ css-select-base-adapter@^0.1.1: resolved "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== -css-select@^1.1.0: - version "1.2.0" - resolved "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" - integrity sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg= - dependencies: - boolbase "~1.0.0" - css-what "2.1" - domutils "1.5.1" - nth-check "~1.0.1" - css-select@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef" @@ -12020,6 +11995,17 @@ css-select@^2.0.0: domutils "^1.7.0" nth-check "^1.0.2" +css-select@^4.1.3: + version "4.1.3" + resolved "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz#a70440f70317f2669118ad74ff105e65849c7067" + integrity sha512-gT3wBNd9Nj49rAbmtFHj1cljIAOLYSX1nZ8CB7TBO3INYckygm5B7LISU/szY//YmdiSLbJvDLOx9VnMVpMBxA== + dependencies: + boolbase "^1.0.0" + css-what "^5.0.0" + domhandler "^4.2.0" + domutils "^2.6.0" + nth-check "^2.0.0" + css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" @@ -12049,16 +12035,16 @@ css-vendor@^2.0.8: "@babel/runtime" "^7.8.3" is-in-browser "^1.0.2" -css-what@2.1: - version "2.1.3" - resolved "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== - css-what@^3.2.1: version "3.2.1" resolved "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1" integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw== +css-what@^5.0.0: + version "5.1.0" + resolved "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + css.escape@1.5.1, css.escape@^1.5.1: version "1.5.1" resolved "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz#42e27d4fa04ae32f931a4b4d4191fa9cddee97cb" @@ -13016,7 +13002,7 @@ dom-accessibility-api@^0.5.4, dom-accessibility-api@^0.5.6: resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz#3f5d43b52c7a3bd68b5fb63fa47b4e4c1fdf65a9" integrity sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw== -dom-converter@^0.2: +dom-converter@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== @@ -13065,7 +13051,7 @@ domain-browser@^1.1.1: resolved "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== -domelementtype@1, domelementtype@^1.3.1: +domelementtype@1: version "1.3.1" resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== @@ -13075,6 +13061,11 @@ domelementtype@^2.0.1, domelementtype@^2.1.0: resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz#a851c080a6d1c3d94344aed151d99f669edf585e" integrity sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w== +domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + domexception@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" @@ -13082,13 +13073,6 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" -domhandler@^2.3.0: - version "2.4.2" - resolved "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz#8805097e933d65e85546f726d60f5eb88b44f803" - integrity sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA== - dependencies: - domelementtype "1" - domhandler@^3.0, domhandler@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz#51cd13efca31da95bbb0c5bee3a48300e333b3e9" @@ -13103,20 +13087,19 @@ domhandler@^4.0.0: dependencies: domelementtype "^2.1.0" +domhandler@^4.2.0: + version "4.2.2" + resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f" + integrity sha512-PzE9aBMsdZO8TK4BnuJwH0QT41wgMbRzuZrHUcpYncEjmQazq8QEaBWgLG7ZyC/DAZKEgglpIA6j4Qn/HmxS3w== + dependencies: + domelementtype "^2.2.0" + dompurify@^2.1.1, dompurify@^2.2.9: version "2.3.3" resolved "https://registry.npmjs.org/dompurify/-/dompurify-2.3.3.tgz#c1af3eb88be47324432964d8abc75cf4b98d634c" integrity sha512-dqnqRkPMAjOZE0FogZ+ceJNM2dZ3V/yNOuFB7+39qpO93hHhfRpHw3heYQC7DPK9FqbQTfBKUJhiSfz4MvXYwg== -domutils@1.5.1: - version "1.5.1" - resolved "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" - integrity sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8= - dependencies: - dom-serializer "0" - domelementtype "1" - -domutils@^1.5.1, domutils@^1.7.0: +domutils@^1.7.0: version "1.7.0" resolved "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" integrity sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg== @@ -13133,6 +13116,15 @@ domutils@^2.0.0: domelementtype "^2.0.1" domhandler "^4.0.0" +domutils@^2.5.2, domutils@^2.6.0: + version "2.8.0" + resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -13411,7 +13403,7 @@ ent@^2.2.0: resolved "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= -entities@^1.1.1, entities@^1.1.2: +entities@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== @@ -16167,18 +16159,6 @@ html-webpack-plugin@^5.3.1: pretty-error "^2.1.1" tapable "^2.0.0" -htmlparser2@^3.3.0: - version "3.10.1" - resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== - dependencies: - domelementtype "^1.3.1" - domhandler "^2.3.0" - domutils "^1.5.1" - entities "^1.1.1" - inherits "^2.0.1" - readable-stream "^3.1.1" - htmlparser2@^4.0: version "4.1.0" resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78" @@ -16189,6 +16169,16 @@ htmlparser2@^4.0: domutils "^2.0.0" entities "^2.0.0" +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -20905,6 +20895,11 @@ node-abi@^2.21.0: dependencies: semver "^5.4.1" +node-abort-controller@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz#f91fa50b1dee3f909afabb7e261b1e1d6b0cb74e" + integrity sha512-/ujIVxthRs+7q6hsdjHMaj8hRG9NuWmwrz+JdRwZ14jdFoKSkm+vDsCbF9PLpnSqjaWQJuTmVtcWHNLr+vrOFw== + node-addon-api@^3.0.0: version "3.2.1" resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" @@ -21312,13 +21307,20 @@ npm-run-path@^4.0.0, npm-run-path@^4.0.1: gauge "~2.7.3" set-blocking "~2.0.0" -nth-check@^1.0.2, nth-check@~1.0.1: +nth-check@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" integrity sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg== dependencies: boolbase "~1.0.0" +nth-check@^2.0.0: + version "2.0.1" + resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" + nullthrows@^1.0.0, nullthrows@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" @@ -23111,12 +23113,12 @@ pretty-bytes@^5.3.0, pretty-bytes@^5.6.0: integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== pretty-error@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" - integrity sha1-X0+HyPkeWuPzuoerTPXgOxoX8aM= + version "2.1.2" + resolved "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6" + integrity sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw== dependencies: - renderkid "^2.0.1" - utila "~0.4" + lodash "^4.17.20" + renderkid "^2.0.4" pretty-format@^24.9.0: version "24.9.0" @@ -23148,10 +23150,10 @@ printj@~1.1.0: resolved "https://registry.npmjs.org/printj/-/printj-1.1.2.tgz#d90deb2975a8b9f600fb3a1c94e3f4c53c78a222" integrity sha512-zA2SmoLaxZyArQTOPj5LXecR+RagfPSU5Kw1qP+jkWeNlrq+eJZyY2oS68SU1Z/7/myXM4lo9716laOFAVStCQ== -prismjs@^1.21.0, prismjs@^1.22.0, prismjs@~1.24.0: - version "1.24.1" - resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.24.1.tgz#c4d7895c4d6500289482fa8936d9cdd192684036" - integrity sha512-mNPsedLuk90RVJioIky8ANZEwYm5w9LcvCXrxHlwf4fNVSn8jEipMybMkWUyyF0JhnC+C4VcOVSBuHRKs1L5Ow== +prismjs@^1.21.0, prismjs@^1.22.0, prismjs@~1.25.0: + version "1.25.0" + resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" + integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== private@^0.1.8: version "0.1.8" @@ -23245,7 +23247,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prompts@2.4.0, prompts@^2.0.1: +prompts@2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7" integrity sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ== @@ -23253,10 +23255,10 @@ prompts@2.4.0, prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" -prompts@^2.4.0: - version "2.4.1" - resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" - integrity sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ== +prompts@^2.0.1, prompts@^2.4.0: + version "2.4.2" + resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== dependencies: kleur "^3.0.3" sisteransi "^1.0.5" @@ -24448,13 +24450,13 @@ reflect-metadata@^0.1.13: integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== refractor@^3.1.0, refractor@^3.2.0: - version "3.4.0" - resolved "https://registry.npmjs.org/refractor/-/refractor-3.4.0.tgz#62bd274b06c942041f390c371b676eb67cb0a678" - integrity sha512-dBeD02lC5eytm9Gld2Mx0cMcnR+zhSnsTfPpWqFaMgUMJfC9A6bcN3Br/NaXrnBJcuxnLFR90k1jrkaSyV8umg== + version "3.5.0" + resolved "https://registry.npmjs.org/refractor/-/refractor-3.5.0.tgz#334586f352dda4beaf354099b48c2d18e0819aec" + integrity sha512-QwPJd3ferTZ4cSPPjdP5bsYHMytwWYnAN5EEnLtGvkqp/FCCnGsBgxrm9EuIDnjUC3Uc/kETtvVi7fSIVC74Dg== dependencies: hastscript "^6.0.0" parse-entities "^2.0.0" - prismjs "~1.24.0" + prismjs "~1.25.0" regenerate-unicode-properties@^8.2.0: version "8.2.0" @@ -24705,16 +24707,16 @@ remove-trailing-spaces@^1.0.6: resolved "https://registry.npmjs.org/remove-trailing-spaces/-/remove-trailing-spaces-1.0.8.tgz#4354d22f3236374702f58ee373168f6d6887ada7" integrity sha512-O3vsMYfWighyFbTd8hk8VaSj9UAGENxAtX+//ugIst2RMk5e03h6RoIS+0ylsFxY1gvmPuAY/PO4It+gPEeySA== -renderkid@^2.0.1: - version "2.0.3" - resolved "https://registry.npmjs.org/renderkid/-/renderkid-2.0.3.tgz#380179c2ff5ae1365c522bf2fcfcff01c5b74149" - integrity sha512-z8CLQp7EZBPCwCnncgf9C4XAi3WR0dv+uWu/PjIyhhAb5d6IJ/QZqlHFprHeKT+59//V6BNUsLbvN8+2LarxGA== +renderkid@^2.0.4: + version "2.0.7" + resolved "https://registry.npmjs.org/renderkid/-/renderkid-2.0.7.tgz#464f276a6bdcee606f4a15993f9b29fc74ca8609" + integrity sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ== dependencies: - css-select "^1.1.0" - dom-converter "^0.2" - htmlparser2 "^3.3.0" - strip-ansi "^3.0.0" - utila "^0.4.0" + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^3.0.1" repeat-element@^1.1.2: version "1.1.3" @@ -27718,12 +27720,9 @@ uc.micro@^1.0.1, uc.micro@^1.0.5: integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== uglify-js@^3.1.4: - version "3.8.0" - resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.8.0.tgz#f3541ae97b2f048d7e7e3aa4f39fd8a1f5d7a805" - integrity sha512-ugNSTT8ierCsDHso2jkBHXYrU8Y5/fY2ZUprfrJUiD7YpuFvV4jODLFmb3h4btQjqr5Nh4TX4XtgDfCU1WdioQ== - dependencies: - commander "~2.20.3" - source-map "~0.6.1" + version "3.14.3" + resolved "https://registry.npmjs.org/uglify-js/-/uglify-js-3.14.3.tgz#c0f25dfea1e8e5323eccf59610be08b6043c15cf" + integrity sha512-mic3aOdiq01DuSVx0TseaEzMIVqebMZ0Z3vaeDhFEh9bsc24hV1TFvN74reA2vs08D0ZWfNjAcJ3UbVLaBss+g== uid-number@0.0.6: version "0.0.6" @@ -28281,7 +28280,7 @@ util@^0.12.3: safe-buffer "^5.1.2" which-typed-array "^1.1.2" -utila@^0.4.0, utila@~0.4: +utila@~0.4: version "0.4.0" resolved "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= @@ -29161,9 +29160,9 @@ xpath@0.0.32: integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== xss@^1.0.8: - version "1.0.9" - resolved "https://registry.npmjs.org/xss/-/xss-1.0.9.tgz#3ffd565571ff60d2e40db7f3b80b4677bec770d2" - integrity sha512-2t7FahYnGJys6DpHLhajusId7R0Pm2yTmuL0GV9+mV0ZlaLSnb2toBmppATfg5sWIhZQGlsTLoecSzya+l4EAQ== + version "1.0.10" + resolved "https://registry.npmjs.org/xss/-/xss-1.0.10.tgz#5cd63a9b147a755a14cb0455c7db8866120eb4d2" + integrity sha512-qmoqrRksmzqSKvgqzN0055UFWY7OKx1/9JWeRswwEVX9fCG5jcYRxa/A2DHcmZX6VJvjzHRQ2STeeVcQkrmLSw== dependencies: commander "^2.20.3" cssfilter "0.0.10" @@ -29441,6 +29440,11 @@ zip-stream@^4.1.0: compress-commons "^4.1.0" readable-stream "^3.6.0" +zod@^3.9.5: + version "3.11.6" + resolved "https://registry.npmjs.org/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483" + integrity sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg== + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.npmjs.org/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"