diff --git a/.changeset/chilled-schools-raise.md b/.changeset/chilled-schools-raise.md deleted file mode 100644 index 56058b6125..0000000000 --- a/.changeset/chilled-schools-raise.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -Make sure that the unique databases names generated by `TestDatabases.create()` -don't collide. diff --git a/.changeset/clean-jokes-think.md b/.changeset/clean-jokes-think.md deleted file mode 100644 index a4fc65e6fc..0000000000 --- a/.changeset/clean-jokes-think.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Expose a new composable `TechDocsIndexPage` and a `DefaultTechDocsHome` with support for starring docs and filtering on owned, starred, owner, and tags. - -You can migrate to the new UI view by making the following changes in your `App.tsx`: - -```diff -- } /> -+ }> -+ -+ -+ } -+ /> -``` diff --git a/.changeset/eighty-glasses-mate.md b/.changeset/eighty-glasses-mate.md deleted file mode 100644 index d71ef8fd42..0000000000 --- a/.changeset/eighty-glasses-mate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Use a more informative error message when URL reading isn't allowed due to no reader matching the target URL. diff --git a/.changeset/four-buttons-design.md b/.changeset/four-buttons-design.md deleted file mode 100644 index 172d011057..0000000000 --- a/.changeset/four-buttons-design.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -Adds ability to send an ExternalId with the assume role request to AWS diff --git a/.changeset/green-ducks-tan.md b/.changeset/green-ducks-tan.md deleted file mode 100644 index e096bfeb36..0000000000 --- a/.changeset/green-ducks-tan.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Added UI Schema support for array items for example, support EntityPicker within an array field diff --git a/.changeset/happy-lies-wink.md b/.changeset/happy-lies-wink.md deleted file mode 100644 index 709cd3862e..0000000000 --- a/.changeset/happy-lies-wink.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -- Adds a new field `EntityNamePicker` that can be used in scaffolder templates to accept and validate an entity name. This field is registered by default, and can be used in templates by setting the `ui:field` property to `EntityNamePicker`. If you've customized your scaffolder field extensions, you can include this one by adding it when registering the scaffolder route: - -```diff -import { - ScaffolderFieldExtensions, -+ EntityNamePickerFieldExtension, -} from '@backstage/plugin-scaffolder'; - - }> - - {/* ...custom field extensions... */} - -+ - - ; -``` - -- Adds a new generic field `TextValuePicker` to be used when writing custom field extensions that use a standard UI with custom validation. An example of doing this can be found in `packages/app/src/components/scaffolder/customScaffolderExtensions.tsx`. diff --git a/.changeset/late-frogs-yell.md b/.changeset/late-frogs-yell.md deleted file mode 100644 index b3e45785e3..0000000000 --- a/.changeset/late-frogs-yell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -Add support for serving the app with a base path other than `/`, which is enabled by including the path in `app.baseUrl`. diff --git a/.changeset/little-tools-melt.md b/.changeset/little-tools-melt.md deleted file mode 100644 index 5e55d27b7a..0000000000 --- a/.changeset/little-tools-melt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-sonarqube': minor ---- - -Use IdentityApi to provide Auth Token for SonarQubeClient Api calls diff --git a/.changeset/modern-ghosts-dress.md b/.changeset/modern-ghosts-dress.md deleted file mode 100644 index 6d3cdbfd3c..0000000000 --- a/.changeset/modern-ghosts-dress.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -'@backstage/core-app-api': patch -'@backstage/core-components': patch -'@backstage/core-plugin-api': patch -'@backstage/dev-utils': patch -'@backstage/test-utils': patch -'@backstage/test-utils-core': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-cost-insights': patch -'@backstage/plugin-explore': patch -'@backstage/plugin-git-release-manager': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-pagerduty': patch -'@backstage/plugin-rollbar': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-search': patch -'@backstage/plugin-sentry': patch -'@backstage/plugin-tech-radar': patch -'@backstage/plugin-techdocs': patch ---- - -Switched `@types/react` dependency to request `*` rather than a specific version. diff --git a/.changeset/neat-meals-arrive.md b/.changeset/neat-meals-arrive.md deleted file mode 100644 index 292d98de26..0000000000 --- a/.changeset/neat-meals-arrive.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Updated the index page redirect to work with apps served on a different base path than `/`. - -To apply this change to an existing app, remove the `/` prefix from the target route in the `Navigate` element in `packages/app/src/App.tsx`: - -```diff -- -+ -``` diff --git a/.changeset/new-ligers-drum.md b/.changeset/new-ligers-drum.md new file mode 100644 index 0000000000..0edee17b4d --- /dev/null +++ b/.changeset/new-ligers-drum.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-xcmetrics': patch +--- + +Enable browsing detailed build information such as host configuration, errors, warnings, metadata and a timeline for all targets diff --git a/.changeset/ninety-worms-kick.md b/.changeset/ninety-worms-kick.md deleted file mode 100644 index f350c6eb31..0000000000 --- a/.changeset/ninety-worms-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Adds an allow list of GitHub installations diff --git a/.changeset/pink-pugs-relate.md b/.changeset/pink-pugs-relate.md deleted file mode 100644 index e9de00901e..0000000000 --- a/.changeset/pink-pugs-relate.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Switch `EventSource` implementation with header support from a Node.js API-based one to an XHR-based one. diff --git a/.changeset/plenty-carpets-jog.md b/.changeset/plenty-carpets-jog.md deleted file mode 100644 index f4586072a7..0000000000 --- a/.changeset/plenty-carpets-jog.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Use new composable `TechDocsIndexPage` and `DefaultTechDocsHome` - -Make the following changes to your `App.tsx` to migrate existing apps: - -```diff -- } /> -+ }> -+ -+ -+ } -+ /> -``` diff --git a/.changeset/quick-fans-smash.md b/.changeset/quick-fans-smash.md deleted file mode 100644 index b394b2af23..0000000000 --- a/.changeset/quick-fans-smash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-elasticsearch': patch ---- - -Fix to allow optionally reading `auth` parameter for custom hosted ElasticSearch instances. Also remove `bearer` auth config since it's currently unsupported. diff --git a/.changeset/search-early-horses-sip.md b/.changeset/search-early-horses-sip.md deleted file mode 100644 index ce5612ae86..0000000000 --- a/.changeset/search-early-horses-sip.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-pg': patch ---- - -Add `plugin-search-backend-module-pg` providing a postgres based search engine. -See the [README of `search-backend-module-pg`](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-pg/README.md) for usage instructions. diff --git a/.changeset/serious-kiwis-wonder.md b/.changeset/serious-kiwis-wonder.md deleted file mode 100644 index 0deba49616..0000000000 --- a/.changeset/serious-kiwis-wonder.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration': patch ---- - -Remove repo restriction from GitHub credentials provider diff --git a/.changeset/shaggy-days-peel.md b/.changeset/shaggy-days-peel.md deleted file mode 100644 index b35d35f45c..0000000000 --- a/.changeset/shaggy-days-peel.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/cli': patch -'@backstage/create-app': patch ---- - -Migrate to using `webpack@5` 🎉 diff --git a/.changeset/sharp-berries-fail.md b/.changeset/sharp-berries-fail.md deleted file mode 100644 index a713b3fdee..0000000000 --- a/.changeset/sharp-berries-fail.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Add a complete prettier setup to the created project. Prettier used to only be added as a dependency to create apps, but there wasn't a complete setup included that makes it easy to run prettier. That has now changed, and the new `prettier:check` command can be used to check the formatting of the files in your created project. - -To apply this change to an existing app, a couple of changes need to be made. - -Create a `.prettierignore` file at the root of your repository with the following contents: - -``` -dist -dist-types -coverage -.vscode -``` - -Next update the root `package.json` by bumping the prettier version and adding the new `prettier:check` command: - -```diff - "scripts": { - ... -+ "prettier:check": "prettier --check .", - ... - }, - ... - "dependencies": { - ... -- "prettier": "^1.19.1" -+ "prettier": "^2.3.2" - } -``` - -Finally run `yarn prettier --write .` on your project to update the existing formatting. diff --git a/.changeset/sharp-donkeys-push.md b/.changeset/sharp-donkeys-push.md deleted file mode 100644 index fd6304118e..0000000000 --- a/.changeset/sharp-donkeys-push.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Removed the `/` prefix in the catalog `SidebarItem` element, as it is no longer needed. - -To apply this change to an existing app, remove the `/` prefix from the catalog and any other sidebar items in `packages/app/src/components/Root/Root.ts`: - -```diff -- -+ -``` diff --git a/.changeset/small-actors-sing.md b/.changeset/small-actors-sing.md deleted file mode 100644 index 40de0f363f..0000000000 --- a/.changeset/small-actors-sing.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Switched `@types/react-dom` dependency to of the app package to request `*` rather than a specific version. - -To apply this change to an existing app, change the following in `packages/app/package.json`: - -```diff -- "@types/react-dom": "^16.9.8", -+ "@types/react-dom": "*", -``` diff --git a/.changeset/small-guests-invite.md b/.changeset/small-guests-invite.md deleted file mode 100644 index 507e0b3d12..0000000000 --- a/.changeset/small-guests-invite.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Support deep linking in OpenAPI definitions. diff --git a/.changeset/spicy-crews-applaud.md b/.changeset/spicy-crews-applaud.md deleted file mode 100644 index d2707516d2..0000000000 --- a/.changeset/spicy-crews-applaud.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-react': patch ---- - -Move and rename `FavoriteEntity` component to `catalog-react` diff --git a/.changeset/techdocs-stale-breakfast-king.md b/.changeset/techdocs-stale-breakfast-king.md deleted file mode 100644 index cdbdb4d0ad..0000000000 --- a/.changeset/techdocs-stale-breakfast-king.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Stale TechDocs content (files that had previously been published but which have -since been removed) is now removed from storage at publish-time. This is now -supported by the following publishers: - -- Google GCS -- AWS S3 -- Azure Blob Storage - -You may need to apply a greater level of permissions (e.g. the ability to -delete objects in your storage provider) to any credentials/accounts used by -the TechDocs CLI or TechDocs backend in order for this change to take effect. - -For more details, see [#6132][issue-ref]. - -[issue-ref]: https://github.com/backstage/backstage/issues/6132 diff --git a/.changeset/violet-bobcats-wave.md b/.changeset/violet-bobcats-wave.md deleted file mode 100644 index 339d7e0e74..0000000000 --- a/.changeset/violet-bobcats-wave.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-components': patch ---- - -Fix for `SidebarItem` matching the active route too broadly. diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 6f28680ecb..57510f2c3f 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -15,6 +15,7 @@ Avro backrub Bigtable Billett +Bitbucket Bitrise Blackbox bool @@ -84,6 +85,8 @@ facto failover Fargate Figma +firehydrant +FireHydrant Firekube Fiverr gitbeaker diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f013d10f8..8361654038 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -109,7 +109,7 @@ jobs: run: node scripts/verify-links.js - name: prettier - run: yarn prettier:check + run: yarn prettier:check '!ADOPTERS.md' - name: lock run: yarn lock:check diff --git a/.github/workflows/prettify.yml b/.github/workflows/prettify.yml new file mode 100644 index 0000000000..4a34ff0c88 --- /dev/null +++ b/.github/workflows/prettify.yml @@ -0,0 +1,54 @@ +name: Prettier + +on: + push: + branches: + - master + +jobs: + autofix-markdown: + name: Autofix Markdown files using Prettier + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + # Fetch changes to previous commit - required for 'only_changed' in Prettier action + fetch-depth: 0 + + # 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: Run Prettier on ADOPTERS.md + uses: creyD/prettier_action@v3.1 + with: + # Modifies commit only if prettier autofixed the ADOPTERS.md + prettier_options: --config docs/prettier.config.js --write ADOPTERS.md + only_changed: true + commit_message: 'Autofix ADOPTERS.md using Prettier' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/app-config.yaml b/app-config.yaml index f2df97b7b2..c1d0121dff 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -152,6 +152,11 @@ integrations: - host: bitbucket.org username: ${BITBUCKET_USERNAME} appPassword: ${BITBUCKET_APP_PASSWORD} + ### Example for how to add your bitbucket server instance using the API: + # - host: server.bitbucket.com + # apiBaseUrl: server.bitbucket.com + # username: ${BITBUCKET_SERVER_USERNAME} + # appPassword: ${BITBUCKET_SERVER_APP_PASSWORD} azure: - host: dev.azure.com token: ${AZURE_TOKEN} diff --git a/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild b/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild index 1e0134017f..f0fa4a034b 100644 --- a/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild +++ b/contrib/docker/frontend-with-nginx/Dockerfile.hostbuild @@ -8,7 +8,6 @@ # simply copies in the build output into the image. If you want to also perform # the build itself inside docker, use Dockerfile.build in this folder instead. - # USAGE: # # - Copy this file and the "docker" folder from this directory to your project @@ -23,7 +22,7 @@ # # yarn install # yarn tsc -# yarn build --config --config ... +# yarn workspace app build --config --config ... # docker build -t backstage-frontend -f Dockerfile.hostbuild . @@ -38,4 +37,3 @@ COPY docker/default.conf.template /etc/nginx/templates/default.conf.template COPY docker/inject-config.sh /docker-entrypoint.d/40-inject-config.sh ENV PORT 80 - diff --git a/docs/auth/identity-resolver.md b/docs/auth/identity-resolver.md index 814ff63729..7ec6ec7117 100644 --- a/docs/auth/identity-resolver.md +++ b/docs/auth/identity-resolver.md @@ -111,6 +111,48 @@ export default async function createPlugin({ ... ``` +## Resolving membership through the catalog + +If you want to provide additional claims through Sign-In resolvers but still +have the software catalog handle group (and transitive group) membership, you +can do this using the `CatalogIdentityClient` provided as context to Sign-In +resolvers: + +```ts +export default async function createPlugin({ + ... +}: PluginEnvironment): Promise { + return await createRouter({ + ... + providerFactories: { + google: createGoogleProvider({ + signIn: { + resolver: async ({ profile: { email } }, ctx) => { + const [sub] = email?.split('@') ?? ''; + // Fetch from an external system that returns entity claims like: + // ['user:default/breanna.davison', ...] + const ent = await externalSystemClient.getUsernames(email); + + // Resolve group membership from the Backstage catalog + const fullEnt = await ctx.catalogIdentityClient.resolveCatalogMembership({ + entityRefs: [sub].concat(ent), + logger: ctx.logger, + }); + const token = await ctx.tokenIssuer.issueToken({ + claims: { sub, ent: fullEnt }, + }); + return { sub, token }; + }, + }, + }), + ... +``` + +The `resolveCatalogMembership` method will retrieve the referenced entities from +the catalog, if possible, and check for +[memberOf](../features/software-catalog/well-known-relations.md#memberof-and-hasmember) +relations to add additional entity claims. + ## AuthHandler Similar to a custom sign-in resolver, you can also write a custom auth handler diff --git a/microsite/static/img/xcmetrics-icon.png b/microsite/static/img/xcmetrics-icon.png index 225ee6e147..09f3ea1e68 100644 Binary files a/microsite/static/img/xcmetrics-icon.png and b/microsite/static/img/xcmetrics-icon.png differ diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 14927117ab..5ce4f341e1 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,33 @@ # example-app +## 0.2.41 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cost-insights@0.11.4 + - @backstage/plugin-github-actions@0.4.16 + - @backstage/plugin-jenkins@0.5.3 + - @backstage/plugin-kubernetes@0.4.11 + - @backstage/plugin-pagerduty@0.3.11 + - @backstage/plugin-scaffolder@0.11.0 + - @backstage/plugin-sentry@0.3.18 + - @backstage/plugin-techdocs@0.10.4 + - @backstage/core-app-api@0.1.8 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-api-docs@0.6.6 + - @backstage/plugin-catalog@0.6.11 + - @backstage/plugin-catalog-import@0.5.17 + - @backstage/plugin-catalog-react@0.4.2 + - @backstage/plugin-explore@0.3.13 + - @backstage/plugin-lighthouse@0.2.23 + - @backstage/plugin-rollbar@0.3.12 + - @backstage/plugin-search@0.4.7 + - @backstage/plugin-tech-radar@0.4.5 + - @backstage/cli@0.7.8 + - @backstage/integration-react@0.1.7 + ## 0.2.40 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index df69e0835a..dcf14c88c1 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,42 +1,42 @@ { "name": "example-app", - "version": "0.2.40", + "version": "0.2.41", "private": true, "bundled": true, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/integration-react": "^0.1.6", - "@backstage/plugin-api-docs": "^0.6.5", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/integration-react": "^0.1.7", + "@backstage/plugin-api-docs": "^0.6.6", "@backstage/plugin-badges": "^0.2.7", - "@backstage/plugin-catalog": "^0.6.10", - "@backstage/plugin-catalog-import": "^0.5.16", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/plugin-catalog": "^0.6.11", + "@backstage/plugin-catalog-import": "^0.5.17", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/plugin-circleci": "^0.2.21", "@backstage/plugin-cloudbuild": "^0.2.21", "@backstage/plugin-code-coverage": "^0.1.9", - "@backstage/plugin-cost-insights": "^0.11.3", - "@backstage/plugin-explore": "^0.3.12", + "@backstage/plugin-cost-insights": "^0.11.4", + "@backstage/plugin-explore": "^0.3.13", "@backstage/plugin-gcp-projects": "^0.3.2", - "@backstage/plugin-github-actions": "^0.4.15", + "@backstage/plugin-github-actions": "^0.4.16", "@backstage/plugin-graphiql": "^0.2.14", - "@backstage/plugin-jenkins": "^0.5.2", + "@backstage/plugin-jenkins": "^0.5.3", "@backstage/plugin-kafka": "^0.2.13", - "@backstage/plugin-kubernetes": "^0.4.10", - "@backstage/plugin-lighthouse": "^0.2.22", + "@backstage/plugin-kubernetes": "^0.4.11", + "@backstage/plugin-lighthouse": "^0.2.23", "@backstage/plugin-newrelic": "^0.3.2", "@backstage/plugin-org": "^0.3.19", - "@backstage/plugin-pagerduty": "0.3.10", - "@backstage/plugin-rollbar": "^0.3.11", - "@backstage/plugin-scaffolder": "^0.10.3", - "@backstage/plugin-search": "^0.4.6", - "@backstage/plugin-sentry": "^0.3.17", + "@backstage/plugin-pagerduty": "0.3.11", + "@backstage/plugin-rollbar": "^0.3.12", + "@backstage/plugin-scaffolder": "^0.11.0", + "@backstage/plugin-search": "^0.4.7", + "@backstage/plugin-sentry": "^0.3.18", "@backstage/plugin-shortcuts": "^0.1.6", - "@backstage/plugin-tech-radar": "^0.4.4", - "@backstage/plugin-techdocs": "^0.10.3", + "@backstage/plugin-tech-radar": "^0.4.5", + "@backstage/plugin-techdocs": "^0.10.4", "@backstage/plugin-todo": "^0.1.7", "@backstage/plugin-user-settings": "^0.3.3", "@backstage/search-common": "^0.1.3", @@ -60,7 +60,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/test-utils": "^0.1.16", + "@backstage/test-utils": "^0.1.17", "@rjsf/core": "^3.0.0", "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index f6f61133ea..297f07b0b4 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-common +## 0.8.9 + +### Patch Changes + +- f7ce7c565: Use a more informative error message when URL reading isn't allowed due to no reader matching the target URL. +- ce1958021: Pass on credentials to the integrations package, so that it can properly pick the API route when using GitHub apps based auth +- Updated dependencies + - @backstage/integration@0.6.0 + ## 0.8.8 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 10602fa2fa..4c7aacdf96 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.8.8", + "version": "0.8.9", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -33,7 +33,7 @@ "@backstage/config": "^0.1.6", "@backstage/config-loader": "^0.6.6", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.9", + "@backstage/integration": "^0.6.0", "@google-cloud/storage": "^5.8.0", "@octokit/rest": "^18.5.3", "@types/cors": "^2.8.6", @@ -76,8 +76,8 @@ } }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/test-utils": "^0.1.15", + "@backstage/cli": "^0.7.8", + "@backstage/test-utils": "^0.1.17", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", "@types/concat-stream": "^1.6.0", diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 930c544287..40d36fb2a0 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -91,15 +91,20 @@ export class GithubUrlReader implements UrlReader { url: string, options?: ReadUrlOptions, ): Promise { - const ghUrl = getGitHubFileFetchUrl(url, this.integration.config); - const { headers } = await this.deps.credentialsProvider.getCredentials({ + const credentials = await this.deps.credentialsProvider.getCredentials({ url, }); + const ghUrl = getGitHubFileFetchUrl( + url, + this.integration.config, + credentials, + ); + let response: Response; try { - response = await fetch(ghUrl.toString(), { + response = await fetch(ghUrl, { headers: { - ...headers, + ...credentials?.headers, ...(options?.etag && { 'If-None-Match': options.etag }), Accept: 'application/vnd.github.v3.raw', }, diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 2faa5afbc3..5671b41fb3 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/backend-test-utils +## 0.1.5 + +### Patch Changes + +- 524045758: Make sure that the unique databases names generated by `TestDatabases.create()` + don't collide. +- Updated dependencies + - @backstage/backend-common@0.8.9 + - @backstage/cli@0.7.8 + ## 0.1.4 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 9ec11a58b7..f1cfffeb37 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-test-utils", "description": "Test helpers library for Backstage backends", - "version": "0.1.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.5", - "@backstage/cli": "^0.7.1", + "@backstage/backend-common": "^0.8.9", + "@backstage/cli": "^0.7.8", "@backstage/config": "^0.1.5", "knex": "^0.95.1", "mysql2": "^2.2.5", @@ -41,7 +41,7 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.1", + "@backstage/cli": "^0.7.8", "jest": "^26.0.1" }, "files": [ diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index a3e08db6b3..39eabe46b0 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,24 @@ # example-backend +## 0.2.41 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.3.20 + - @backstage/integration@0.6.0 + - @backstage/plugin-scaffolder-backend@0.15.0 + - @backstage/backend-common@0.8.9 + - @backstage/plugin-kubernetes-backend@0.3.14 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.2 + - @backstage/plugin-search-backend-module-pg@0.1.1 + - @backstage/plugin-catalog-backend@0.13.2 + - @backstage/plugin-code-coverage-backend@0.1.9 + - @backstage/plugin-scaffolder-backend-module-rails@0.1.4 + - @backstage/plugin-techdocs-backend@0.9.2 + - @backstage/plugin-todo-backend@0.1.9 + - example-app@0.2.41 + ## 0.2.38 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 33221bde01..b2877dedf5 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.38", + "version": "0.2.41", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -27,35 +27,35 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.8.7", + "@backstage/backend-common": "^0.8.9", "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", - "@backstage/integration": "^0.5.8", + "@backstage/integration": "^0.6.0", "@backstage/plugin-app-backend": "^0.3.15", - "@backstage/plugin-auth-backend": "^0.3.18", + "@backstage/plugin-auth-backend": "^0.3.20", "@backstage/plugin-badges-backend": "^0.1.8", - "@backstage/plugin-catalog-backend": "^0.13.0", - "@backstage/plugin-code-coverage-backend": "^0.1.8", + "@backstage/plugin-catalog-backend": "^0.13.2", + "@backstage/plugin-code-coverage-backend": "^0.1.9", "@backstage/plugin-graphql-backend": "^0.1.8", "@backstage/plugin-jenkins-backend": "^0.1.2", - "@backstage/plugin-kubernetes-backend": "^0.3.11", + "@backstage/plugin-kubernetes-backend": "^0.3.14", "@backstage/plugin-kafka-backend": "^0.2.8", "@backstage/plugin-proxy-backend": "^0.2.9", "@backstage/plugin-rollbar-backend": "^0.1.13", - "@backstage/plugin-scaffolder-backend": "^0.14.1", - "@backstage/plugin-scaffolder-backend-module-rails": "^0.1.3", + "@backstage/plugin-scaffolder-backend": "^0.15.0", + "@backstage/plugin-scaffolder-backend-module-rails": "^0.1.4", "@backstage/plugin-search-backend": "^0.2.3", "@backstage/plugin-search-backend-node": "^0.4.0", - "@backstage/plugin-search-backend-module-elasticsearch": "^0.0.1", - "@backstage/plugin-search-backend-module-pg": "^0.1.0", - "@backstage/plugin-techdocs-backend": "^0.9.0", - "@backstage/plugin-todo-backend": "^0.1.8", + "@backstage/plugin-search-backend-module-elasticsearch": "^0.0.2", + "@backstage/plugin-search-backend-module-pg": "^0.1.1", + "@backstage/plugin-techdocs-backend": "^0.9.2", + "@backstage/plugin-todo-backend": "^0.1.9", "@gitbeaker/node": "^30.2.0", "@octokit/rest": "^18.5.3", "azure-devops-node-api": "^10.2.2", "dockerode": "^3.2.1", - "example-app": "^0.2.37", + "example-app": "^0.2.41", "express": "^4.17.1", "express-promise-router": "^4.1.0", "knex": "^0.95.1", @@ -65,7 +65,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.5", + "@backstage/cli": "^0.7.8", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index dc4512adee..848a106db0 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/cli +## 0.7.8 + +### Patch Changes + +- c4ef9181a: Migrate to using `webpack@5` 🎉 + ## 0.7.7 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index c0e6d987f1..63b8db373e 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.7.7", + "version": "0.7.8", "private": false, "publishConfig": { "access": "public" @@ -118,13 +118,13 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.8.8", + "@backstage/backend-common": "^0.8.9", "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@backstage/theme": "^0.2.9", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index b709350a73..192e6810bc 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/codemods +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@0.1.8 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + ## 0.1.8 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 38602ca03d..8f87631897 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.8", + "version": "0.1.9", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index dbb26fe2a0..1ec944ac4a 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/core-app-api +## 0.1.8 + +### Patch Changes + +- 362657623: Add support for serving the app with a base path other than `/`, which is enabled by including the path in `app.baseUrl`. +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + ## 0.1.7 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 204550323c..2b66a991f1 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.7", + "version": "0.1.8", "private": false, "publishConfig": { "access": "public", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.3.0", + "@backstage/core-components": "^0.3.1", "@backstage/config": "^0.1.6", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -44,9 +44,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/test-utils": "^0.1.16", - "@backstage/test-utils-core": "^0.1.1", + "@backstage/cli": "^0.7.8", + "@backstage/test-utils": "^0.1.17", + "@backstage/test-utils-core": "^0.1.2", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index cb93e38ba5..0241234574 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/core-components +## 0.3.1 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- 55a5dbd54: Fix for `SidebarItem` matching the active route too broadly. +- Updated dependencies + - @backstage/core-plugin-api@0.1.6 + ## 0.3.0 ### Minor Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 5ae86a0030..69f2019943 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.3.0", + "version": "0.3.1", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.6", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.9", "@material-table/core": "^3.1.0", @@ -70,9 +70,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/core-app-api": "^0.1.7", - "@backstage/cli": "^0.7.7", - "@backstage/test-utils": "^0.1.16", + "@backstage/core-app-api": "^0.1.8", + "@backstage/cli": "^0.7.8", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 5ea46a334e..2a76ab243f 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core-plugin-api +## 0.1.6 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. + ## 0.1.5 ### Patch Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index a0057c1b0c..d482795c90 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.5", + "version": "0.1.6", "private": false, "publishConfig": { "access": "public", @@ -41,10 +41,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/test-utils": "^0.1.16", - "@backstage/test-utils-core": "^0.1.1", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/test-utils": "^0.1.17", + "@backstage/test-utils-core": "^0.1.2", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 318c508211..baee775947 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,83 @@ # @backstage/create-app +## 0.3.35 + +### Patch Changes + +- 362ea5a72: Updated the index page redirect to work with apps served on a different base path than `/`. + + To apply this change to an existing app, remove the `/` prefix from the target route in the `Navigate` element in `packages/app/src/App.tsx`: + + ```diff + - + + + ``` + +- 80582cbec: Use new composable `TechDocsIndexPage` and `DefaultTechDocsHome` + + Make the following changes to your `App.tsx` to migrate existing apps: + + ```diff + - } /> + + }> + + + + + + } + + /> + ``` + +- c4ef9181a: Migrate to using `webpack@5` 🎉 +- 56c773909: Add a complete prettier setup to the created project. Prettier used to only be added as a dependency to create apps, but there wasn't a complete setup included that makes it easy to run prettier. That has now changed, and the new `prettier:check` command can be used to check the formatting of the files in your created project. + + To apply this change to an existing app, a couple of changes need to be made. + + Create a `.prettierignore` file at the root of your repository with the following contents: + + ``` + dist + dist-types + coverage + .vscode + ``` + + Next update the root `package.json` by bumping the prettier version and adding the new `prettier:check` command: + + ```diff + "scripts": { + ... + + "prettier:check": "prettier --check .", + ... + }, + ... + "dependencies": { + ... + - "prettier": "^1.19.1" + + "prettier": "^2.3.2" + } + ``` + + Finally run `yarn prettier --write .` on your project to update the existing formatting. + +- 9f8f8dd6b: Removed the `/` prefix in the catalog `SidebarItem` element, as it is no longer needed. + + To apply this change to an existing app, remove the `/` prefix from the catalog and any other sidebar items in `packages/app/src/components/Root/Root.ts`: + + ```diff + - + + + ``` + +- 56c773909: Switched `@types/react-dom` dependency to of the app package to request `*` rather than a specific version. + + To apply this change to an existing app, change the following in `packages/app/package.json`: + + ```diff + - "@types/react-dom": "^16.9.8", + + "@types/react-dom": "*", + ``` + ## 0.3.34 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 130c69603a..3be83ec6be 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.3.34", + "version": "0.3.35", "private": false, "publishConfig": { "access": "public" diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 5e570164a0..86a6e44a75 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/dev-utils +## 0.2.6 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-app-api@0.1.8 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/test-utils@0.1.17 + - @backstage/plugin-catalog-react@0.4.2 + - @backstage/integration-react@0.1.7 + ## 0.2.5 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index a7efd5c778..66f4d7bba6 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.2.5", + "version": "0.2.6", "private": false, "publishConfig": { "access": "public", @@ -29,13 +29,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-app-api": "^0.1.7", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-app-api": "^0.1.8", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/catalog-model": "^0.9.0", - "@backstage/integration-react": "^0.1.6", - "@backstage/plugin-catalog-react": "^0.4.1", - "@backstage/test-utils": "^0.1.16", + "@backstage/integration-react": "^0.1.7", + "@backstage/plugin-catalog-react": "^0.4.2", + "@backstage/test-utils": "^0.1.17", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -50,7 +50,7 @@ "react-router-dom": "6.0.0-beta.0" }, "devDependencies": { - "@backstage/cli": "^0.7.7", + "@backstage/cli": "^0.7.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index e5877d9079..281fed0211 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/integration-react +## 0.1.7 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + ## 0.1.6 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 6bf8cba4a0..3217881046 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "0.1.6", + "version": "0.1.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,9 +21,9 @@ }, "dependencies": { "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/integration": "^0.5.9", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/integration": "^0.6.0", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -33,9 +33,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 5b13f89d14..54b2d374cc 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/integration +## 0.6.0 + +### Minor Changes + +- ce1958021: `getGitHubFileFetchUrl` and `getGitHubRequestOptions` now require a `credentials` argument. This is needed to address an issue where the raw route was chosen by the `UrlReader` when using GitHub Apps based auth. + + Deprecated the `getGitHubRequestOptions` function, which is no longer used internally. + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- 68af4d556: Adds an allow list of GitHub installations +- 5fd31c2f4: Remove repo restriction from GitHub credentials provider + ## 0.5.9 ### Patch Changes diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 160a0a12ca..52663f55fa 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -165,20 +165,23 @@ export function getBitbucketRequestOptions( // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// Warning: (ae-forgotten-export) The symbol "GithubCredentials" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "getGitHubFileFetchUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public export function getGitHubFileFetchUrl( url: string, config: GitHubIntegrationConfig, + credentials: GithubCredentials, ): string; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // Warning: (ae-missing-release-tag) "getGitHubRequestOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public +// @public @deprecated export function getGitHubRequestOptions( config: GitHubIntegrationConfig, + credentials: GithubCredentials, ): RequestInit; // Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen @@ -228,7 +231,6 @@ export class GithubCredentialsProvider { // Warning: (tsdoc-escape-right-brace) The "}" character should be escaped using a backslash to avoid confusion with a TSDoc inline tag // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" // Warning: (tsdoc-malformed-inline-tag) Expecting a TSDoc tag starting with "{@" - // Warning: (ae-forgotten-export) The symbol "GithubCredentials" needs to be exported by the entry point index.d.ts getCredentials(opts: { url: string }): Promise; } diff --git a/packages/integration/package.json b/packages/integration/package.json index 9aa4df761a..a17572c47e 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration", - "version": "0.5.9", + "version": "0.6.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,12 +34,12 @@ "git-url-parse": "~11.4.4", "@octokit/rest": "^18.5.3", "@octokit/auth-app": "^3.4.0", - "luxon": "^1.25.0" + "luxon": "^2.0.2" }, "devDependencies": { - "@backstage/cli": "^0.7.7", + "@backstage/cli": "^0.7.8", "@backstage/config-loader": "^0.6.6", - "@backstage/test-utils": "^0.1.13", + "@backstage/test-utils": "^0.1.17", "@types/jest": "^26.0.7", "@types/luxon": "^1.25.0", "msw": "^0.29.0" diff --git a/packages/integration/src/github/GithubCredentialsProvider.ts b/packages/integration/src/github/GithubCredentialsProvider.ts index a3938b7ba3..c7ba6408d9 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.ts +++ b/packages/integration/src/github/GithubCredentialsProvider.ts @@ -250,11 +250,7 @@ export class GithubCredentialsProvider { } return { - headers: token - ? { - Authorization: `Bearer ${token}`, - } - : undefined, + headers: token ? { Authorization: `Bearer ${token}` } : undefined, token, type, }; diff --git a/packages/integration/src/github/core.test.ts b/packages/integration/src/github/core.test.ts index 2dab4ab4d5..53733b40d0 100644 --- a/packages/integration/src/github/core.test.ts +++ b/packages/integration/src/github/core.test.ts @@ -16,8 +16,25 @@ import { GitHubIntegrationConfig } from './config'; import { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; +import { GithubCredentials } from './GithubCredentialsProvider'; describe('github core', () => { + const appCredentials: GithubCredentials = { + type: 'app', + token: 'A', + headers: {}, + }; + + const tokenCredentials: GithubCredentials = { + type: 'token', + token: 'A', + headers: {}, + }; + + const noCredentials: GithubCredentials = { + type: 'token', + }; + describe('getGitHubRequestOptions', () => { it('inserts a token when needed', () => { const withToken: GitHubIntegrationConfig = { @@ -30,10 +47,12 @@ describe('github core', () => { rawBaseUrl: '', }; expect( - (getGitHubRequestOptions(withToken).headers as any).Authorization, + (getGitHubRequestOptions(withToken, appCredentials).headers as any) + .Authorization, ).toEqual('token A'); expect( - (getGitHubRequestOptions(withoutToken).headers as any).Authorization, + (getGitHubRequestOptions(withoutToken, noCredentials).headers as any) + .Authorization, ).toBeUndefined(); }); }); @@ -41,7 +60,7 @@ describe('github core', () => { describe('getGitHubFileFetchUrl', () => { it('rejects targets that do not look like URLs', () => { const config: GitHubIntegrationConfig = { host: '', apiBaseUrl: '' }; - expect(() => getGitHubFileFetchUrl('a/b', config)).toThrow( + expect(() => getGitHubFileFetchUrl('a/b', config, noCredentials)).toThrow( /Incorrect URL: a\/b/, ); }); @@ -55,14 +74,16 @@ describe('github core', () => { getGitHubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, + appCredentials, ), ).toEqual( 'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname', ); expect( getGitHubFileFetchUrl( - 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', + 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, + tokenCredentials, ), ).toEqual( 'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname', @@ -78,6 +99,16 @@ describe('github core', () => { getGitHubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, + appCredentials, + ), + ).toEqual( + 'https://ghe.mycompany.net/api/v3/repos/a/b/contents/path/to/c.yaml?ref=branchname', + ); + expect( + getGitHubFileFetchUrl( + 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', + config, + tokenCredentials, ), ).toEqual( 'https://ghe.mycompany.net/api/v3/repos/a/b/contents/path/to/c.yaml?ref=branchname', @@ -93,6 +124,7 @@ describe('github core', () => { getGitHubFileFetchUrl( 'https://github.com/a/b/tree/branchname/path/to/c.yaml', config, + tokenCredentials, ), ).toEqual( 'https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname', @@ -108,6 +140,7 @@ describe('github core', () => { getGitHubFileFetchUrl( 'https://ghe.mycompany.net/a/b/tree/branchname/path/to/c.yaml', config, + tokenCredentials, ), ).toEqual( 'https://ghe.mycompany.net/api/v3/repos/a/b/contents/path/to/c.yaml?ref=branchname', @@ -123,6 +156,7 @@ describe('github core', () => { getGitHubFileFetchUrl( 'https://github.com/a/b/blob/branchname/path/to/c.yaml', config, + tokenCredentials, ), ).toEqual( 'https://raw.githubusercontent.com/a/b/branchname/path/to/c.yaml', @@ -138,6 +172,7 @@ describe('github core', () => { getGitHubFileFetchUrl( 'https://ghe.mycompany.net/a/b/blob/branchname/path/to/c.yaml', config, + tokenCredentials, ), ).toEqual('https://ghe.mycompany.net/raw/a/b/branchname/path/to/c.yaml'); }); diff --git a/packages/integration/src/github/core.ts b/packages/integration/src/github/core.ts index 4a069bcba4..49695ec5ac 100644 --- a/packages/integration/src/github/core.ts +++ b/packages/integration/src/github/core.ts @@ -16,6 +16,7 @@ import parseGitUrl from 'git-url-parse'; import { GitHubIntegrationConfig } from './config'; +import { GithubCredentials } from './GithubCredentialsProvider'; /** * Given a URL pointing to a file on a provider, returns a URL that is suitable @@ -32,6 +33,7 @@ import { GitHubIntegrationConfig } from './config'; export function getGitHubFileFetchUrl( url: string, config: GitHubIntegrationConfig, + credentials: GithubCredentials, ): string { try { const { owner, name, ref, filepathtype, filepath } = parseGitUrl(url); @@ -49,7 +51,7 @@ export function getGitHubFileFetchUrl( } const pathWithoutSlash = filepath.replace(/^\//, ''); - if (chooseEndpoint(config) === 'api') { + if (chooseEndpoint(config, credentials) === 'api') { return `${config.apiBaseUrl}/repos/${owner}/${name}/contents/${pathWithoutSlash}?ref=${ref}`; } return `${config.rawBaseUrl}/${owner}/${name}/${ref}/${pathWithoutSlash}`; @@ -61,25 +63,31 @@ export function getGitHubFileFetchUrl( /** * Gets the request options necessary to make requests to a given provider. * + * @deprecated This function is no longer used internally * @param config The relevant provider config */ export function getGitHubRequestOptions( config: GitHubIntegrationConfig, + credentials: GithubCredentials, ): RequestInit { const headers: HeadersInit = {}; - if (chooseEndpoint(config) === 'api') { + if (chooseEndpoint(config, credentials) === 'api') { headers.Accept = 'application/vnd.github.v3.raw'; } - if (config.token) { - headers.Authorization = `token ${config.token}`; + + if (credentials.token) { + headers.Authorization = `token ${credentials.token}`; } return { headers }; } -export function chooseEndpoint(config: GitHubIntegrationConfig): 'api' | 'raw' { - if (config.apiBaseUrl && (config.token || !config.rawBaseUrl)) { +export function chooseEndpoint( + config: GitHubIntegrationConfig, + credentials: GithubCredentials, +): 'api' | 'raw' { + if (config.apiBaseUrl && (credentials.token || !config.rawBaseUrl)) { return 'api'; } return 'raw'; diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 5f115daabf..2b925f453f 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/techdocs-common +## 0.8.1 + +### Patch Changes + +- bc405be6e: Stale TechDocs content (files that had previously been published but which have + since been removed) is now removed from storage at publish-time. This is now + supported by the following publishers: + + - Google GCS + - AWS S3 + - Azure Blob Storage + + You may need to apply a greater level of permissions (e.g. the ability to + delete objects in your storage provider) to any credentials/accounts used by + the TechDocs CLI or TechDocs backend in order for this change to take effect. + + For more details, see [#6132][issue-ref]. + + [issue-ref]: https://github.com/backstage/backstage/issues/6132 + +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/backend-common@0.8.9 + ## 0.8.0 ### Minor Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 65d16bf0f7..8726c4ac37 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.8.0", + "version": "0.8.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -38,11 +38,11 @@ "dependencies": { "@azure/identity": "^1.5.0", "@azure/storage-blob": "^12.5.0", - "@backstage/backend-common": "^0.8.8", + "@backstage/backend-common": "^0.8.9", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.9", + "@backstage/integration": "^0.6.0", "@google-cloud/storage": "^5.6.0", "@types/express": "^4.17.6", "aws-sdk": "^2.840.0", @@ -58,7 +58,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.7", + "@backstage/cli": "^0.7.8", "@types/fs-extra": "^9.0.5", "@types/js-yaml": "^4.0.0", "@types/mime-types": "^2.1.0", diff --git a/packages/test-utils-core/CHANGELOG.md b/packages/test-utils-core/CHANGELOG.md new file mode 100644 index 0000000000..a84ab931f3 --- /dev/null +++ b/packages/test-utils-core/CHANGELOG.md @@ -0,0 +1,7 @@ +# @backstage/test-utils-core + +## 0.1.2 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index 84475d3c5d..b88184f121 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils-core", "description": "Utilities to test Backstage core", - "version": "0.1.1", + "version": "0.1.2", "private": false, "publishConfig": { "access": "public", diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index a16b2a6259..08594ae824 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/test-utils +## 0.1.17 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-app-api@0.1.8 + - @backstage/core-plugin-api@0.1.6 + - @backstage/test-utils-core@0.1.2 + ## 0.1.16 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 80fb029f34..0134382b7f 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.16", + "version": "0.1.17", "private": false, "publishConfig": { "access": "public", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-app-api": "^0.1.6", - "@backstage/core-plugin-api": "^0.1.4", - "@backstage/test-utils-core": "^0.1.1", + "@backstage/core-app-api": "^0.1.8", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/test-utils-core": "^0.1.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@testing-library/jest-dom": "^5.10.1", @@ -46,7 +46,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.6", + "@backstage/cli": "^0.7.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index a3e06a1a65..728667dabd 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-api-docs +## 0.6.6 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- 0383314c9: Support deep linking in OpenAPI definitions. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog@0.6.11 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.6.5 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 18c7952fb2..f3bacf0a13 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.6.5", + "version": "0.6.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "dependencies": { "@asyncapi/react-component": "^0.23.0", "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog": "^0.6.10", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog": "^0.6.11", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.12.2", @@ -52,10 +52,10 @@ "swagger-ui-react": "^3.37.2" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 9183eac0ad..23a0445929 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-auth-backend +## 0.3.20 + +### Patch Changes + +- 29f7cfffb: Added `resolveCatalogMembership` utility to query the catalog for additional authentication claims within sign-in resolvers. +- 8bedb75ae: Update Luxon dependency to 2.x +- bfe0ff93f: Add Sign In and Handler resolver for Okta provider +- Updated dependencies + - @backstage/backend-common@0.8.9 + - @backstage/test-utils@0.1.17 + ## 0.3.19 ### Patch Changes diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index 9fb9e29a4a..af4375f5d7 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -98,6 +98,13 @@ export const createMicrosoftProvider: ( options?: MicrosoftProviderOptions | undefined, ) => AuthProviderFactory; +// Warning: (ae-missing-release-tag) "createOktaProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const createOktaProvider: ( + _options?: OktaProviderOptions | undefined, +) => AuthProviderFactory; + // Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -303,6 +310,21 @@ export type OAuthState = { env: string; }; +// Warning: (ae-missing-release-tag) "oktaEmailSignInResolver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const oktaEmailSignInResolver: SignInResolver; + +// Warning: (ae-missing-release-tag) "OktaProviderOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type OktaProviderOptions = { + authHandler?: AuthHandler; + signIn?: { + resolver?: SignInResolver; + }; +}; + // Warning: (ae-missing-release-tag) "postMessageResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 4ea799c37c..4b1e2f415a 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.3.19", + "version": "0.3.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,12 +29,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.7", + "@backstage/backend-common": "^0.8.9", "@backstage/catalog-client": "^0.3.18", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/test-utils": "^0.1.16", + "@backstage/test-utils": "^0.1.17", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", "compression": "^1.7.4", @@ -50,7 +50,7 @@ "jose": "^1.27.1", "jwt-decode": "^3.1.0", "knex": "^0.95.1", - "luxon": "^1.25.0", + "luxon": "^2.0.2", "minimatch": "^3.0.3", "morgan": "^1.10.0", "node-cache": "^5.1.2", @@ -69,7 +69,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.6", + "@backstage/cli": "^0.7.8", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts index 329db316ab..6ffcfd6f46 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts @@ -15,7 +15,11 @@ */ import { CatalogApi } from '@backstage/catalog-client'; -import { UserEntity } from '@backstage/catalog-model'; +import { + RELATION_MEMBER_OF, + UserEntity, + UserEntityV1alpha1, +} from '@backstage/catalog-model'; import { TokenIssuer } from '../../identity'; import { CatalogIdentityClient } from './CatalogIdentityClient'; @@ -37,12 +41,12 @@ describe('CatalogIdentityClient', () => { afterEach(() => jest.resetAllMocks()); - it('passes through the correct search params', async () => { + it('findUser passes through the correct search params', async () => { catalogApi.getEntities.mockResolvedValueOnce({ items: [{} as UserEntity] }); tokenIssuer.issueToken.mockResolvedValue('my-token'); const client = new CatalogIdentityClient({ - catalogApi: catalogApi, - tokenIssuer: tokenIssuer, + catalogApi, + tokenIssuer, }); await client.findUser({ annotations: { key: 'value' } }); @@ -62,4 +66,88 @@ describe('CatalogIdentityClient', () => { }, }); }); + + it('resolveCatalogMembership resolves membership', async () => { + const mockUsers: Array = [ + { + apiVersion: 'backstage.io/v1beta1', + kind: 'User', + metadata: { + name: 'inigom', + }, + spec: { + memberOf: ['team-a'], + }, + relations: [ + { + type: RELATION_MEMBER_OF, + target: { + kind: 'Group', + namespace: 'default', + name: 'team-a', + }, + }, + ], + }, + { + apiVersion: 'backstage.io/v1beta1', + kind: 'User', + metadata: { + name: 'mpatinkin', + namespace: 'reality', + }, + spec: { + memberOf: ['screen-actors-guild'], + }, + relations: [ + { + type: RELATION_MEMBER_OF, + target: { + kind: 'Group', + namespace: 'reality', + name: 'screen-actors-guild', + }, + }, + ], + }, + ]; + catalogApi.getEntities.mockResolvedValueOnce({ items: mockUsers }); + + const client = new CatalogIdentityClient({ + catalogApi, + tokenIssuer, + }); + + const claims = await client.resolveCatalogMembership({ + entityRefs: ['inigom', 'User:default/imontoya', 'User:reality/mpatinkin'], + }); + + expect(catalogApi.getEntities).toHaveBeenCalledWith({ + filter: [ + { + kind: 'user', + 'metadata.namespace': 'default', + 'metadata.name': 'inigom', + }, + { + kind: 'user', + 'metadata.namespace': 'default', + 'metadata.name': 'imontoya', + }, + { + kind: 'user', + 'metadata.namespace': 'reality', + 'metadata.name': 'mpatinkin', + }, + ], + }); + + expect(claims).toMatchObject([ + 'user:default/inigom', + 'user:default/imontoya', + 'user:reality/mpatinkin', + 'group:default/team-a', + 'group:reality/screen-actors-guild', + ]); + }); }); diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts index 07a7190503..02fe9818cb 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts @@ -14,15 +14,27 @@ * limitations under the License. */ +import { Logger } from 'winston'; import { ConflictError, NotFoundError } from '@backstage/errors'; import { CatalogApi } from '@backstage/catalog-client'; -import { UserEntity } from '@backstage/catalog-model'; +import { + EntityName, + parseEntityRef, + RELATION_MEMBER_OF, + stringifyEntityRef, + UserEntity, +} from '@backstage/catalog-model'; import { TokenIssuer } from '../../identity'; type UserQuery = { annotations: Record; }; +type MemberClaimQuery = { + entityRefs: string[]; + logger?: Logger; +}; + /** * A catalog client tailored for reading out identity data from the catalog. */ @@ -64,4 +76,62 @@ export class CatalogIdentityClient { return items[0] as UserEntity; } + + /** + * Resolve additional entity claims from the catalog, using the passed-in entity names. Designed + * to be used within a `signInResolver` where additional entity claims might be provided, but + * group membership and transient group membership lean on imported catalog relations. + * + * Returns a superset of the entity names that can be passed directly to `issueToken` as `ent`. + */ + async resolveCatalogMembership({ + entityRefs, + logger, + }: MemberClaimQuery): Promise { + const resolvedEntityRefs = entityRefs + .map((ref: string) => { + try { + const parsedRef = parseEntityRef(ref.toLocaleLowerCase('en-US'), { + defaultKind: 'user', + defaultNamespace: 'default', + }); + return parsedRef; + } catch { + logger?.warn(`Failed to parse entityRef from ${ref}, ignoring`); + return null; + } + }) + .filter((ref): ref is EntityName => ref !== null); + + const filter = resolvedEntityRefs.map(ref => ({ + kind: ref.kind, + 'metadata.namespace': ref.namespace, + 'metadata.name': ref.name, + })); + const entities = await this.catalogApi + .getEntities({ filter }) + .then(r => r.items); + + if (entityRefs.length !== entities.length) { + const foundEntityNames = entities.map(stringifyEntityRef); + const missingEntityNames = resolvedEntityRefs + .map(stringifyEntityRef) + .filter(s => !foundEntityNames.includes(s)); + logger?.debug(`Entities not found for refs ${missingEntityNames.join()}`); + } + + const memberOf = entities.flatMap( + e => + e!.relations + ?.filter(r => r.type === RELATION_MEMBER_OF) + .map(r => r.target) ?? [], + ); + + const newEntityRefs = [ + ...new Set(resolvedEntityRefs.concat(memberOf).map(stringifyEntityRef)), + ]; + + logger?.debug(`Found catalog membership: ${newEntityRefs.join()}`); + return newEntityRefs; + } } diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts index 6b262dcf68..4c093df8ce 100644 --- a/plugins/auth-backend/src/providers/index.ts +++ b/plugins/auth-backend/src/providers/index.ts @@ -16,6 +16,7 @@ export * from './google'; export * from './microsoft'; +export * from './okta'; export { factories as defaultAuthProviderFactories } from './factories'; // Export the minimal interface required for implementing a diff --git a/plugins/auth-backend/src/providers/okta/index.ts b/plugins/auth-backend/src/providers/okta/index.ts index 37abef181a..fd9ebf46da 100644 --- a/plugins/auth-backend/src/providers/okta/index.ts +++ b/plugins/auth-backend/src/providers/okta/index.ts @@ -14,5 +14,5 @@ * limitations under the License. */ -export { createOktaProvider } from './provider'; +export { createOktaProvider, oktaEmailSignInResolver } from './provider'; export type { OktaProviderOptions } from './provider'; diff --git a/plugins/auth-backend/src/providers/okta/provider.test.ts b/plugins/auth-backend/src/providers/okta/provider.test.ts new file mode 100644 index 0000000000..939981e81c --- /dev/null +++ b/plugins/auth-backend/src/providers/okta/provider.test.ts @@ -0,0 +1,105 @@ +/* + * 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 { OktaAuthProvider } from './provider'; +import * as helpers from '../../lib/passport/PassportStrategyHelper'; +import { OAuthResult } from '../../lib/oauth'; +import { getVoidLogger } from '@backstage/backend-common'; +import { TokenIssuer } from '../../identity/types'; +import { CatalogIdentityClient } from '../../lib/catalog'; + +const mockFrameHandler = jest.spyOn( + helpers, + 'executeFrameHandlerStrategy', +) as unknown as jest.MockedFunction< + () => Promise<{ result: OAuthResult; privateInfo: any }> +>; + +describe('createOktaProvider', () => { + it('should auth', async () => { + const tokenIssuer = { + issueToken: jest.fn(), + listPublicKeys: jest.fn(), + }; + const catalogIdentityClient = { + findUser: jest.fn(), + }; + + const provider = new OktaAuthProvider({ + logger: getVoidLogger(), + catalogIdentityClient: + catalogIdentityClient as unknown as CatalogIdentityClient, + tokenIssuer: tokenIssuer as unknown as TokenIssuer, + authHandler: async ({ fullProfile }) => ({ + profile: { + email: fullProfile.emails![0]!.value, + displayName: fullProfile.displayName, + }, + }), + audience: 'http://example.com', + clientId: 'mock', + clientSecret: 'mock', + callbackUrl: 'mock', + }); + + mockFrameHandler.mockResolvedValueOnce({ + result: { + fullProfile: { + emails: [ + { + type: 'work', + value: 'conrad@example.com', + }, + ], + displayName: 'Conrad', + name: { + familyName: 'Ribas', + givenName: 'Francisco', + }, + id: 'conrad', + provider: 'okta', + photos: [ + { + value: 'some-data', + }, + ], + }, + params: { + id_token: 'idToken', + scope: 'scope', + expires_in: 123, + }, + accessToken: 'accessToken', + }, + privateInfo: { + refreshToken: 'wacka', + }, + }); + const { response } = await provider.handler({} as any); + expect(response).toEqual({ + providerInfo: { + accessToken: 'accessToken', + expiresInSeconds: 123, + idToken: 'idToken', + scope: 'scope', + }, + profile: { + email: 'conrad@example.com', + displayName: 'Conrad', + }, + }); + }); +}); diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 9709b549d7..24ee14becc 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -35,8 +35,16 @@ import { executeFetchUserProfileStrategy, PassportDoneCallback, } from '../../lib/passport'; -import { RedirectInfo, AuthProviderFactory } from '../types'; +import { + AuthProviderFactory, + AuthHandler, + RedirectInfo, + SignInResolver, +} from '../types'; import { StateStore } from 'passport-oauth2'; +import { CatalogIdentityClient, getEntityClaims } from '../../lib/catalog'; +import { TokenIssuer } from '../../identity'; +import { Logger } from 'winston'; type PrivateInfo = { refreshToken: string; @@ -44,10 +52,20 @@ type PrivateInfo = { export type OktaAuthProviderOptions = OAuthProviderOptions & { audience: string; + signInResolver?: SignInResolver; + authHandler: AuthHandler; + tokenIssuer: TokenIssuer; + catalogIdentityClient: CatalogIdentityClient; + logger: Logger; }; export class OktaAuthProvider implements OAuthHandlers { private readonly _strategy: any; + private readonly _signInResolver?: SignInResolver; + private readonly _authHandler: AuthHandler; + private readonly _tokenIssuer: TokenIssuer; + private readonly _catalogIdentityClient: CatalogIdentityClient; + private readonly _logger: Logger; /** * Due to passport-okta-oauth forcing options.state = true, @@ -67,6 +85,12 @@ export class OktaAuthProvider implements OAuthHandlers { }; constructor(options: OktaAuthProviderOptions) { + this._signInResolver = options.signInResolver; + this._authHandler = options.authHandler; + this._tokenIssuer = options.tokenIssuer; + this._catalogIdentityClient = options.catalogIdentityClient; + this._logger = options.logger; + this._strategy = new OktaStrategy( { clientID: options.clientId, @@ -117,18 +141,8 @@ export class OktaAuthProvider implements OAuthHandlers { PrivateInfo >(req, this._strategy); - const profile = makeProfileInfo(result.fullProfile, result.params.id_token); - return { - response: await this.populateIdentity({ - profile, - providerInfo: { - idToken: result.params.id_token, - accessToken: result.accessToken, - scope: result.params.scope, - expiresInSeconds: result.params.expires_in, - }, - }), + response: await this.handleResult(result), refreshToken: privateInfo.refreshToken, }; } @@ -144,52 +158,157 @@ export class OktaAuthProvider implements OAuthHandlers { this._strategy, accessToken, ); - const profile = makeProfileInfo(fullProfile, params.id_token); - return this.populateIdentity({ - providerInfo: { - accessToken, - idToken: params.id_token, - expiresInSeconds: params.expires_in, - scope: params.scope, - }, - profile, + return this.handleResult({ + fullProfile, + params, + accessToken, + refreshToken: req.refreshToken, }); } - private async populateIdentity( - response: OAuthResponse, - ): Promise { - const { profile } = response; + private async handleResult(result: OAuthResult) { + const { profile } = await this._authHandler(result); - if (!profile.email) { - throw new Error('Okta profile contained no email'); + const response: OAuthResponse = { + providerInfo: { + idToken: result.params.id_token, + accessToken: result.accessToken, + scope: result.params.scope, + expiresInSeconds: result.params.expires_in, + }, + profile, + }; + + if (this._signInResolver) { + response.backstageIdentity = await this._signInResolver( + { + result, + profile, + }, + { + tokenIssuer: this._tokenIssuer, + catalogIdentityClient: this._catalogIdentityClient, + logger: this._logger, + }, + ); } - // TODO(Rugvip): Hardcoded to the local part of the email for now - const id = profile.email.split('@')[0]; - - return { ...response, backstageIdentity: { id } }; + return response; } } -export type OktaProviderOptions = {}; +export const oktaEmailSignInResolver: SignInResolver = async ( + info, + ctx, +) => { + const { profile } = info; + + if (!profile.email) { + throw new Error('Okta profile contained no email'); + } + + const entity = await ctx.catalogIdentityClient.findUser({ + annotations: { + 'okta.com/email': profile.email, + }, + }); + + const claims = getEntityClaims(entity); + const token = await ctx.tokenIssuer.issueToken({ claims }); + + return { id: entity.metadata.name, entity, token }; +}; + +export const oktaDefaultSignInResolver: SignInResolver = async ( + info, + ctx, +) => { + const { profile } = info; + + if (!profile.email) { + throw new Error('Okta profile contained no email'); + } + + // TODO(Rugvip): Hardcoded to the local part of the email for now + const userId = profile.email.split('@')[0]; + + const token = await ctx.tokenIssuer.issueToken({ + claims: { sub: userId, ent: [`user:default/${userId}`] }, + }); + + return { id: userId, token }; +}; + +export type OktaProviderOptions = { + /** + * The profile transformation function used to verify and convert the auth response + * into the profile that will be presented to the user. + */ + authHandler?: AuthHandler; + + /** + * Configure sign-in for this provider, without it the provider can not be used to sign users in. + */ + /** + * Maps an auth result to a Backstage identity for the user. + * + * Set to `'email'` to use the default email-based sign in resolver, which will search + * the catalog for a single user entity that has a matching `okta.com/email` annotation. + */ + signIn?: { + resolver?: SignInResolver; + }; +}; export const createOktaProvider = ( _options?: OktaProviderOptions, ): AuthProviderFactory => { - return ({ providerId, globalConfig, config, tokenIssuer }) => + return ({ + providerId, + globalConfig, + config, + tokenIssuer, + catalogApi, + logger, + }) => OAuthEnvironmentHandler.mapConfig(config, envConfig => { const clientId = envConfig.getString('clientId'); const clientSecret = envConfig.getString('clientSecret'); const audience = envConfig.getString('audience'); const callbackUrl = `${globalConfig.baseUrl}/${providerId}/handler/frame`; + const catalogIdentityClient = new CatalogIdentityClient({ + catalogApi, + tokenIssuer, + }); + + const authHandler: AuthHandler = _options?.authHandler + ? _options.authHandler + : async ({ fullProfile, params }) => ({ + profile: makeProfileInfo(fullProfile, params.id_token), + }); + + const signInResolverFn = + _options?.signIn?.resolver ?? oktaDefaultSignInResolver; + + const signInResolver: SignInResolver = info => + signInResolverFn(info, { + catalogIdentityClient, + tokenIssuer, + logger, + }); + const provider = new OktaAuthProvider({ audience, clientId, clientSecret, callbackUrl, + authHandler, + signInResolver, + tokenIssuer, + catalogIdentityClient, + logger, }); return OAuthAdapter.fromConfig(globalConfig, provider, { diff --git a/plugins/badges/package.json b/plugins/badges/package.json index a46081443a..fb97217d54 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.4.1", "@backstage/theme": "^0.2.9", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index 3ba44e37d4..45050ae0e3 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-bitrise +## 0.1.10 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.1.9 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 98cd862b0f..88e6b4b000 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitrise", - "version": "0.1.9", + "version": "0.1.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,16 +21,16 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "cross-fetch": "^3.0.6", "lodash": "^4.17.21", - "luxon": "^1.26.0", + "luxon": "^2.0.2", "qs": "^6.9.6", "react": "^16.13.1", "react-dom": "^16.13.1", @@ -38,10 +38,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 3849fec510..bd66da47b6 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend +## 0.13.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/backend-common@0.8.9 + ## 0.13.1 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index d67cd7553b..0bf5a08dfd 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.13.1", + "version": "0.13.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,12 +29,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.6", + "@backstage/backend-common": "^0.8.9", "@backstage/catalog-client": "^0.3.18", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.8", + "@backstage/integration": "^0.6.0", "@backstage/plugin-search-backend-node": "^0.4.0", "@backstage/search-common": "^0.1.2", "@octokit/graphql": "^4.5.8", @@ -61,9 +61,9 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.4", - "@backstage/cli": "^0.7.6", - "@backstage/test-utils": "^0.1.16", + "@backstage/backend-test-utils": "^0.1.5", + "@backstage/cli": "^0.7.8", + "@backstage/test-utils": "^0.1.17", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 421871cb52..a60a97759f 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-import +## 0.5.17 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + - @backstage/integration-react@0.1.7 + ## 0.5.16 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 35e44bd070..44609323d5 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.5.16", + "version": "0.5.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,11 +32,11 @@ "dependencies": { "@backstage/catalog-client": "^0.3.18", "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/integration": "^0.5.9", - "@backstage/integration-react": "^0.1.6", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/integration": "^0.6.0", + "@backstage/integration-react": "^0.1.7", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -54,10 +54,10 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 1a9f329f4b..f152a6c003 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-react +## 0.4.2 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- a440d3b38: Move and rename `FavoriteEntity` component to `catalog-react` +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-app-api@0.1.8 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + ## 0.4.1 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 0737ece2ff..426ebb5038 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "0.4.1", + "version": "0.4.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "dependencies": { "@backstage/catalog-client": "^0.3.18", "@backstage/catalog-model": "^0.9.0", - "@backstage/core-app-api": "^0.1.7", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/integration": "^0.5.9", + "@backstage/core-app-api": "^0.1.8", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/integration": "^0.6.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -47,9 +47,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index cd3d846382..dc9dc66e47 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog +## 0.6.11 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- a440d3b38: Move and rename `FavoriteEntity` component to `catalog-react` +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + - @backstage/integration-react@0.1.7 + ## 0.6.10 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 65b741a855..e1132ab299 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.6.10", + "version": "0.6.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,12 +32,12 @@ "dependencies": { "@backstage/catalog-client": "^0.3.18", "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.9", - "@backstage/integration-react": "^0.1.6", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/integration": "^0.6.0", + "@backstage/integration-react": "^0.1.7", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -54,10 +54,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 06e062e785..85ae5d522f 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -32,8 +32,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/plugin-catalog-react": "^0.4.1", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", @@ -50,10 +50,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 504aa7cfa8..ae64b1ebc3 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/plugin-catalog-react": "^0.4.1", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 7e008b4dcf..1c8b9f18ea 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-code-coverage-backend +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/backend-common@0.8.9 + ## 0.1.8 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index fa2f38b3eb..cbebaaf5d8 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-code-coverage-backend", - "version": "0.1.8", + "version": "0.1.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,12 +19,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.5", + "@backstage/backend-common": "^0.8.9", "@backstage/catalog-client": "^0.3.16", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.8", + "@backstage/integration": "^0.6.0", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", "express": "^4.17.1", @@ -36,7 +36,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.1", + "@backstage/cli": "^0.7.8", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.29.0", diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 75915fc86b..dd00c56239 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -22,8 +22,8 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.4.1", "@backstage/theme": "^0.2.9", @@ -40,10 +40,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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 a09f6d4775..1f63d733c1 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", @@ -35,10 +35,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 60a04574fa..7f4ccf2db8 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-cost-insights +## 0.11.4 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + ## 0.11.3 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index e46d154cf9..ce8abcd12c 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cost-insights", - "version": "0.11.3", + "version": "0.11.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ }, "dependencies": { "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,7 +42,7 @@ "@types/recharts": "^1.8.14", "classnames": "^2.2.6", "history": "^5.0.0", - "luxon": "^1.26.0", + "luxon": "^2.0.2", "pluralize": "^8.0.0", "qs": "^6.9.4", "react": "^16.13.1", @@ -54,10 +54,10 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/explore/CHANGELOG.md index d21b4e0401..49fdfc8cba 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-explore +## 0.3.13 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.3.12 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index d66d6e8465..a687f0f43c 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.3.12", + "version": "0.3.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/plugin-explore-react": "^0.0.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/.eslintrc.js b/plugins/firehydrant/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/firehydrant/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/firehydrant/README.md b/plugins/firehydrant/README.md new file mode 100644 index 0000000000..aaab8b7d0e --- /dev/null +++ b/plugins/firehydrant/README.md @@ -0,0 +1,59 @@ +# FireHydrant Plugin + +## Overview + +The [FireHydrant](https://firehydrant.io) plugin brings incident management to Backstage, and it displays service incidents information such as active incidents and incident analytics. There are also quick action links that let you create and view incidents in FireHydrant. + +FireHydrant plugin screenshot + +## Features + +- View total active incidents for a service declared in FireHydrant +- Quick links to the top 5 most recent active incidents in FireHydrant +- View incident metrics for a given service within the last 30 days, including: healthiness, total time impacted, total number of incidents, and MTT* (Mean Time To *) data such as MTTD (detect), MTTA (acknowledge), MTTM (mitigate) and MTTR (resolve). + +## Setup + +1. Install the plugin: + +```bash +# From your Backstage root directory +cd packages/app +yarn add @backstage/plugin-firehydrant +``` + +2. Add the plugin to `EntityPage.tsx`, inside the `const overviewContent`'s parent `` component: + +```ts +// In packages/app/src/components/catalog/EntityPage.tsx +import { FirehydrantCard } from '@backstage/plugin-firehydrant'; + +// Add to code as a grid item + + +; +``` + +3. Add proxy configuration to `app-config.yaml`: + +```yaml +proxy: + '/firehydrant/api': + target: 'https://api.firehydrant.io/v1/' + changeOrigin: true + headers: + # Supply the token you generated from https://app.firehydrant.io/organizations/bots + Authorization: Bearer ${FIREHYDRANT_BOT_TOKEN} +``` + +Note: if you are not using environment variables, you can directly type the API Bot Token into `app-config.yaml`: + +```yaml +proxy: +'/firehydrant/api': + target: 'https://api.firehydrant.io/v1/' + changeOrigin: true + headers: + # Supply the token you generated from https://app.firehydrant.io/organizations/bots + Authorization: Bearer fhb-e4911b22bcd788c4a4afeb0c111ffbfa +``` diff --git a/plugins/firehydrant/api-report.md b/plugins/firehydrant/api-report.md new file mode 100644 index 0000000000..6cd8270178 --- /dev/null +++ b/plugins/firehydrant/api-report.md @@ -0,0 +1,27 @@ +## API Report File for "@backstage/plugin-firehydrant" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; + +// Warning: (ae-missing-release-tag) "FirehydrantCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const FirehydrantCard: () => JSX.Element; + +// Warning: (ae-missing-release-tag) "firehydrantPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const firehydrantPlugin: BackstagePlugin< + { + root: RouteRef; + }, + {} +>; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/firehydrant/dev/index.tsx b/plugins/firehydrant/dev/index.tsx new file mode 100644 index 0000000000..237cb2737e --- /dev/null +++ b/plugins/firehydrant/dev/index.tsx @@ -0,0 +1,19 @@ +/* + * 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 { createDevApp } from '@backstage/dev-utils'; +import { firehydrantPlugin } from '../src/plugin'; + +createDevApp().registerPlugin(firehydrantPlugin).render(); diff --git a/plugins/firehydrant/doc/firehydrant_logo.png b/plugins/firehydrant/doc/firehydrant_logo.png new file mode 100644 index 0000000000..47cd43b0fc Binary files /dev/null and b/plugins/firehydrant/doc/firehydrant_logo.png differ diff --git a/plugins/firehydrant/doc/firehydrant_plugin_screenshot.png b/plugins/firehydrant/doc/firehydrant_plugin_screenshot.png new file mode 100644 index 0000000000..25b5cbcaa1 Binary files /dev/null and b/plugins/firehydrant/doc/firehydrant_plugin_screenshot.png differ diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json new file mode 100644 index 0000000000..c4fc6a3f48 --- /dev/null +++ b/plugins/firehydrant/package.json @@ -0,0 +1,68 @@ +{ + "name": "@backstage/plugin-firehydrant", + "version": "0.1.1", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/theme": "^0.2.9", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "luxon": "^1.27.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-use": "^17.2.4" + }, + "devDependencies": { + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^11.2.5", + "@testing-library/user-event": "^13.1.8", + "@types/jest": "^26.0.7", + "@types/node": "^14.14.32", + "cross-fetch": "^3.0.6", + "msw": "^0.29.0" + }, + "files": [ + "dist" + ], + "configSchema": { + "$schema": "https://backstage.io/schema/config-v1", + "title": "@backstage/plugin-firehydrant", + "type": "object", + "properties": { + "firehydrant": { + "type": "object", + "properties": { + "baseUrl": { + "type": "string", + "visibility": "frontend" + } + } + } + } + } +} diff --git a/plugins/firehydrant/src/api/index.ts b/plugins/firehydrant/src/api/index.ts new file mode 100644 index 0000000000..b78e81a024 --- /dev/null +++ b/plugins/firehydrant/src/api/index.ts @@ -0,0 +1,136 @@ +/* + * 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 { + ServiceAnalyticsResponse, + ServiceDetailsResponse, + ServiceIncidentsResponse, +} from './types'; +import { Incident, Service } from '../components/types'; +import { createApiRef, DiscoveryApi } from '@backstage/core-plugin-api'; + +export interface FireHydrantAPI { + getServiceAnalytics(options: { + serviceId: string; + startDate: string; + endDate: string; + }): Promise; + + getServiceDetails(options: { + serviceName: string; + }): Promise; + + getServiceIncidents(options: { + serviceId: string; + }): Promise; +} + +export const fireHydrantApiRef = createApiRef({ + id: 'plugin.firehydrant.service', + description: 'Used by FireHydrant plugin for requests', +}); + +export type Options = { + discoveryApi: DiscoveryApi; + proxyPath?: string; +}; + +const DEFAULT_PROXY_PATH = '/firehydrant/api'; + +export class FireHydrantAPIClient implements FireHydrantAPI { + private readonly discoveryApi: DiscoveryApi; + private readonly proxyPath: string; + + constructor(options: Options) { + this.discoveryApi = options.discoveryApi; + this.proxyPath = options.proxyPath ?? DEFAULT_PROXY_PATH; + } + + async getServiceAnalytics(options: { + serviceId: string; + startDate: string; + endDate: string; + }): Promise { + const proxyUrl = await this.getApiUrl(); + const response = await fetch( + `${proxyUrl}/metrics/services/${options.serviceId}?start_date=${options.startDate}&end_date=${options.endDate}`, + ); + if (!response.ok) { + throw new Error( + `There was a problem fetching FireHydrant analytics data: ${response.statusText}`, + ); + } + const json = await response.json(); + return json; + } + + async getServiceDetails(options: { + serviceName: string; + }): Promise { + const proxyUrl = await this.getApiUrl(); + const response = await fetch( + `${proxyUrl}/services?query=${options.serviceName}`, + ); + + if (!response.ok) { + throw new Error( + `There was a problem fetching FireHydrant data: ${response.statusText}`, + ); + } + + const json = await response.json(); + + const servicesData: ServiceDetailsResponse = { + service: {} as Service, + incidents: [] as Incident[], + }; + + if (json.data?.length === 0) { + return servicesData; + } + + servicesData.service = json.data[0]; + + const incidentsJson = await this.getServiceIncidents({ + serviceId: json.data[0].id, + }); + + servicesData.incidents = incidentsJson; + + return servicesData; + } + + async getServiceIncidents(options: { + serviceId: string; + }): Promise { + const proxyUrl = await this.getApiUrl(); + const response = await fetch( + `${proxyUrl}/incidents?services=${options.serviceId}&active=true`, + ); + if (!response.ok) { + throw new Error( + `There was a problem fetching FireHydrant incidents data: ${response.statusText}`, + ); + } + + const json = await response.json(); + return json.data; + } + + private async getApiUrl() { + const proxyUrl = await this.discoveryApi.getBaseUrl('proxy'); + return proxyUrl + this.proxyPath; + } +} diff --git a/plugins/firehydrant/src/api/types.ts b/plugins/firehydrant/src/api/types.ts new file mode 100644 index 0000000000..089c850c97 --- /dev/null +++ b/plugins/firehydrant/src/api/types.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. + */ +import { Incident, Service } from '../components/types'; + +export type ServiceDetailsResponse = { + service?: Service; + incidents?: ServiceIncidentsResponse; +}; + +export type ServiceAnalyticsResponse = { + id: string; + mttd?: number; + mtta?: number; + mttm?: number; + mttr?: number; + count: number; + total_time: number; +}; + +export type ServiceIncidentsResponse = Incident[]; diff --git a/plugins/firehydrant/src/components/ServiceAnalytics/ServiceAnalytics.test.tsx b/plugins/firehydrant/src/components/ServiceAnalytics/ServiceAnalytics.test.tsx new file mode 100644 index 0000000000..82bbac57c1 --- /dev/null +++ b/plugins/firehydrant/src/components/ServiceAnalytics/ServiceAnalytics.test.tsx @@ -0,0 +1,42 @@ +/* + * 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 { ServiceAnalytics } from './ServiceAnalytics'; +import { renderInTestApp } from '@backstage/test-utils'; + +describe('ServiceAnalytics', () => { + it('renders service analytics', async () => { + await renderInTestApp( + , + ); + + expect(await screen.findByText(/32/)).toBeInTheDocument(); + expect(await screen.findByText(/44/)).toBeInTheDocument(); + }); +}); diff --git a/plugins/firehydrant/src/components/ServiceAnalytics/ServiceAnalytics.tsx b/plugins/firehydrant/src/components/ServiceAnalytics/ServiceAnalytics.tsx new file mode 100644 index 0000000000..e5818ef484 --- /dev/null +++ b/plugins/firehydrant/src/components/ServiceAnalytics/ServiceAnalytics.tsx @@ -0,0 +1,166 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import { DateTime } from 'luxon'; +import { + ResponseErrorPanel, + Table, + TableColumn, + Progress, +} from '@backstage/core-components'; + +const useStyles = makeStyles({ + container: { + overflow: 'auto', + width: '100%', + '& td': { + minWidth: '145px', + }, + '& th': { + minWidth: '145px', + }, + }, +}); + +export const DenseTable = ({ + service, + startDate, + endDate, +}: { + service: object; + startDate: DateTime; + endDate: DateTime; +}) => { + const classes = useStyles(); + + const columns: TableColumn[] = [ + { field: 'healthiness', title: 'Healthiness' }, + { field: 'impacted', title: 'Impacted' }, + { field: 'incidents', title: 'Incidents' }, + { field: 'mttd', title: 'MTTD' }, + { field: 'mtta', title: 'MTTA' }, + { field: 'mttm', title: 'MTTM' }, + { field: 'mttr', title: 'MTTR' }, + ]; + + return ( +
+ + + ); +}; + +export const secondsToDhms = (seconds: number) => { + const secs = Number(seconds); + const d = Math.floor(secs / (60 * 60 * 24)); + const h = Math.floor((secs / (60 * 60)) % 24); + const m = Math.floor((secs / 60) % 60); + const s = secs % 60; + + const dDisplay = d > 0 ? `${d}d ` : ''; + const hDisplay = h > 0 ? `${h}h ` : '00h '; + const mDisplay = m > 0 ? `${m}m ` : '00m '; + const sDisplay = s > 0 ? `${s}s` : '00s'; + return dDisplay + hDisplay + mDisplay + sDisplay; +}; + +export const truncateNum = (num: number) => { + const matcher = `^-?\\d+(?:\\.\\d{0,3})?`; + const re = new RegExp(matcher); + const match = num.toString().match(re); + const result = (match && match[0]) || '0'; // eslint-disable-line no-mixed-operators + return result; +}; + +export const calcHealthiness = ({ + mttm, + incidents, + range, +}: { + mttm: number; + incidents: number; + range: number; +}) => { + const num = (1 - (mttm * incidents) / range) * 100; + return `${truncateNum(num)}%`; +}; + +type AnalyticsDataType = { + id?: string; + count?: number; + mttd?: number; + mtta?: number; + mttm?: number; + mttr?: number; + total_time?: number; +}; + +export const ServiceAnalytics = ({ + value, + loading, + error, +}: { + value: AnalyticsDataType; + loading: boolean; + error: any; +}) => { + if (loading) { + return ; + } else if (error) { + return ; + } + const startDate = DateTime.now().minus({ days: 30 }).toUTC(); + const endDate = DateTime.now().toUTC(); + + // Transform and format the data to display in the table + if (value.id) { + const serviceData = { + healthiness: + value.mttm && value.count + ? calcHealthiness({ + mttm: value.mttm, + incidents: value.count, + range: endDate.diff(startDate, 'seconds').as('seconds'), + }) + : '100%', + impacted: value.total_time ? secondsToDhms(value.total_time) : '-', + incidents: value.count, + mttd: value.mttd ? secondsToDhms(value.mttd) : '-', + mtta: value.mtta ? secondsToDhms(value.mtta) : '-', + mttm: value.mttm ? secondsToDhms(value.mttm) : '-', + mttr: value.mttr ? secondsToDhms(value.mttr) : '-', + }; + + return ( + + ); + } + + return null; +}; diff --git a/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.test.tsx b/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.test.tsx new file mode 100644 index 0000000000..1123f014da --- /dev/null +++ b/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.test.tsx @@ -0,0 +1,118 @@ +/* + * 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 { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { fireHydrantApiRef } from '../../api'; +import { screen } from '@testing-library/react'; +import { ServiceDetailsCard } from './ServiceDetailsCard'; +import { Service, Incident } from '../types'; +import { renderInTestApp } from '@backstage/test-utils'; + +const mockFireHydrantApi = { + getServiceDetails: () => {}, + getServiceAnalytics: () => {}, +}; + +const apis = ApiRegistry.from([[fireHydrantApiRef, mockFireHydrantApi]]); + +jest.mock('@backstage/plugin-catalog-react', () => ({ + useEntity: () => { + return { entity: { metadata: { name: 'service-example' } } }; + }, +})); + +describe('ServiceDetailsCard', () => { + it('renders service incidents', async () => { + mockFireHydrantApi.getServiceDetails = jest.fn().mockImplementationOnce( + async () => + [ + { + service: { + id: '12345', + name: 'service-example', + description: 'This is a sample description', + active_incidents: ['654321'], + } as Service, + incidents: [ + { + id: '654321', + active: true, + description: 'test incident', + name: 'incident name here', + incident_url: 'http://example.com', + } as Incident, + ], + }, + ][0], + ); + + mockFireHydrantApi.getServiceAnalytics = jest.fn().mockImplementationOnce( + async () => + [ + { + buckets: [ + { + metrics: { + 12345: { + count: 0, + mtta: null, + mttd: null, + mttm: null, + mttr: null, + total_time: null, + }, + }, + }, + ], + }, + ][0], + ); + + await renderInTestApp( + + + , + ); + + expect( + await screen.findByText(/View service incidents/), + ).toBeInTheDocument(); + expect( + await screen.findByText(/There is 1 active incident/), + ).toBeInTheDocument(); + expect(await screen.findByText(/incident name here/)).toBeInTheDocument(); + }); + + it('handles empty response', async () => { + mockFireHydrantApi.getServiceDetails = jest + .fn() + .mockImplementationOnce(async () => []); + + mockFireHydrantApi.getServiceAnalytics = jest + .fn() + .mockImplementationOnce(async () => []); + + await renderInTestApp( + + + , + ); + + expect( + await screen.findByText(/This service does not exist in FireHydrant/), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.tsx b/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.tsx new file mode 100644 index 0000000000..b70c578e11 --- /dev/null +++ b/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.tsx @@ -0,0 +1,305 @@ +/* + * 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 { DateTime } from 'luxon'; +import { ServiceAnalytics } from '../ServiceAnalytics/ServiceAnalytics'; +import { + Box, + Button as MaterialButton, + Typography, + makeStyles, +} from '@material-ui/core'; +import ExitToAppIcon from '@material-ui/icons/ExitToApp'; +import NotesIcon from '@material-ui/icons/Notes'; +import WhatshotIcon from '@material-ui/icons/Whatshot'; +import WarningIcon from '@material-ui/icons/Warning'; +import AddIcon from '@material-ui/icons/Add'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { Incident } from '../types'; +import { ServiceIncidentsResponse } from '../../api/types'; +import { useServiceDetails } from '../serviceDetails'; +import { useServiceAnalytics } from '../serviceAnalytics'; +import { + InfoCard, + Link, + Progress, + ResponseErrorPanel, +} from '@backstage/core-components'; +import { configApiRef, useApi } from '@backstage/core-plugin-api'; + +const useStyles = makeStyles(theme => ({ + button: { + color: '#3b2492', + display: 'grid', + gridGap: '4px', + textAlign: 'center', + justifyItems: 'center', + width: '105px', + backgroundColor: theme.palette.type === 'dark' ? '#f1edff' : '', + '&:hover, &:focus': { + backgroundColor: '#f1edff', + color: '#614ab6', + }, + '&:active': { + color: '#3b2492', + backgroundColor: '#b2a6e3', + boxShadow: + 'rgb(59, 36, 146) 0px 0px 0px 1px inset, rgb(141, 134, 188) 3px 3px 0px 0px inset;', + }, + border: '1px solid #3b2492', + borderRadius: '5px', + padding: '8px 10px', + textTransform: 'none', + }, + buttonLink: { + backgroundColor: '#3b2492', + color: '#FFF', + textTransform: 'none', + '&:hover': { + backgroundColor: '#614ab6', + }, + }, + buttonContainer: { + display: 'grid', + gridGap: '24px', + gridAutoFlow: 'column', + gridAutoColumns: 'min-content', + }, + icon: { + color: '#f1642d', + }, + link: { + textDecoration: 'underline', + fontSize: '16px', + lineHeight: '27px', + color: '#3b2492', + '&:hover, &:focus': { + fontWeight: '500', + }, + }, + linksContainer: { + borderBottom: '1px solid #d5d5d5', + padding: '10px 0px 10px 20px', + backgroundColor: '#f1edff', + marginBottom: '20px', + }, + table: { + width: '100%', + }, + warning: { + display: 'flex', + alignItems: 'center', + padding: '10px', + background: '#f1edff', + color: '#3b2492', + }, +})); + +const ServiceAnalyticsView = ({ + serviceId, + startDate, + endDate, +}: { + serviceId: string; + startDate: DateTime; + endDate: DateTime; +}) => { + const { + loading: analyticsLoading, + value: analyticsValue = {}, + error: analyticsError, + } = useServiceAnalytics({ + serviceId, + startDate: startDate.toFormat('YYYY-MM-DD'), + endDate: endDate.toFormat('YYYY-MM-DD'), + }); + + return ( + + ); +}; + +export const ServiceDetailsCard = () => { + const { entity } = useEntity(); + const classes = useStyles(); + const [showServiceDetails, setShowServiceDetails] = useState(false); + const configApi = useApi(configApiRef); + + const BASE_URL = + configApi.getOptionalString('firehydrant.baseUrl') || + 'https://app.firehydrant.io'; + + const startDate = DateTime.now().minus({ days: 30 }).toUTC(); + const endDate = DateTime.now().toUTC(); + + // The Backstage service name in FireHydrant is a unique formatted string + // that requires the entity's kind, name, and namespace. + const fireHydrantServiceName = `${entity?.kind}:${ + entity?.metadata?.namespace ?? 'default' + }/${entity?.metadata?.name}`; + + const { loading, value, error } = useServiceDetails({ + serviceName: fireHydrantServiceName, + }); + + const activeIncidents: string[] = value?.service?.active_incidents ?? []; + const incidents: ServiceIncidentsResponse = value?.incidents ?? []; + const serviceId: string = value?.service?.id!; + + useEffect(() => { + if (value?.service && Object.keys(value?.service).length > 0) { + setShowServiceDetails(true); + } + }, [value]); + + if (loading) { + return ; + } + + if (error) { + return ; + } + + const headerText: string = showServiceDetails + ? `There ${activeIncidents?.length === 1 ? 'is' : 'are'} ${ + activeIncidents?.length + } active incident${activeIncidents?.length === 1 ? '' : 's'}.` + : ''; + + const serviceIncidentsLink: string = `${BASE_URL}/incidents?search={"services":[{"label":${JSON.stringify( + value?.service?.name, + )},"value":${JSON.stringify(value?.service?.id)}}]}`; + + return ( + + {!showServiceDetails && !loading && ( +
+ +   This service does not exist in FireHydrant. +
+ )} + {showServiceDetails && ( + + +

{headerText}

+
+ + } + target="_blank" + variant="outlined" + > + View service incidents + + +
+ )} + {activeIncidents && activeIncidents?.length > 0 && ( + + {incidents && + incidents?.slice(0, 5).map((incident: Incident, index: number) => ( +
+ + {incident.name} + +
+ ))} +
+ )} + + View in FireHydrant + + + + + + + + Declare an incident + + + + + + + + + + View all incidents + + + + {showServiceDetails && ( + + + + + + + View Service Details + + + + )} + + + {showServiceDetails && ( + + + + )} +
+ ); +}; diff --git a/plugins/firehydrant/src/components/ServiceDetailsCard/index.ts b/plugins/firehydrant/src/components/ServiceDetailsCard/index.ts new file mode 100644 index 0000000000..33e68ecf3b --- /dev/null +++ b/plugins/firehydrant/src/components/ServiceDetailsCard/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 { ServiceDetailsCard } from './ServiceDetailsCard'; diff --git a/plugins/firehydrant/src/components/serviceAnalytics.ts b/plugins/firehydrant/src/components/serviceAnalytics.ts new file mode 100644 index 0000000000..ba88af86da --- /dev/null +++ b/plugins/firehydrant/src/components/serviceAnalytics.ts @@ -0,0 +1,51 @@ +/* + * 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 { useAsyncRetry } from 'react-use'; +import { fireHydrantApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; + +export const useServiceAnalytics = ({ + serviceId, + startDate, + endDate, +}: { + serviceId: string; + startDate: string; + endDate: string; +}) => { + const api = useApi(fireHydrantApiRef); + const errorApi = useApi(errorApiRef); + + const { loading, value, error, retry } = useAsyncRetry(async () => { + try { + return await api.getServiceAnalytics({ + serviceId: serviceId, + startDate: startDate, + endDate: endDate, + }); + } catch (e) { + errorApi.post(e); + return Promise.reject(e); + } + }); + + return { + loading, + value, + error, + retry, + }; +}; diff --git a/plugins/firehydrant/src/components/serviceDetails.ts b/plugins/firehydrant/src/components/serviceDetails.ts new file mode 100644 index 0000000000..b8cac2736a --- /dev/null +++ b/plugins/firehydrant/src/components/serviceDetails.ts @@ -0,0 +1,39 @@ +/* + * 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 { useAsyncRetry } from 'react-use'; +import { fireHydrantApiRef } from '../api'; +import { errorApiRef, useApi } from '@backstage/core-plugin-api'; + +export const useServiceDetails = ({ serviceName }: { serviceName: string }) => { + const api = useApi(fireHydrantApiRef); + const errorApi = useApi(errorApiRef); + + const { loading, value, error, retry } = useAsyncRetry(async () => { + try { + return await api.getServiceDetails({ serviceName: serviceName }); + } catch (e) { + errorApi.post(e); + return Promise.reject(e); + } + }); + + return { + loading, + value, + error, + retry, + }; +}; diff --git a/plugins/firehydrant/src/components/types.ts b/plugins/firehydrant/src/components/types.ts new file mode 100644 index 0000000000..e607b2357d --- /dev/null +++ b/plugins/firehydrant/src/components/types.ts @@ -0,0 +1,29 @@ +/* + * 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 type Service = { + id: string; + name: string; + description?: string; + active_incidents?: string[]; +}; + +export type Incident = { + id: string; + active: boolean; + description?: string; + name: string; + incident_url: string; +}; diff --git a/plugins/firehydrant/src/index.ts b/plugins/firehydrant/src/index.ts new file mode 100644 index 0000000000..465e42f1ae --- /dev/null +++ b/plugins/firehydrant/src/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 { firehydrantPlugin, FirehydrantCard } from './plugin'; diff --git a/plugins/firehydrant/src/plugin.test.ts b/plugins/firehydrant/src/plugin.test.ts new file mode 100644 index 0000000000..ffde182e64 --- /dev/null +++ b/plugins/firehydrant/src/plugin.test.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. + */ +import { firehydrantPlugin } from './plugin'; + +describe('firehydrant', () => { + it('should export plugin', () => { + expect(firehydrantPlugin).toBeDefined(); + }); +}); diff --git a/plugins/firehydrant/src/plugin.ts b/plugins/firehydrant/src/plugin.ts new file mode 100644 index 0000000000..4277c1833f --- /dev/null +++ b/plugins/firehydrant/src/plugin.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { fireHydrantApiRef, FireHydrantAPIClient } from './api'; +import { + createApiFactory, + createPlugin, + discoveryApiRef, + createComponentExtension, +} from '@backstage/core-plugin-api'; + +import { rootRouteRef } from './routes'; + +export const firehydrantPlugin = createPlugin({ + id: 'firehydrant', + apis: [ + createApiFactory({ + api: fireHydrantApiRef, + deps: { discoveryApi: discoveryApiRef }, + factory: ({ discoveryApi }) => new FireHydrantAPIClient({ discoveryApi }), + }), + ], + routes: { + root: rootRouteRef, + }, +}); + +export const FirehydrantCard = firehydrantPlugin.provide( + createComponentExtension({ + component: { + lazy: () => + import('./components/ServiceDetailsCard').then( + m => m.ServiceDetailsCard, + ), + }, + }), +); diff --git a/plugins/firehydrant/src/routes.ts b/plugins/firehydrant/src/routes.ts new file mode 100644 index 0000000000..eb2f8b6375 --- /dev/null +++ b/plugins/firehydrant/src/routes.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. + */ +import { createRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + title: 'firehydrant', +}); diff --git a/plugins/firehydrant/src/setupTests.ts b/plugins/firehydrant/src/setupTests.ts new file mode 100644 index 0000000000..fc6dbd98f8 --- /dev/null +++ b/plugins/firehydrant/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 194b6024cc..62d5b2c5d8 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-fossa +## 0.2.14 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.2.13 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 616e34234b..069474d338 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-fossa", - "version": "0.2.13", + "version": "0.2.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,26 +32,26 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "cross-fetch": "^3.0.6", - "luxon": "^1.26.0", + "luxon": "^2.0.2", "p-limit": "^3.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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 d265fc0b22..5dd185a8ce 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index 8fab58d7d9..10f6204d92 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-git-release-manager +## 0.2.3 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + ## 0.2.2 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 328798186e..a762590d94 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-git-release-manager", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,16 +20,16 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/integration": "^0.5.9", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/integration": "^0.6.0", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@octokit/rest": "^18.5.3", "@types/react": "*", - "luxon": "^1.26.0", + "luxon": "^2.0.2", "qs": "^6.10.1", "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", @@ -38,10 +38,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react-hooks": "^3.4.2", "@testing-library/react": "^11.2.5", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index e5fb95c4a4..0ea9cd57dd 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-github-actions +## 0.4.16 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.4.15 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 791eacc9fe..9e64d7d37c 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.4.15", + "version": "0.4.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,16 +33,16 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/integration": "^0.5.9", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/integration": "^0.6.0", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@octokit/rest": "^18.5.3", - "luxon": "^1.27.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-lazylog": "^4.5.3", @@ -51,10 +51,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 6f5f7ac37f..a31d0c0b6a 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-github-deployments +## 0.1.14 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + - @backstage/integration-react@0.1.7 + ## 0.1.13 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index cc45227627..62d535cb7f 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-deployments", - "version": "0.1.13", + "version": "0.1.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,27 +21,27 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.9", - "@backstage/integration-react": "^0.1.6", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/integration": "^0.6.0", + "@backstage/integration-react": "^0.1.7", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@octokit/graphql": "^4.5.8", - "luxon": "^1.26.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 1c7c19f446..a71eef4b3f 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -43,10 +43,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index cfac7e7e5a..b8d19cad44 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -44,10 +44,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index 299adabda5..cad51a3f69 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-ilert +## 0.1.9 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.1.8 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index e849b8469c..7bc510f934 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-ilert", - "version": "0.1.8", + "version": "0.1.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@date-io/luxon": "2.x", "@material-ui/core": "^4.12.2", @@ -32,16 +32,16 @@ "@material-ui/lab": "4.0.0-alpha.45", "@material-ui/pickers": "^3.3.10", "humanize-duration": "^3.26.0", - "luxon": "^1.26.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index c680c70726..90352a7f95 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-jenkins +## 0.5.3 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.5.2 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 8040d6636b..99f5fa2f51 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.5.2", + "version": "0.5.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,14 +32,14 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "luxon": "^1.25.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/package.json b/plugins/kafka/package.json index 37720f5334..607d980e4e 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/plugin-catalog-react": "^0.4.1", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", @@ -34,10 +34,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 8b5db3086a..ab8263abe3 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kubernetes-backend +## 0.3.14 + +### Patch Changes + +- bbcd92afa: Adds ability to send an ExternalId with the assume role request to AWS +- Updated dependencies + - @backstage/backend-common@0.8.9 + ## 0.3.13 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 82347dc743..6abbd6ca61 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes-backend", - "version": "0.3.13", + "version": "0.3.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.8", + "@backstage/backend-common": "^0.8.9", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", "@backstage/plugin-kubernetes-common": "^0.1.3", @@ -53,7 +53,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.7", + "@backstage/cli": "^0.7.8", "@types/aws4": "^1.5.1", "supertest": "^6.1.3", "aws-sdk-mock": "^5.2.1", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index d307ec2ced..c3f3b292a9 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes +## 0.4.11 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.4.10 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 7e4a409c4b..b86ce4b85b 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.4.10", + "version": "0.4.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/plugin-kubernetes-common": "^0.1.3", "@backstage/theme": "^0.2.9", "@kubernetes/client-node": "^0.15.0", @@ -43,17 +43,17 @@ "@material-ui/lab": "4.0.0-alpha.45", "js-yaml": "^4.0.0", "lodash": "^4.17.21", - "luxon": "^1.26.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index bde0acfa6f..d2ad6ce91e 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-lighthouse +## 0.2.23 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.2.22 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index d9bf3345d5..177c5e2964 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.22", + "version": "0.2.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,9 +33,9 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 2ca36f9897..8baf70cd69 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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 8cad5e31c1..c6be40fc51 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -21,8 +21,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/plugin-catalog-react": "^0.4.1", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index aa2b5cd583..b07d102131 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-pagerduty +## 0.3.11 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.3.10 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index bf3792597b..dc918b2ae9 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-pagerduty", - "version": "0.3.10", + "version": "0.3.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,26 +31,26 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "@types/react": "*", "classnames": "^2.2.6", - "luxon": "1.25.0", + "luxon": "2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 5bb3f2a021..da263c3a2b 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-rollbar +## 0.3.12 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.3.11 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 68eb7f77b3..ff6db11a5e 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.3.11", + "version": "0.3.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md new file mode 100644 index 0000000000..5ed0f15033 --- /dev/null +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -0,0 +1,10 @@ +# @backstage/plugin-scaffolder-backend-module-cookiecutter + +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/plugin-scaffolder-backend@0.15.0 + - @backstage/backend-common@0.8.9 diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index f321d6b45c..252ebb8cb8 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", - "version": "0.1.0", + "version": "0.1.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,10 +19,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.6", + "@backstage/backend-common": "^0.8.9", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.7", - "@backstage/plugin-scaffolder-backend": "^0.14.0", + "@backstage/integration": "^0.6.0", + "@backstage/plugin-scaffolder-backend": "^0.15.0", "@backstage/config": "^0.1.5", "command-exists": "^1.2.9", "fs-extra": "10.0.0", @@ -31,7 +31,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.3", + "@backstage/cli": "^0.7.8", "@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 857667bc6d..ce81cb7c46 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.4 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/plugin-scaffolder-backend@0.15.0 + - @backstage/backend-common@0.8.9 + ## 0.1.3 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 3062136a76..0ee28e4325 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,16 +20,16 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.6", - "@backstage/plugin-scaffolder-backend": "^0.14.0", + "@backstage/backend-common": "^0.8.9", + "@backstage/plugin-scaffolder-backend": "^0.15.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.8", + "@backstage/integration": "^0.6.0", "command-exists": "^1.2.9", "fs-extra": "^9.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.4", + "@backstage/cli": "^0.7.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/command-exists": "^1.2.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index f1ac87be05..eea4a87768 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend +## 0.15.0 + +### Minor Changes + +- e30646aeb: Add Bitbucket workspace and project fields to RepoUrlPicker to support Bitbucket cloud and server + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/backend-common@0.8.9 + - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.1.1 + ## 0.14.2 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 81f86a6f5b..44389c5afe 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.14.2", + "version": "0.15.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,13 +29,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.8", + "@backstage/backend-common": "^0.8.9", "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.9", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.0", + "@backstage/integration": "^0.6.0", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.1", "@gitbeaker/core": "^30.2.0", "@gitbeaker/node": "^30.2.0", "@octokit/rest": "^18.5.3", @@ -57,7 +57,7 @@ "jsonschema": "^1.2.6", "knex": "^0.95.1", "lodash": "^4.17.21", - "luxon": "^1.26.0", + "luxon": "^2.0.2", "morgan": "^1.10.0", "nunjucks": "^3.2.3", "octokit-plugin-create-pull-request": "^3.9.3", @@ -66,8 +66,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/test-utils": "^0.1.15", + "@backstage/cli": "^0.7.8", + "@backstage/test-utils": "^0.1.17", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml new file mode 100644 index 0000000000..87f4d449f5 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template.yaml @@ -0,0 +1,78 @@ +apiVersion: backstage.io/v1beta2 +kind: Template +metadata: + name: bitbucket-demo + title: Test Bitbucket RepoUrlPicker template + description: scaffolder v1beta2 template demo publishing to bitbucket +spec: + owner: backstage/techdocs-core + type: service + + parameters: + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - bitbucket.org + - server.bitbucket.com + - title: Fill in some steps + required: + - name + - owner + properties: + name: + title: Name + type: string + description: Unique name of the component + ui:autofocus: true + ui:options: + rows: 5 + owner: + title: Owner + type: string + description: Owner of the component + ui:field: OwnerPicker + ui:options: + allowedKinds: + - Group + + steps: + - id: fetch-base + name: Fetch Base + action: fetch:cookiecutter + input: + url: ./template + values: + name: '{{ parameters.name }}' + owner: '{{ parameters.owner }}' + + - id: fetch-docs + name: Fetch Docs + action: fetch:plain + input: + targetPath: ./community + url: https://github.com/backstage/community/tree/main/backstage-community-sessions + + - id: publish + name: Publish + action: publish:bitbucket + input: + description: 'This is {{ parameters.name }}' + repoUrl: '{{ parameters.repoUrl }}' + + - id: register + name: Register + action: catalog:register + input: + repoContentsUrl: '{{ steps.publish.output.repoContentsUrl }}' + catalogInfoPath: '/catalog-info.yaml' + + output: + remoteUrl: '{{ steps.publish.output.remoteUrl }}' + entityRef: '{{ steps.register.output.entityRef }}' diff --git a/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template/catalog-info.yaml b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template/catalog-info.yaml new file mode 100644 index 0000000000..875664d2a8 --- /dev/null +++ b/plugins/scaffolder-backend/sample-templates/bitbucket-demo/template/catalog-info.yaml @@ -0,0 +1,8 @@ +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: {{cookiecutter.name | jsonify}} +spec: + type: website + lifecycle: experimental + owner: {{cookiecutter.owner | jsonify}} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts index 7f48a155ed..5dfba7776e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts @@ -69,7 +69,13 @@ export function createGithubActionsDispatchAction(options: { async handler(ctx) { const { repoUrl, workflowId, branchOrTagName } = ctx.input; - const { owner, repo, host } = parseRepoUrl(repoUrl); + const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError( + `No owner provided for host: ${host}, and repo ${repo}`, + ); + } ctx.logger.info( `Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts index 0af8b7b975..cee99e1646 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts @@ -68,21 +68,21 @@ describe('publish:azure', () => { await expect( action.handler({ ...mockContext, - input: { repoUrl: 'azure.com?repo=bob' }, + input: { repoUrl: 'dev.azure.com?repo=bob' }, }), ).rejects.toThrow(/missing owner/); await expect( action.handler({ ...mockContext, - input: { repoUrl: 'azure.com?owner=owner' }, + input: { repoUrl: 'dev.azure.com?owner=owner' }, }), ).rejects.toThrow(/missing repo/); await expect( action.handler({ ...mockContext, - input: { repoUrl: 'azure.com?owner=owner&repo=repo' }, + input: { repoUrl: 'dev.azure.com?owner=owner&repo=repo' }, }), ).rejects.toThrow(/missing organization/); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts index f5c7879fa9..7e394a8d8b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts @@ -80,7 +80,10 @@ export function createPublishAzureAction(options: { async handler(ctx) { const { repoUrl, defaultBranch = 'master' } = ctx.input; - const { owner, repo, host, organization } = parseRepoUrl(repoUrl); + const { owner, repo, host, organization } = parseRepoUrl( + repoUrl, + integrations, + ); if (!organization) { throw new InputError( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts index 180b6cc579..4eee4b4b96 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts @@ -50,7 +50,7 @@ describe('publish:bitbucket', () => { const action = createPublishBitbucketAction({ integrations, config }); const mockContext = { input: { - repoUrl: 'bitbucket.org?repo=repo&owner=owner', + repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo', repoVisibility: 'private', }, workspacePath: 'lol', @@ -70,14 +70,21 @@ describe('publish:bitbucket', () => { await expect( action.handler({ ...mockContext, - input: { repoUrl: 'bitbucket.com?repo=bob' }, + input: { repoUrl: 'bitbucket.org?project=project&repo=repo' }, }), - ).rejects.toThrow(/missing owner/); + ).rejects.toThrow(/missing workspace/); await expect( action.handler({ ...mockContext, - input: { repoUrl: 'bitbucket.com?owner=owner' }, + input: { repoUrl: 'bitbucket.org?workspace=workspace&repo=repo' }, + }), + ).rejects.toThrow(/missing project/); + + await expect( + action.handler({ + ...mockContext, + input: { repoUrl: 'bitbucket.org?workspace=workspace&project=project' }, }), ).rejects.toThrow(/missing repo/); }); @@ -86,7 +93,9 @@ describe('publish:bitbucket', () => { await expect( action.handler({ ...mockContext, - input: { repoUrl: 'missing.com?repo=bob&owner=owner' }, + input: { + repoUrl: 'missing.com?workspace=workspace&project=project&repo=repo', + }, }), ).rejects.toThrow(/No matching integration configuration/); }); @@ -96,7 +105,8 @@ describe('publish:bitbucket', () => { action.handler({ ...mockContext, input: { - repoUrl: 'notoken.bitbucket.com?repo=bob&owner=owner', + repoUrl: + 'notoken.bitbucket.com?workspace=workspace&project=project&repo=repo', }, }), ).rejects.toThrow(/Authorization has not been provided for Bitbucket/); @@ -106,22 +116,26 @@ describe('publish:bitbucket', () => { expect.assertions(2); server.use( rest.post( - 'https://api.bitbucket.org/2.0/repositories/owner/repo', + 'https://api.bitbucket.org/2.0/repositories/workspace/repo', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer tokenlols'); - expect(req.body).toEqual({ is_private: true, scm: 'git' }); + expect(req.body).toEqual({ + is_private: true, + scm: 'git', + project: { key: 'project' }, + }); return res( ctx.status(200), ctx.set('Content-Type', 'application/json'), ctx.json({ links: { html: { - href: 'https://bitbucket.org/owner/repo', + href: 'https://bitbucket.org/workspace/repo', }, clone: [ { name: 'https', - href: 'https://bitbucket.org/owner/repo', + href: 'https://bitbucket.org/workspace/repo', }, ], }, @@ -131,14 +145,20 @@ describe('publish:bitbucket', () => { ), ); - await action.handler(mockContext); + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + repoUrl: 'bitbucket.org?workspace=workspace&project=project&repo=repo', + }, + }); }); it('should call the correct APIs when the host is hosted bitbucket', async () => { expect.assertions(2); server.use( rest.post( - 'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos', + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer thing'); expect(req.body).toEqual({ public: false, name: 'repo' }); @@ -169,7 +189,7 @@ describe('publish:bitbucket', () => { ...mockContext, input: { ...mockContext.input, - repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo', + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', }, }); }); @@ -195,7 +215,7 @@ describe('publish:bitbucket', () => { expect.assertions(1); server.use( rest.post( - 'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos', + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (_, res, ctx) => { return res( ctx.status(201), @@ -205,7 +225,7 @@ describe('publish:bitbucket', () => { }, ), rest.put( - 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/owner/repos/repo/enabled', + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer thing'); return res(ctx.status(204)); @@ -217,7 +237,7 @@ describe('publish:bitbucket', () => { ...mockContext, input: { ...mockContext.input, - repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo', + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', enableLFS: true, }, }); @@ -226,7 +246,7 @@ describe('publish:bitbucket', () => { it('should report an error if enabling LFS fails', async () => { server.use( rest.post( - 'https://hosted.bitbucket.com/rest/api/1.0/projects/owner/repos', + 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (_, res, ctx) => { return res( ctx.status(201), @@ -236,7 +256,7 @@ describe('publish:bitbucket', () => { }, ), rest.put( - 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/owner/repos/repo/enabled', + 'https://hosted.bitbucket.com/rest/git-lfs/admin/projects/project/repos/repo/enabled', (_, res, ctx) => { return res(ctx.status(500)); }, @@ -248,7 +268,7 @@ describe('publish:bitbucket', () => { ...mockContext, input: { ...mockContext.input, - repoUrl: 'hosted.bitbucket.com?owner=owner&repo=repo', + repoUrl: 'hosted.bitbucket.com?project=project&repo=repo', enableLFS: true, }, }), @@ -259,7 +279,7 @@ describe('publish:bitbucket', () => { it('should call initAndPush with the correct values', async () => { server.use( rest.post( - 'https://api.bitbucket.org/2.0/repositories/owner/repo', + 'https://api.bitbucket.org/2.0/repositories/workspace/repo', (_, res, ctx) => res( ctx.status(200), @@ -267,12 +287,12 @@ describe('publish:bitbucket', () => { ctx.json({ links: { html: { - href: 'https://bitbucket.org/owner/repo', + href: 'https://bitbucket.org/workspace/repo', }, clone: [ { name: 'https', - href: 'https://bitbucket.org/owner/cloneurl', + href: 'https://bitbucket.org/workspace/cloneurl', }, ], }, @@ -285,7 +305,7 @@ describe('publish:bitbucket', () => { expect(initRepoAndPush).toHaveBeenCalledWith({ dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/owner/cloneurl', + remoteUrl: 'https://bitbucket.org/workspace/cloneurl', defaultBranch: 'master', auth: { username: 'x-token-auth', password: 'tokenlols' }, logger: mockContext.logger, @@ -296,7 +316,7 @@ describe('publish:bitbucket', () => { it('should call initAndPush with the correct default branch', async () => { server.use( rest.post( - 'https://api.bitbucket.org/2.0/repositories/owner/repo', + 'https://api.bitbucket.org/2.0/repositories/workspace/repo', (_, res, ctx) => res( ctx.status(200), @@ -304,12 +324,12 @@ describe('publish:bitbucket', () => { ctx.json({ links: { html: { - href: 'https://bitbucket.org/owner/repo', + href: 'https://bitbucket.org/workspace/repo', }, clone: [ { name: 'https', - href: 'https://bitbucket.org/owner/cloneurl', + href: 'https://bitbucket.org/workspace/cloneurl', }, ], }, @@ -328,7 +348,7 @@ describe('publish:bitbucket', () => { expect(initRepoAndPush).toHaveBeenCalledWith({ dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/owner/cloneurl', + remoteUrl: 'https://bitbucket.org/workspace/cloneurl', defaultBranch: 'main', auth: { username: 'x-token-auth', password: 'tokenlols' }, logger: mockContext.logger, @@ -371,7 +391,7 @@ describe('publish:bitbucket', () => { server.use( rest.post( - 'https://api.bitbucket.org/2.0/repositories/owner/repo', + 'https://api.bitbucket.org/2.0/repositories/workspace/repo', (_, res, ctx) => res( ctx.status(200), @@ -379,12 +399,12 @@ describe('publish:bitbucket', () => { ctx.json({ links: { html: { - href: 'https://bitbucket.org/owner/repo', + href: 'https://bitbucket.org/workspace/repo', }, clone: [ { name: 'https', - href: 'https://bitbucket.org/owner/cloneurl', + href: 'https://bitbucket.org/workspace/cloneurl', }, ], }, @@ -397,7 +417,7 @@ describe('publish:bitbucket', () => { expect(initRepoAndPush).toHaveBeenCalledWith({ dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/owner/cloneurl', + remoteUrl: 'https://bitbucket.org/workspace/cloneurl', auth: { username: 'x-token-auth', password: 'tokenlols' }, logger: mockContext.logger, defaultBranch: 'master', @@ -437,7 +457,7 @@ describe('publish:bitbucket', () => { server.use( rest.post( - 'https://api.bitbucket.org/2.0/repositories/owner/repo', + 'https://api.bitbucket.org/2.0/repositories/workspace/repo', (_, res, ctx) => res( ctx.status(200), @@ -445,12 +465,12 @@ describe('publish:bitbucket', () => { ctx.json({ links: { html: { - href: 'https://bitbucket.org/owner/repo', + href: 'https://bitbucket.org/workspace/repo', }, clone: [ { name: 'https', - href: 'https://bitbucket.org/owner/cloneurl', + href: 'https://bitbucket.org/workspace/cloneurl', }, ], }, @@ -463,7 +483,7 @@ describe('publish:bitbucket', () => { expect(initRepoAndPush).toHaveBeenCalledWith({ dir: mockContext.workspacePath, - remoteUrl: 'https://bitbucket.org/owner/cloneurl', + remoteUrl: 'https://bitbucket.org/workspace/cloneurl', auth: { username: 'x-token-auth', password: 'tokenlols' }, logger: mockContext.logger, defaultBranch: 'master', @@ -475,7 +495,7 @@ describe('publish:bitbucket', () => { it('should call outputs with the correct urls', async () => { server.use( rest.post( - 'https://api.bitbucket.org/2.0/repositories/owner/repo', + 'https://api.bitbucket.org/2.0/repositories/workspace/repo', (_, res, ctx) => res( ctx.status(200), @@ -483,12 +503,12 @@ describe('publish:bitbucket', () => { ctx.json({ links: { html: { - href: 'https://bitbucket.org/owner/repo', + href: 'https://bitbucket.org/workspace/repo', }, clone: [ { name: 'https', - href: 'https://bitbucket.org/owner/cloneurl', + href: 'https://bitbucket.org/workspace/cloneurl', }, ], }, @@ -501,11 +521,11 @@ describe('publish:bitbucket', () => { expect(mockContext.output).toHaveBeenCalledWith( 'remoteUrl', - 'https://bitbucket.org/owner/cloneurl', + 'https://bitbucket.org/workspace/cloneurl', ); expect(mockContext.output).toHaveBeenCalledWith( 'repoContentsUrl', - 'https://bitbucket.org/owner/repo/src/master', + 'https://bitbucket.org/workspace/repo/src/master', ); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts index 41d71c9ab5..daa6bc249b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts @@ -26,13 +26,21 @@ import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { Config } from '@backstage/config'; const createBitbucketCloudRepository = async (opts: { - owner: string; + workspace: string; + project: string; repo: string; description: string; repoVisibility: 'private' | 'public'; authorization: string; }) => { - const { owner, repo, description, repoVisibility, authorization } = opts; + const { + workspace, + project, + repo, + description, + repoVisibility, + authorization, + } = opts; const options: RequestInit = { method: 'POST', @@ -40,6 +48,7 @@ const createBitbucketCloudRepository = async (opts: { scm: 'git', description: description, is_private: repoVisibility === 'private', + project: { key: project }, }), headers: { Authorization: authorization, @@ -50,7 +59,7 @@ const createBitbucketCloudRepository = async (opts: { let response: Response; try { response = await fetch( - `https://api.bitbucket.org/2.0/repositories/${owner}/${repo}`, + `https://api.bitbucket.org/2.0/repositories/${workspace}/${repo}`, options, ); } catch (e) { @@ -80,7 +89,7 @@ const createBitbucketCloudRepository = async (opts: { const createBitbucketServerRepository = async (opts: { host: string; - owner: string; + project: string; repo: string; description: string; repoVisibility: 'private' | 'public'; @@ -89,7 +98,7 @@ const createBitbucketServerRepository = async (opts: { }) => { const { host, - owner, + project, repo, description, authorization, @@ -113,7 +122,7 @@ const createBitbucketServerRepository = async (opts: { try { const baseUrl = apiBaseUrl ? apiBaseUrl : `https://${host}/rest/api/1.0`; - response = await fetch(`${baseUrl}/projects/${owner}/repos`, options); + response = await fetch(`${baseUrl}/projects/${project}/repos`, options); } catch (e) { throw new Error(`Unable to create repository, ${e}`); } @@ -160,10 +169,10 @@ const getAuthorizationHeader = (config: BitbucketIntegrationConfig) => { const performEnableLFS = async (opts: { authorization: string; host: string; - owner: string; + project: string; repo: string; }) => { - const { authorization, host, owner, repo } = opts; + const { authorization, host, project, repo } = opts; const options: RequestInit = { method: 'PUT', @@ -173,7 +182,7 @@ const performEnableLFS = async (opts: { }; const { ok, status, statusText } = await fetch( - `https://${host}/rest/git-lfs/admin/projects/${owner}/repos/${repo}/enabled`, + `https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`, options, ); @@ -258,7 +267,26 @@ export function createPublishBitbucketAction(options: { enableLFS = false, } = ctx.input; - const { owner, repo, host } = parseRepoUrl(repoUrl); + const { workspace, project, repo, host } = parseRepoUrl( + repoUrl, + integrations, + ); + + // Workspace is only required for bitbucket cloud + if (host === 'bitbucket.org') { + if (!workspace) { + throw new InputError( + `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`, + ); + } + } + + // Project is required for both bitbucket cloud and bitbucket server + if (!project) { + throw new InputError( + `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`, + ); + } const integrationConfig = integrations.bitbucket.byHost(host); @@ -279,7 +307,8 @@ export function createPublishBitbucketAction(options: { const { remoteUrl, repoContentsUrl } = await createMethod({ authorization, host, - owner, + workspace: workspace || '', + project, repo, repoVisibility, description, @@ -311,7 +340,7 @@ export function createPublishBitbucketAction(options: { }); if (enableLFS && host !== 'bitbucket.org') { - await performEnableLFS({ authorization, host, owner, repo }); + await performEnableLFS({ authorization, host, project, repo }); } ctx.output('remoteUrl', remoteUrl); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index 640ae5dfe5..f767d6d38c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -144,7 +144,13 @@ export function createPublishGithubAction(options: { topics, } = ctx.input; - const { owner, repo, host } = parseRepoUrl(repoUrl); + const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError( + `No owner provided for host: ${host}, and repo ${repo}`, + ); + } const credentialsProvider = credentialsProviders.get(host); const integrationConfig = integrations.github.byHost(host); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index 408ec243e7..e21ffc5cd0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -173,7 +173,13 @@ export const createPublishGithubPullRequestAction = ({ sourcePath, } = ctx.input; - const { owner, repo, host } = parseRepoUrl(repoUrl); + const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError( + `No owner provided for host: ${host}, and repo ${repo}`, + ); + } const client = await clientFactory({ integrations, host, owner, repo }); const fileRoot = sourcePath diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts index fbd2dcaaae..eead310777 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts @@ -84,7 +84,13 @@ export function createPublishGitlabAction(options: { defaultBranch = 'master', } = ctx.input; - const { owner, repo, host } = parseRepoUrl(repoUrl); + const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError( + `No owner provided for host: ${host}, and repo ${repo}`, + ); + } const integrationConfig = integrations.gitlab.byHost(host); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts index e7d4e75f6a..dea180dc04 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts @@ -16,6 +16,7 @@ import { InputError } from '@backstage/errors'; import { join as joinPath, normalize as normalizePath } from 'path'; +import { ScmIntegrationRegistry } from '@backstage/integration'; export const getRepoSourceDirectory = ( workspacePath: string, @@ -33,11 +34,16 @@ export const getRepoSourceDirectory = ( export type RepoSpec = { repo: string; host: string; - owner: string; + owner?: string; organization?: string; + workspace?: string; + project?: string; }; -export const parseRepoUrl = (repoUrl: string): RepoSpec => { +export const parseRepoUrl = ( + repoUrl: string, + integrations: ScmIntegrationRegistry, +): RepoSpec => { let parsed; try { parsed = new URL(`https://${repoUrl}`); @@ -47,13 +53,40 @@ export const parseRepoUrl = (repoUrl: string): RepoSpec => { ); } const host = parsed.host; - const owner = parsed.searchParams.get('owner'); + const owner = parsed.searchParams.get('owner') ?? undefined; + const organization = parsed.searchParams.get('organization') ?? undefined; + const workspace = parsed.searchParams.get('workspace') ?? undefined; + const project = parsed.searchParams.get('project') ?? undefined; - if (!owner) { + const type = integrations.byHost(host)?.type; + + if (!type) { throw new InputError( - `Invalid repo URL passed to publisher: ${repoUrl}, missing owner`, + `No matching integration configuration for host ${host}, please check your integrations config`, ); } + + if (type === 'bitbucket') { + if (host === 'bitbucket.org') { + if (!workspace) { + throw new InputError( + `Invalid repo URL passed to publisher: ${repoUrl}, missing workspace`, + ); + } + } + if (!project) { + throw new InputError( + `Invalid repo URL passed to publisher: ${repoUrl}, missing project`, + ); + } + } else { + if (!owner) { + throw new InputError( + `Invalid repo URL passed to publisher: ${repoUrl}, missing owner`, + ); + } + } + const repo = parsed.searchParams.get('repo'); if (!repo) { throw new InputError( @@ -61,7 +94,5 @@ export const parseRepoUrl = (repoUrl: string): RepoSpec => { ); } - const organization = parsed.searchParams.get('organization') ?? undefined; - - return { host, owner, repo, organization }; + return { host, owner, repo, organization, workspace, project }; }; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index b5e8d19e62..2c274df7fc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -22,6 +22,7 @@ import { RepoSpec } from '../actions/builtin/publish/util'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker } from './StorageTaskBroker'; import { TaskWorker } from './TaskWorker'; +import { ScmIntegrations } from '@backstage/integration'; async function createStore(): Promise { const manager = DatabaseManager.fromConfig( @@ -41,6 +42,14 @@ describe('TaskWorker', () => { let storage: DatabaseTaskStore; let actionRegistry = new TemplateActionRegistry(); + const integrations = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + github: [{ host: 'github.com', token: 'token' }], + }, + }), + ); + beforeAll(async () => { storage = await createStore(); }); @@ -65,6 +74,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], @@ -90,6 +100,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ @@ -142,6 +153,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ @@ -177,6 +189,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ @@ -210,6 +223,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ @@ -243,6 +257,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ @@ -331,6 +346,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ @@ -388,6 +404,12 @@ describe('TaskWorker', () => { organization: { type: 'string', }, + workspace: { + type: 'string', + }, + project: { + type: 'string', + }, }, }, }, @@ -396,7 +418,10 @@ describe('TaskWorker', () => { async handler(ctx) { ctx.output('host', ctx.input.destination.host); ctx.output('repo', ctx.input.destination.repo); - ctx.output('owner', ctx.input.destination.owner); + + if (ctx.input.destination.owner) { + ctx.output('owner', ctx.input.destination.owner); + } if (ctx.input.destination.host !== 'github.com') { throw new Error( @@ -410,7 +435,10 @@ describe('TaskWorker', () => { ); } - if (ctx.input.destination.owner !== 'owner') { + if ( + ctx.input.destination.owner && + ctx.input.destination.owner !== 'owner' + ) { throw new Error( `expected repo to be "owner" got ${ctx.input.destination.owner}`, ); @@ -425,6 +453,7 @@ describe('TaskWorker', () => { workingDirectory: os.tmpdir(), actionRegistry, taskBroker: broker, + integrations, }); const { taskId } = await broker.dispatch({ diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index b4ba936719..819df56109 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -27,12 +27,14 @@ import { parseRepoUrl } from '../actions/builtin/publish/util'; import { TemplateActionRegistry } from '../actions/TemplateActionRegistry'; import { isTruthy } from './helper'; import { Task, TaskBroker } from './types'; +import { ScmIntegrations } from '@backstage/integration'; type Options = { logger: Logger; taskBroker: TaskBroker; workingDirectory: string; actionRegistry: TemplateActionRegistry; + integrations: ScmIntegrations; }; export class TaskWorker { @@ -45,11 +47,11 @@ export class TaskWorker { // scary right now, so we're going to lock it off like the component API is // in the frontend until we can work out a nice way to do it. this.handlebars.registerHelper('parseRepoUrl', repoUrl => { - return JSON.stringify(parseRepoUrl(repoUrl)); + return JSON.stringify(parseRepoUrl(repoUrl, options.integrations)); }); this.handlebars.registerHelper('projectSlug', repoUrl => { - const { owner, repo } = parseRepoUrl(repoUrl); + const { owner, repo } = parseRepoUrl(repoUrl, options.integrations); return `${owner}/${repo}`; }); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index cee9d2cfb4..f2a9c5b708 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -90,6 +90,7 @@ export async function createRouter( taskBroker, actionRegistry, workingDirectory, + integrations, }); workers.push(worker); } diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 859407ec88..d97ce8e54c 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-scaffolder +## 0.11.0 + +### Minor Changes + +- e30646aeb: Add Bitbucket workspace and project fields to RepoUrlPicker to support Bitbucket cloud and server + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- 7894421f1: Added UI Schema support for array items for example, support EntityPicker within an array field +- fa84fe44e: - Adds a new field `EntityNamePicker` that can be used in scaffolder templates to accept and validate an entity name. This field is registered by default, and can be used in templates by setting the `ui:field` property to `EntityNamePicker`. If you've customized your scaffolder field extensions, you can include this one by adding it when registering the scaffolder route: + + ```diff + import { + ScaffolderFieldExtensions, + + EntityNamePickerFieldExtension, + } from '@backstage/plugin-scaffolder'; + + }> + + {/* ...custom field extensions... */} + + + + + ; + ``` + + - Adds a new generic field `TextValuePicker` to be used when writing custom field extensions that use a standard UI with custom validation. An example of doing this can be found in `packages/app/src/components/scaffolder/customScaffolderExtensions.tsx`. + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + - @backstage/integration-react@0.1.7 + ## 0.10.3 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 9227015bb0..84720e95a8 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.10.3", + "version": "0.11.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "@backstage/catalog-client": "^0.3.18", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.9", - "@backstage/integration-react": "^0.1.6", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/integration": "^0.6.0", + "@backstage/integration-react": "^0.1.7", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -52,7 +52,7 @@ "immer": "^9.0.1", "json-schema": "^0.3.0", "lodash": "^4.17.21", - "luxon": "^1.25.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-lazylog": "^4.5.2", @@ -63,10 +63,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 4287d1147b..f862fa66fe 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -16,6 +16,7 @@ import React, { useCallback, useEffect } from 'react'; import { FieldProps } from '@rjsf/core'; import { scaffolderApiRef } from '../../../api'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { useAsync } from 'react-use'; import Select from '@material-ui/core/Select'; import InputLabel from '@material-ui/core/InputLabel'; @@ -31,6 +32,8 @@ function splitFormData(url: string | undefined) { let owner = undefined; let repo = undefined; let organization = undefined; + let workspace = undefined; + let project = undefined; try { if (url) { @@ -40,12 +43,15 @@ function splitFormData(url: string | undefined) { repo = parsed.searchParams.get('repo') || undefined; // This is azure dev ops specific. not used for any other provider. organization = parsed.searchParams.get('organization') || undefined; + // These are bitbucket specific, not used for any other provider. + workspace = parsed.searchParams.get('workspace') || undefined; + project = parsed.searchParams.get('project') || undefined; } } catch { /* ok */ } - return { host, owner, repo, organization }; + return { host, owner, repo, organization, workspace, project }; } function serializeFormData(data: { @@ -53,10 +59,13 @@ function serializeFormData(data: { owner?: string; repo?: string; organization?: string; + workspace?: string; + project?: string; }) { if (!data.host) { return undefined; } + const params = new URLSearchParams(); if (data.owner) { params.set('owner', data.owner); @@ -67,6 +76,12 @@ function serializeFormData(data: { if (data.organization) { params.set('organization', data.organization); } + if (data.workspace) { + params.set('workspace', data.workspace); + } + if (data.project) { + params.set('project', data.project); + } return `${data.host}?${params.toString()}`; } @@ -77,25 +92,30 @@ export const RepoUrlPicker = ({ rawErrors, formData, }: FieldProps) => { - const api = useApi(scaffolderApiRef); + const scaffolderApi = useApi(scaffolderApiRef); + const integrationApi = useApi(scmIntegrationsApiRef); const allowedHosts = uiSchema['ui:options']?.allowedHosts as string[]; const { value: integrations, loading } = useAsync(async () => { - return await api.getIntegrationsList({ allowedHosts }); + return await scaffolderApi.getIntegrationsList({ allowedHosts }); }); - const { host, owner, repo, organization } = splitFormData(formData); + const { host, owner, repo, organization, workspace, project } = + splitFormData(formData); const updateHost = useCallback( - (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => + (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => { onChange( serializeFormData({ host: evt.target.value as string, owner, repo, organization, + workspace, + project, }), - ), - [onChange, owner, repo, organization], + ); + }, + [onChange, owner, repo, organization, workspace, project], ); const updateOwner = useCallback( @@ -106,9 +126,11 @@ export const RepoUrlPicker = ({ owner: evt.target.value as string, repo, organization, + workspace, + project, }), ), - [onChange, host, repo, organization], + [onChange, host, repo, organization, workspace, project], ); const updateRepo = useCallback( @@ -119,9 +141,11 @@ export const RepoUrlPicker = ({ owner, repo: evt.target.value as string, organization, + workspace, + project, }), ), - [onChange, host, owner, organization], + [onChange, host, owner, organization, workspace, project], ); const updateOrganization = useCallback( @@ -132,9 +156,41 @@ export const RepoUrlPicker = ({ owner, repo, organization: evt.target.value as string, + workspace, + project, }), ), - [onChange, host, owner, repo], + [onChange, host, owner, repo, workspace, project], + ); + + const updateWorkspace = useCallback( + (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => + onChange( + serializeFormData({ + host, + owner, + repo, + organization, + workspace: evt.target.value as string, + project, + }), + ), + [onChange, host, owner, repo, organization, project], + ); + + const updateProject = useCallback( + (evt: React.ChangeEvent<{ name?: string; value: unknown }>) => + onChange( + serializeFormData({ + host, + owner, + repo, + organization, + workspace, + project: evt.target.value as string, + }), + ), + [onChange, host, owner, repo, organization, workspace], ); useEffect(() => { @@ -145,10 +201,21 @@ export const RepoUrlPicker = ({ owner, repo, organization, + workspace, + project, }), ); } - }, [onChange, integrations, host, owner, repo, organization]); + }, [ + onChange, + integrations, + host, + owner, + repo, + organization, + workspace, + project, + ]); if (loading) { return ; @@ -179,6 +246,7 @@ export const RepoUrlPicker = ({ The host where the repository will be created + {/* Show this for dev.azure.com only */} {host === 'dev.azure.com' && ( The name of the organization )} - 0 && !owner} - > - Owner - - - The organization, user or project that this repo will belong to - - + {host && integrationApi.byHost(host)?.type === 'bitbucket' && ( + <> + {/* Show this for bitbucket.org only */} + {host === 'bitbucket.org' && ( + 0 && !workspace} + > + Workspace + + + The workspace where the repository will be created + + + )} + 0 && !project} + > + Project + + + The project where the repository will be created + + + + )} + {/* Show this for all hosts except bitbucket */} + {host && integrationApi.byHost(host)?.type !== 'bitbucket' && ( + <> + 0 && !owner} + > + Owner + + + The organization, user or project that this repo will belong to + + + + )} + {/* Show this for all hosts */} { const fieldValidator = () => @@ -23,30 +26,60 @@ describe('RepoPicker Validation', () => { addError: jest.fn(), } as unknown as FieldValidation); + const config = new ConfigReader({ + integrations: { + bitbucket: [ + { + host: 'bitbucket.org', + }, + { + host: 'server.bitbucket.com', + }, + ], + github: [ + { + host: 'github.com', + }, + ], + }, + }); + + const scmIntegrations = ScmIntegrations.fromConfig(config); + + const apiHolderMock: jest.Mocked = { + get: jest.fn().mockImplementation(() => { + return scmIntegrations; + }), + }; + it('validates when no repo', () => { const mockFieldValidation = fieldValidator(); - repoPickerValidation('github.com?owner=a', mockFieldValidation); + repoPickerValidation('github.com?owner=a', mockFieldValidation, { + apiHolder: apiHolderMock, + }); expect(mockFieldValidation.addError).toHaveBeenCalledWith( - 'Incomplete repository location provided', + 'Incomplete repository location provided, repo not provided', ); }); it('validates when no owner', () => { const mockFieldValidation = fieldValidator(); - repoPickerValidation('github.com?repo=a', mockFieldValidation); + repoPickerValidation('github.com?repo=a', mockFieldValidation, { + apiHolder: apiHolderMock, + }); expect(mockFieldValidation.addError).toHaveBeenCalledWith( - 'Incomplete repository location provided', + 'Incomplete repository location provided, owner not provided', ); }); it('validates when not a real url', () => { const mockFieldValidation = fieldValidator(); - repoPickerValidation('', mockFieldValidation); + repoPickerValidation('', mockFieldValidation, { apiHolder: apiHolderMock }); expect(mockFieldValidation.addError).toHaveBeenCalledWith( 'Unable to parse the Repository URL', @@ -56,8 +89,116 @@ describe('RepoPicker Validation', () => { it('validates properly with proper input', () => { const mockFieldValidation = fieldValidator(); - repoPickerValidation('github.com?owner=a&repo=b', mockFieldValidation); + repoPickerValidation('github.com?owner=a&repo=b', mockFieldValidation, { + apiHolder: apiHolderMock, + }); expect(mockFieldValidation.addError).not.toHaveBeenCalled(); }); + + it('validates when no workspace, project or repo provided for bitbucket cloud', () => { + const mockFieldValidation = fieldValidator(); + + repoPickerValidation('bitbucket.org', mockFieldValidation, { + apiHolder: apiHolderMock, + }); + + expect(mockFieldValidation.addError).toHaveBeenNthCalledWith( + 1, + 'Incomplete repository location provided, workspace not provided', + ); + expect(mockFieldValidation.addError).toHaveBeenNthCalledWith( + 2, + 'Incomplete repository location provided, project not provided', + ); + expect(mockFieldValidation.addError).toHaveBeenNthCalledWith( + 3, + 'Incomplete repository location provided, repo not provided', + ); + }); + + it('validates when no workspace provided for bitbucket cloud', () => { + const mockFieldValidation = fieldValidator(); + + repoPickerValidation( + 'bitbucket.org?project=p&repo=r', + mockFieldValidation, + { apiHolder: apiHolderMock }, + ); + + expect(mockFieldValidation.addError).toHaveBeenCalledWith( + 'Incomplete repository location provided, workspace not provided', + ); + }); + + it('validates when no project provided for bitbucket cloud', () => { + const mockFieldValidation = fieldValidator(); + + repoPickerValidation( + 'bitbucket.org?workspace=w&repo=r', + mockFieldValidation, + { apiHolder: apiHolderMock }, + ); + + expect(mockFieldValidation.addError).toHaveBeenCalledWith( + 'Incomplete repository location provided, project not provided', + ); + }); + + it('validates when no repo provided for bitbucket cloud', () => { + const mockFieldValidation = fieldValidator(); + + repoPickerValidation( + 'bitbucket.org?workspace=w&project=p', + mockFieldValidation, + { apiHolder: apiHolderMock }, + ); + + expect(mockFieldValidation.addError).toHaveBeenCalledWith( + 'Incomplete repository location provided, repo not provided', + ); + }); + + it('validates when no project or repo provided for bitbucket server', () => { + const mockFieldValidation = fieldValidator(); + + repoPickerValidation('server.bitbucket.com', mockFieldValidation, { + apiHolder: apiHolderMock, + }); + + expect(mockFieldValidation.addError).toHaveBeenNthCalledWith( + 1, + 'Incomplete repository location provided, project not provided', + ); + expect(mockFieldValidation.addError).toHaveBeenNthCalledWith( + 2, + 'Incomplete repository location provided, repo not provided', + ); + }); + + it('validates when no project provided for bitbucket server', () => { + const mockFieldValidation = fieldValidator(); + + repoPickerValidation('server.bitbucket.com?repo=r', mockFieldValidation, { + apiHolder: apiHolderMock, + }); + + expect(mockFieldValidation.addError).toHaveBeenCalledWith( + 'Incomplete repository location provided, project not provided', + ); + }); + + it('validates when no repo provided for bitbucket server', () => { + const mockFieldValidation = fieldValidator(); + + repoPickerValidation( + 'server.bitbucket.com?project=p', + mockFieldValidation, + { apiHolder: apiHolderMock }, + ); + + expect(mockFieldValidation.addError).toHaveBeenCalledWith( + 'Incomplete repository location provided, repo not provided', + ); + }); }); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts index 760b9f7890..24a1c2f5ce 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/validation.ts @@ -15,15 +15,53 @@ */ import { FieldValidation } from '@rjsf/core'; +import { ApiHolder } from '@backstage/core-plugin-api'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; export const repoPickerValidation = ( value: string, validation: FieldValidation, + context: { apiHolder: ApiHolder }, ) => { try { const { host, searchParams } = new URL(`https://${value}`); - if (!host || !searchParams.get('owner') || !searchParams.get('repo')) { - validation.addError('Incomplete repository location provided'); + + const integrationApi = context.apiHolder.get(scmIntegrationsApiRef); + + if (!host) { + validation.addError( + 'Incomplete repository location provided, host not provided', + ); + } else { + if (integrationApi?.byHost(host)?.type === 'bitbucket') { + // workspace is only applicable for bitbucket cloud + if (host === 'bitbucket.org' && !searchParams.get('workspace')) { + validation.addError( + 'Incomplete repository location provided, workspace not provided', + ); + } + + if (!searchParams.get('project')) { + validation.addError( + 'Incomplete repository location provided, project not provided', + ); + } + } + // For anything other than bitbucket + else { + if (!searchParams.get('owner')) { + validation.addError( + 'Incomplete repository location provided, owner not provided', + ); + } + } + + // Do this for all hosts + if (!searchParams.get('repo')) { + validation.addError( + 'Incomplete repository location provided, repo not provided', + ); + } } } catch { validation.addError('Unable to parse the Repository URL'); diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md new file mode 100644 index 0000000000..6dd34513f0 --- /dev/null +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -0,0 +1,7 @@ +# @backstage/plugin-search-backend-module-elasticsearch + +## 0.0.2 + +### Patch Changes + +- 76872096b: Fix to allow optionally reading `auth` parameter for custom hosted ElasticSearch instances. Also remove `bearer` auth config since it's currently unsupported. diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index dd9ca7703c..de55d3952e 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", - "version": "0.0.1", + "version": "0.0.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,8 +29,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-common": "^0.8.6", - "@backstage/cli": "^0.7.4", + "@backstage/backend-common": "^0.8.9", + "@backstage/cli": "^0.7.8", "@elastic/elasticsearch-mock": "^0.3.0" }, "files": [ diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md new file mode 100644 index 0000000000..1607585348 --- /dev/null +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -0,0 +1,10 @@ +# @backstage/plugin-search-backend-module-pg + +## 0.1.1 + +### Patch Changes + +- 9255e1430: Add `plugin-search-backend-module-pg` providing a postgres based search engine. + See the [README of `search-backend-module-pg`](https://github.com/backstage/backstage/blob/master/plugins/search-backend-module-pg/README.md) for usage instructions. +- Updated dependencies + - @backstage/backend-common@0.8.9 diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 9463e5c5a9..3b7fe0b28f 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-module-pg", - "version": "0.1.0", + "version": "0.1.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,15 +19,15 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.7", + "@backstage/backend-common": "^0.8.9", "@backstage/search-common": "^0.1.2", "@backstage/plugin-search-backend-node": "^0.4.0", "lodash": "^4.17.15", "knex": "^0.95.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.4", - "@backstage/cli": "^0.7.6" + "@backstage/backend-test-utils": "^0.1.5", + "@backstage/cli": "^0.7.8" }, "files": [ "dist", diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 81872df49e..0864dd740e 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search +## 0.4.7 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.4.6 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 1b32c49e75..a535278e93 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.4.6", + "version": "0.4.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/search-common": "^0.1.3", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", @@ -49,10 +49,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index edc81f6f1a..9f5656f1e2 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-sentry +## 0.3.18 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.3.17 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index a288fb6f93..a660a15cd4 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.3.17", + "version": "0.3.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,14 +32,14 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "luxon": "^1.27.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router": "6.0.0-beta.0", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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 898ddb5908..4ac197f589 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -36,10 +36,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index efc4ba3c86..a29ee6365d 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-sonarqube +## 0.2.0 + +### Minor Changes + +- 8db48b968: Use IdentityApi to provide Auth Token for SonarQubeClient Api calls + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.1.24 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index e20826b464..13d375dfae 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube", - "version": "0.1.24", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,9 +33,9 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index 25cad60053..73ba4ee6bd 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-splunk-on-call +## 0.3.8 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog-react@0.4.2 + ## 0.3.7 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 4a78c1591b..f2eff3b739 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-splunk-on-call", - "version": "0.3.7", + "version": "0.3.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,25 +31,25 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", "classnames": "^2.2.6", - "luxon": "^1.25.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index d507ee7746..7b4daca110 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-tech-radar +## 0.4.5 + +### Patch Changes + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + ## 0.4.4 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 0866099e18..55e5385a85 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-radar", - "version": "0.4.4", + "version": "0.4.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -44,10 +44,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index b13e8bfbfa..1f2e5d6bc2 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-techdocs-backend +## 0.9.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/backend-common@0.8.9 + - @backstage/techdocs-common@0.8.1 + ## 0.9.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index ccaf0de227..d08d5141cd 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.9.1", + "version": "0.9.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,14 +30,14 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.8", + "@backstage/backend-common": "^0.8.9", "@backstage/catalog-client": "^0.3.17", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.9", + "@backstage/integration": "^0.6.0", "@backstage/search-common": "^0.1.3", - "@backstage/techdocs-common": "^0.8.0", + "@backstage/techdocs-common": "^0.8.1", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", "dockerode": "^3.2.1", @@ -50,8 +50,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/test-utils": "^0.1.15", + "@backstage/cli": "^0.7.8", + "@backstage/test-utils": "^0.1.17", "@types/dockerode": "^3.2.1", "msw": "^0.29.0", "supertest": "^6.1.3" diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 507dd8693f..0b088d36cd 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-techdocs +## 0.10.4 + +### Patch Changes + +- a440d3b38: Expose a new composable `TechDocsIndexPage` and a `DefaultTechDocsHome` with support for starring docs and filtering on owned, starred, owner, and tags. + + You can migrate to the new UI view by making the following changes in your `App.tsx`: + + ```diff + - } /> + + }> + + + + + + } + + /> + ``` + +- 56c773909: Switched `@types/react` dependency to request `*` rather than a specific version. +- 8a3e46591: Switch `EventSource` implementation with header support from a Node.js API-based one to an XHR-based one. +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + - @backstage/plugin-catalog@0.6.11 + - @backstage/plugin-catalog-react@0.4.2 + - @backstage/integration-react@0.1.7 + ## 0.10.3 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index bb9f8a7c95..ddb223be89 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.10.3", + "version": "0.10.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,13 +33,13 @@ "dependencies": { "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.6", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.9", - "@backstage/integration-react": "^0.1.6", - "@backstage/plugin-catalog": "^0.6.10", - "@backstage/plugin-catalog-react": "^0.4.1", + "@backstage/integration": "^0.6.0", + "@backstage/integration-react": "^0.1.7", + "@backstage/plugin-catalog": "^0.6.11", + "@backstage/plugin-catalog-react": "^0.4.2", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -59,10 +59,10 @@ "git-url-parse": "~11.4.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index cc69ae6c3d..bc27aed097 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-todo-backend +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@0.6.0 + - @backstage/backend-common@0.8.9 + ## 0.1.8 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 61e84f8377..0d59f2b073 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-todo-backend", - "version": "0.1.8", + "version": "0.1.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,12 +24,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.8.5", + "@backstage/backend-common": "^0.8.9", "@backstage/catalog-client": "^0.3.16", "@backstage/catalog-model": "^0.9.0", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/integration": "^0.5.8", + "@backstage/integration": "^0.6.0", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", "express": "^4.17.1", @@ -39,7 +39,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.1", + "@backstage/cli": "^0.7.8", "@types/supertest": "^2.0.8", "msw": "^0.29.0", "supertest": "^6.1.3" diff --git a/plugins/todo/package.json b/plugins/todo/package.json index d26dc79358..6cfe7790db 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -27,8 +27,8 @@ }, "dependencies": { "@backstage/catalog-model": "^0.9.0", - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", "@backstage/plugin-catalog-react": "^0.4.1", "@backstage/theme": "^0.2.9", @@ -40,10 +40,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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 e31209f857..a55390ab86 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,11 +42,11 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", "@backstage/core-plugin-api": "^0.1.5", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 1ad3f4962a..d7e7e0e355 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -30,8 +30,8 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/CHANGELOG.md b/plugins/xcmetrics/CHANGELOG.md index 80a9188aa3..b0123dcdb1 100644 --- a/plugins/xcmetrics/CHANGELOG.md +++ b/plugins/xcmetrics/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-xcmetrics +## 0.2.2 + +### Patch Changes + +- 8bedb75ae: Update Luxon dependency to 2.x +- Updated dependencies + - @backstage/core-components@0.3.1 + - @backstage/core-plugin-api@0.1.6 + ## 0.2.1 ### Patch Changes diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index 6e582d3686..83e57acf70 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-xcmetrics", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,23 +20,24 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-components": "^0.3.0", - "@backstage/core-plugin-api": "^0.1.5", + "@backstage/core-components": "^0.3.1", + "@backstage/core-plugin-api": "^0.1.6", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.9", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", - "luxon": "^1.27.0", + "luxon": "^2.0.2", "react": "^16.13.1", "react-dom": "^16.13.1", - "react-use": "^17.2.4" + "react-use": "^17.2.4", + "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.7", - "@backstage/core-app-api": "^0.1.7", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.1.16", + "@backstage/cli": "^0.7.8", + "@backstage/core-app-api": "^0.1.8", + "@backstage/dev-utils": "^0.2.6", + "@backstage/test-utils": "^0.1.17", "@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/src/api/XcmetricsClient.ts b/plugins/xcmetrics/src/api/XcmetricsClient.ts index 81b3e78a31..37baa949d4 100644 --- a/plugins/xcmetrics/src/api/XcmetricsClient.ts +++ b/plugins/xcmetrics/src/api/XcmetricsClient.ts @@ -20,9 +20,14 @@ import { DateTime } from 'luxon'; import { Build, BuildCount, + BuildError, BuildFilters, + BuildHost, + BuildMetadata, + BuildResponse, BuildStatusResult, BuildTime, + BuildWarning, PaginationResult, XcmetricsApi, } from './types'; @@ -38,25 +43,13 @@ export class XcmetricsClient implements XcmetricsApi { this.discoveryApi = options.discoveryApi; } - async getBuild(id: string): Promise { - const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`; - const response = await fetch(`${baseUrl}/build/${id}`); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return ((await response.json()) as Record<'build', Build>).build; + async getBuild(id: string): Promise { + const response = await this.get(`/build/${id}`); + return (await response.json()) as BuildResponse; } async getBuilds(limit: number = 10): Promise { - const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`; - const response = await fetch(`${baseUrl}/build?per=${limit}`); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - + const response = await this.get(`/build?per=${limit}`); return ((await response.json()) as PaginationResult).items; } @@ -65,80 +58,88 @@ export class XcmetricsClient implements XcmetricsApi { page?: number, perPage?: number, ): Promise> { + const response = await this.post('/build/filter', { + from: DateTime.fromISO(filters.from) + .startOf('day') + .toISO({ suppressMilliseconds: true }), + to: DateTime.fromISO(filters.to) + .endOf('day') + .startOf('second') + .toISO({ suppressMilliseconds: true }), + status: filters.buildStatus, + projectName: filters.project, + page, + per: perPage, + }); + + return (await response.json()) as PaginationResult; + } + + async getBuildCounts(days: number): Promise { + const response = await this.get(`/statistics/build/count?days=${days}`); + return (await response.json()) as BuildCount[]; + } + + async getBuildErrors(buildId: string): Promise { + const response = await this.get(`/build/error/${buildId}`); + return (await response.json()) as BuildError[]; + } + + async getBuildHost(buildId: string): Promise { + const response = await this.get(`/build/host/${buildId}`); + return (await response.json()) as BuildHost; + } + + async getBuildMetadata(buildId: string): Promise { + const response = await this.get(`/build/metadata/${buildId}`); + return ((await response.json()) as Record<'metadata', BuildMetadata>) + .metadata; + } + + async getBuildTimes(days: number): Promise { + const response = await this.get(`/statistics/build/time?days=${days}`); + return (await response.json()) as BuildTime[]; + } + + async getBuildStatuses(limit: number): Promise { + const response = await this.get(`/statistics/build/status?per=${limit}`); + return ((await response.json()) as PaginationResult) + .items; + } + + async getBuildWarnings(buildId: string): Promise { + const response = await this.get(`/build/warning/${buildId}`); + return (await response.json()) as BuildWarning[]; + } + + async getProjects(): Promise { + const response = await this.get('/build/project'); + return (await response.json()) as string[]; + } + + private async get(path: string): Promise { const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`; - const response = await fetch(`${baseUrl}/build/filter`, { + const response = await fetch(`${baseUrl}${path}`); + + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + + return response; + } + + private async post(path: string, body: Object): Promise { + const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`; + const response = await fetch(`${baseUrl}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - from: DateTime.fromISO(filters.from) - .startOf('day') - .toISO({ suppressMilliseconds: true }), - to: DateTime.fromISO(filters.to) - .endOf('day') - .startOf('second') - .toISO({ suppressMilliseconds: true }), - status: filters.buildStatus, - projectName: filters.project, - page, - per: perPage, - }), + body: JSON.stringify(body), }); if (!response.ok) { throw await ResponseError.fromResponse(response); } - return (await response.json()) as PaginationResult; - } - - async getBuildCounts(days: number): Promise { - const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`; - const response = await fetch( - `${baseUrl}/statistics/build/count?days=${days}`, - ); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return (await response.json()) as BuildCount[]; - } - - async getBuildTimes(days: number): Promise { - const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`; - const response = await fetch( - `${baseUrl}/statistics/build/time?days=${days}`, - ); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return (await response.json()) as BuildTime[]; - } - - async getBuildStatuses(limit: number): Promise { - const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`; - const response = await fetch( - `${baseUrl}/statistics/build/status?per=${limit}`, - ); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return ((await response.json()) as PaginationResult) - .items; - } - - async getProjects(): Promise { - const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`; - const response = await fetch(`${baseUrl}/build/project`); - - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - - return (await response.json()) as string[]; + return response; } } diff --git a/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts b/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts index 430fbb1006..765be721c6 100644 --- a/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts +++ b/plugins/xcmetrics/src/api/__mocks__/XcmetricsClient.ts @@ -14,9 +14,24 @@ * limitations under the License. */ -import { Build, BuildFilters, XcmetricsApi } from '../types'; +import { + Build, + BuildCount, + BuildError, + BuildFilters, + BuildHost, + BuildMetadata, + BuildStatusResult, + BuildTime, + BuildWarning, + Target, + XcmetricsApi, + Xcode, +} from '../types'; -export const mockBuild = { +const MOCK_BUILD_ID = 'buildId'; + +export const mockBuild: Build = { userid: 'userid1', warningCount: 1, duration: 1, @@ -31,29 +46,140 @@ export const mockBuild = { projectName: 'ProjectName', compilationEndTimestampMicroseconds: 1, errorCount: 1, - id: 'buildId', + id: MOCK_BUILD_ID, buildStatus: 'succeeded', compilationDuration: 1, - schema: 'Schema', + schema: 'SchemaName', compiledCount: 1, endTimestamp: '2021-01-01T00:00:01Z', userid256: 'userId256', machineName: 'Example_Machine', wasSuspended: true, -} as Build; +}; -export const mockBuildCount = { day: '2021-07-10', builds: 10, errors: 1 }; -export const mockBuildTime = { +export const mockBuildCount: BuildCount = { + day: '2021-07-10', + builds: 10, + errors: 1, +}; + +export const mockBuildError: BuildError = { + detail: `\/Users\/\/myproject\/Sources\/MyClass.m:241:97:// / error: instance method 'fetch' not found ; did you mean 'fetchIt'?\r + myclass:[self.myService fetch]\r ^~~~~~~~~~~~~~\r fetch\r + 1 error generated.\r"`, + characterRangeEnd: 13815, + id: '3E6EF185-6AC1-4E95-87E8-E305F41916E9', + endingColumn: 97, + parentIdentifier: 'MyMac_34580469-5792-40F3-BEFB-7C5925996F23_8860', + day: '2020-11-02T00:00:00Z', + type: 'clangError', + title: 'Instance method "fetch" not found ; did you mean "fetchIt"?', + endingLine: 241, + severity: 2, + startingLine: 241, + parentType: 'step', + buildIdentifier: MOCK_BUILD_ID, + startingColumn: 97, + characterRangeStart: 0, + documentURL: 'file:///Users//myproject/Sources/MyClass.m', +}; + +export const mockBuildHost: BuildHost = { + id: '9DD5508D-4AD9-4C1C-AB7C-45BC2183EC51', + swapFreeMb: 1615.25, + hostOsFamily: 'Darwin', + isVirtual: false, + uptimeSeconds: 1602055187, + hostModel: 'MacBookPro14,2', + hostOsVersion: '10.15.7', + day: '2020-10-26T00:00:00Z', + cpuCount: 4, + swapTotalMb: 7168, + hostOs: 'Mac OS X', + hostArchitecture: 'x86_64', + memoryTotalMb: 16384, + timezone: 'CET', + cpuModel: 'Intel(R) Core(TM) i7-7567U CPU @ 3.50GHz', + buildIdentifier: MOCK_BUILD_ID, + memoryFreeMb: 24.5234375, + cpuSpeedGhz: 3.5, +}; + +export const mockBuildMetadata: BuildMetadata = { + anotherKey: '42', + thirdKey: 'Third value', + aKey: 'value1', +}; + +export const mockBuildTime: BuildTime = { day: '2021-07-10', durationP50: 1.1, durationP95: 2.1, totalDuration: 3.1, }; -export const mockBuildStatus = { id: 'build_id', status: 'succeeded' }; +export const mockBuildStatus: BuildStatusResult = { + id: MOCK_BUILD_ID, + buildStatus: 'succeeded', +}; + +export const mockBuildWarning: BuildWarning = { + detail: null, + characterRangeEnd: 9817, + documentURL: 'file:///Users//myproject/Sources/MyViewController.m', + endingColumn: 22, + id: '5F2011AC-F87F-4EDC-BBC6-2BBA3D789EB3', + parentIdentifier: 'MyMac_34580469-5792-40F3-BEFB-7C5925996F23_1845', + day: '2020-11-02T00:00:00Z', + type: 'deprecatedWarning', + title: + "'dimsBackgroundDuringPresentation' is deprecated: first deprecated in iOS 12.0", + endingLine: 235, + severity: 1, + startingLine: 235, + parentType: 'step', + clangFlag: '[-Wdeprecated-declarations]', + startingColumn: 22, + buildIdentifier: MOCK_BUILD_ID, + characterRangeStart: 0, +}; + +export const mockTarget: Target = { + id: 'MyMac_34580469-5792-40F3-BEFB-7C5925996F23_1992', + category: 'noop', + startTimestamp: '2020-11-02T10:59:09Z', + compilationEndTimestampMicroseconds: 1604314749.2909288, + endTimestampMicroseconds: 1604314982.298002, + endTimestamp: '2020-11-02T11:03:02Z', + fetchedFromCache: false, + errorCount: 0, + day: '2020-11-02T00:00:00Z', + warningCount: 0, + compilationEndTimestamp: '2020-11-02T10:59:09Z', + compilationDuration: 0, + compiledCount: 0, + duration: 0.000233007, + buildIdentifier: MOCK_BUILD_ID, + name: 'Model', + startTimestampMicroseconds: 1604314749.2909288, +}; + +export const mockXcode: Xcode = { + buildNumber: '12A7209', + id: '6354C87F-0ADC-4354-929C-02EBE545E099', + buildIdentifier: MOCK_BUILD_ID, + day: '2020-11-02T00:00:00Z', + version: '1200', +}; + +export const mockBuildResponse = { + build: mockBuild, + targets: [mockTarget], + xcode: mockXcode, +}; export const XcmetricsClient: XcmetricsApi = { - getBuild: (id: string) => { - return Promise.resolve({ ...mockBuild, id }); + getBuild: (_id: string) => { + return Promise.resolve(mockBuildResponse); }, getBuilds: () => { return Promise.resolve([ @@ -78,15 +204,27 @@ export const XcmetricsClient: XcmetricsApi = { }, }); }, + getBuildErrors: (buildId: string) => { + return Promise.resolve([{ ...mockBuildError, buildIdentifier: buildId }]); + }, getBuildCounts: () => { return Promise.resolve([mockBuildCount, mockBuildCount]); }, + getBuildHost: (buildId: string) => { + return Promise.resolve({ ...mockBuildHost, buildIdentifier: buildId }); + }, + getBuildMetadata: (_buildId: string) => { + return Promise.resolve(mockBuildMetadata); + }, getBuildStatuses: (limit: number) => { return Promise.resolve([mockBuild].slice(0, limit)); }, getBuildTimes: (days: number) => { return Promise.resolve([mockBuildTime, mockBuildTime].slice(0, days)); }, + getBuildWarnings: (buildId: string) => { + return Promise.resolve([{ ...mockBuildWarning, buildIdentifier: buildId }]); + }, getProjects: () => { return Promise.resolve([mockBuild.projectName]); }, diff --git a/plugins/xcmetrics/src/api/types.ts b/plugins/xcmetrics/src/api/types.ts index c018a67056..44f27872ca 100644 --- a/plugins/xcmetrics/src/api/types.ts +++ b/plugins/xcmetrics/src/api/types.ts @@ -52,6 +52,50 @@ export type BuildCount = { builds: number; }; +export type BuildError = { + detail: string; + characterRangeEnd: number; + id: string; + endingColumn: number; + parentIdentifier: string; + day: string; + type: string; + title: string; + endingLine: number; + severity: number; + startingLine: number; + parentType: string; + buildIdentifier: string; + startingColumn: number; + characterRangeStart: number; + documentURL: string; +}; + +export type BuildHost = { + id: string; + swapFreeMb: number; + hostOsFamily: string; + isVirtual: boolean; + uptimeSeconds: number; + hostModel: string; + hostOsVersion: string; + day: string; + cpuCount: number; + swapTotalMb: number; + hostOs: string; + hostArchitecture: string; + memoryTotalMb: number; + timezone: string; + cpuModel: string; + buildIdentifier: string; + memoryFreeMb: number; + cpuSpeedGhz: number; +}; + +export type BuildMetadata = { + [key: string]: string; +}; + export type BuildTime = { day: string; durationP50: number; @@ -59,6 +103,26 @@ export type BuildTime = { totalDuration: number; }; +export type BuildWarning = { + detail: string | null; + characterRangeEnd: number; + documentURL: string; + endingColumn: number; + id: string; + parentIdentifier: string; + day: string; + type: string; + title: string; + endingLine: number; + severity: number; + startingLine: number; + parentType: string; + clangFlag: string; + startingColumn: number; + buildIdentifier: string; + characterRangeStart: number; +}; + export type PaginationResult = { items: T[]; metadata: { @@ -68,6 +132,40 @@ export type PaginationResult = { }; }; +export type Target = { + id: string; + category: string; + startTimestamp: string; + compilationEndTimestampMicroseconds: number; + endTimestampMicroseconds: number; + endTimestamp: string; + fetchedFromCache: boolean; + errorCount: number; + day: string; + warningCount: number; + compilationEndTimestamp: string; + compilationDuration: number; + compiledCount: number; + duration: number; + buildIdentifier: string; + name: string; + startTimestampMicroseconds: number; +}; + +export type Xcode = { + buildNumber: string; + id: string; + buildIdentifier: string; + day: string; + version: string; +}; + +export type BuildResponse = { + build: Build; + targets: Target[]; + xcode: Xcode; +}; + export type BuildFilters = { from: string; // ISO Date (e.g. "2021-01-01") to: string; // ISO Date (e.g. "2021-01-02") @@ -76,16 +174,20 @@ export type BuildFilters = { }; export interface XcmetricsApi { - getBuild(id: string): Promise; + getBuild(id: string): Promise; getBuilds(limit?: number): Promise; getFilteredBuilds( filters: BuildFilters, page?: number, perPage?: number, ): Promise>; + getBuildErrors(buildId: string): Promise; getBuildCounts(days: number): Promise; + getBuildHost(buildId: string): Promise; + getBuildMetadata(buildId: string): Promise; getBuildTimes(days: number): Promise; getBuildStatuses(limit: number): Promise; + getBuildWarnings(buildId: string): Promise; getProjects(): Promise; } diff --git a/plugins/xcmetrics/src/components/AccordionComponent/AccordionComponent.test.tsx b/plugins/xcmetrics/src/components/AccordionComponent/AccordionComponent.test.tsx new file mode 100644 index 0000000000..baa8c0e5f2 --- /dev/null +++ b/plugins/xcmetrics/src/components/AccordionComponent/AccordionComponent.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { AccordionComponent } from './AccordionComponent'; +import userEvent from '@testing-library/user-event'; + +describe('AccordionComponent', () => { + it('should render', async () => { + const rendered = await renderInTestApp( + , + ); + + expect(rendered.getByText('heading')).toBeInTheDocument(); + expect(rendered.getByText('secondaryHeading')).toBeInTheDocument(); + }); + + it('should show content when clicked', async () => { + const rendered = await renderInTestApp( + + Content + , + ); + + expect(rendered.getByText('Content')).not.toBeVisible(); + + userEvent.click(rendered.getByRole('button')); + expect(await rendered.findByText('Content')).toBeVisible(); + }); +}); diff --git a/plugins/xcmetrics/src/components/AccordionComponent/AccordionComponent.tsx b/plugins/xcmetrics/src/components/AccordionComponent/AccordionComponent.tsx new file mode 100644 index 0000000000..9afe736a2a --- /dev/null +++ b/plugins/xcmetrics/src/components/AccordionComponent/AccordionComponent.tsx @@ -0,0 +1,71 @@ +/* + * 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 { + Accordion, + AccordionSummary, + Typography, + AccordionDetails, + makeStyles, + createStyles, +} from '@material-ui/core'; +import React, { PropsWithChildren } from 'react'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import { BackstageTheme } from '@backstage/theme'; + +const useStyles = makeStyles((theme: BackstageTheme) => + createStyles({ + heading: { + flexBasis: '33.33%', + flexShrink: 0, + }, + secondaryHeading: { + color: theme.palette.text.secondary, + }, + }), +); + +interface AccordionProps { + id: string; + heading: string; + secondaryHeading?: string | number; + disabled?: boolean; + unmountOnExit?: boolean; +} + +export const AccordionComponent = ( + props: PropsWithChildren, +) => { + const classes = useStyles(); + + return ( + + } + aria-controls={`${props.id}-content`} + id={`${props.id}-header`} + > + {props.heading} + + {props.secondaryHeading} + + + {props.children} + + ); +}; diff --git a/plugins/xcmetrics/src/components/AccordionComponent/index.ts b/plugins/xcmetrics/src/components/AccordionComponent/index.ts new file mode 100644 index 0000000000..33632d6828 --- /dev/null +++ b/plugins/xcmetrics/src/components/AccordionComponent/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 { AccordionComponent } from './AccordionComponent'; diff --git a/plugins/xcmetrics/src/components/BuildDetailsComponent/BuildDetailsComponent.test.tsx b/plugins/xcmetrics/src/components/BuildDetailsComponent/BuildDetailsComponent.test.tsx new file mode 100644 index 0000000000..15b8720a1d --- /dev/null +++ b/plugins/xcmetrics/src/components/BuildDetailsComponent/BuildDetailsComponent.test.tsx @@ -0,0 +1,106 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; +import { BuildDetailsComponent, withRequest } from './BuildDetailsComponent'; +import { xcmetricsApiRef } from '../../api'; + +jest.mock('../../api/XcmetricsClient'); +const client = require('../../api/XcmetricsClient'); + +jest.mock('../AccordionComponent', () => ({ + AccordionComponent: ({ heading }: { heading: string }) => ( +
accordion-{heading}
+ ), +})); + +jest.mock('../BuildTimelineComponent', () => ({ + BuildTimelineComponent: () => 'BuildTimelineComponent', +})); + +describe('BuildDetailsComponent', () => { + it('should render', async () => { + const rendered = await renderInTestApp( + + + , + ); + + expect(rendered.getByText('accordion-Host')).toBeInTheDocument(); + expect(rendered.getByText('accordion-Errors')).toBeInTheDocument(); + expect(rendered.getByText('accordion-Warnings')).toBeInTheDocument(); + expect(rendered.getByText('accordion-Metadata')).toBeInTheDocument(); + expect(rendered.getByText('accordion-Timeline')).toBeInTheDocument(); + + expect(rendered.getByText(client.mockBuild.id)).toBeInTheDocument(); + expect( + rendered.getByText(client.mockBuild.projectName), + ).toBeInTheDocument(); + expect(rendered.getByText(client.mockBuild.schema)).toBeInTheDocument(); + }); +}); + +describe('BuildDetailsComponent with request', () => { + const BuildDetails = withRequest(BuildDetailsComponent); + + it('should fetch the build and render', async () => { + const rendered = await renderInTestApp( + + + , + ); + + expect(rendered.getByText(client.mockBuild.id)).toBeInTheDocument(); + }); + + it('should show an error when API not responding', async () => { + const errorMessage = 'MockErrorMessage'; + client.XcmetricsClient.getBuild = jest + .fn() + .mockRejectedValue({ message: errorMessage }); + + const rendered = await renderInTestApp( + + + , + ); + + expect(rendered.getByText(errorMessage)).toBeInTheDocument(); + }); + + it('should show a message when no build is returned from the API', async () => { + client.XcmetricsClient.getBuild = jest.fn().mockReturnValue(undefined); + + const rendered = await renderInTestApp( + + + , + ); + + expect( + rendered.getByText(`Could not load build ${client.mockBuild.id}`), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/xcmetrics/src/components/BuildDetailsComponent/BuildDetailsComponent.tsx b/plugins/xcmetrics/src/components/BuildDetailsComponent/BuildDetailsComponent.tsx new file mode 100644 index 0000000000..44f43a268b --- /dev/null +++ b/plugins/xcmetrics/src/components/BuildDetailsComponent/BuildDetailsComponent.tsx @@ -0,0 +1,202 @@ +/* + * 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 { createStyles, Divider, Grid, makeStyles } from '@material-ui/core'; +import React from 'react'; +import { BuildResponse, xcmetricsApiRef } from '../../api'; +import { Progress, StructuredMetadataTable } from '@backstage/core-components'; +import { Alert } from '@material-ui/lab'; +import { useAsync } from 'react-use'; +import { useApi } from '@backstage/core-plugin-api'; +import { formatDuration, formatStatus, formatTime } from '../../utils'; +import { StatusIconComponent as StatusIcon } from '../StatusIconComponent'; +import { BackstageTheme } from '@backstage/theme'; +import { AccordionComponent } from '../AccordionComponent'; +import { BuildTimelineComponent } from '../BuildTimelineComponent'; +import { PreformattedTextComponent } from '../PreformattedTextComponent'; + +const useStyles = makeStyles((theme: BackstageTheme) => + createStyles({ + divider: { + marginTop: theme.spacing(2), + marginBottom: theme.spacing(2), + }, + }), +); + +interface BuildDetailsProps { + buildData: BuildResponse; + showId?: boolean; +} + +export const BuildDetailsComponent = ({ + buildData: { build, targets, xcode }, + showId, +}: BuildDetailsProps) => { + const classes = useStyles(); + const client = useApi(xcmetricsApiRef); + const hostResult = useAsync( + async () => client.getBuildHost(build.id), + [build.id], + ); + const errorsResult = useAsync( + async () => client.getBuildErrors(build.id), + [build.id], + ); + const warningsResult = useAsync( + async () => client.getBuildWarnings(build.id), + [build.id], + ); + const metadataResult = useAsync( + async () => client.getBuildMetadata(build.id), + [build.id], + ); + + const buildDetails = { + project: build.projectName, + schema: build.schema, + category: build.category, + userId: build.userid, + 'started at': formatTime(build.startTimestamp), + 'ended at': formatTime(build.endTimestamp), + duration: formatDuration(build.duration), + status: ( + <> + + {formatStatus(build.buildStatus)} + + ), + xcode: `${xcode.version} (${xcode.buildNumber})`, + CI: build.isCi, + }; + + return ( + + + + + + + {hostResult.loading && } + {!hostResult.loading && hostResult.value && ( + + )} + + + +
+ {errorsResult.loading && } + {!errorsResult.loading && + errorsResult.value?.map((error, idx) => ( +
+ + {idx !== errorsResult.value.length - 1 && ( + + )} +
+ ))} +
+
+ + +
+ {warningsResult.loading && } + {!warningsResult.loading && + warningsResult.value?.map((warning, idx) => ( +
+ + {idx !== warningsResult.value.length - 1 && ( + + )} +
+ ))} +
+
+ + + {metadataResult.loading && } + {!metadataResult.loading && metadataResult.value && ( + + )} + + + + + +
+
+ ); +}; + +type WithRequestProps = Omit & { + buildId: string; +}; + +export const withRequest = + (Component: typeof BuildDetailsComponent) => + ({ buildId, ...props }: WithRequestProps) => { + const client = useApi(xcmetricsApiRef); + const { + value: buildResponse, + loading, + error, + } = useAsync(async () => client.getBuild(buildId), []); + + if (loading) { + return ; + } + + if (error) { + return {error.message}; + } + + if (!buildResponse) { + return Could not load build {buildId}; + } + + return ; + }; diff --git a/plugins/xcmetrics/src/components/BuildDetailsComponent/index.ts b/plugins/xcmetrics/src/components/BuildDetailsComponent/index.ts new file mode 100644 index 0000000000..4e50cf23a0 --- /dev/null +++ b/plugins/xcmetrics/src/components/BuildDetailsComponent/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 { BuildDetailsComponent, withRequest } from './BuildDetailsComponent'; diff --git a/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.test.tsx b/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.test.tsx index 3818d51a16..761c08a711 100644 --- a/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.test.tsx +++ b/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.test.tsx @@ -18,6 +18,7 @@ import { renderInTestApp } from '@backstage/test-utils'; import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; import { BuildListComponent } from './BuildListComponent'; import { xcmetricsApiRef } from '../../api'; +import userEvent from '@testing-library/user-event'; jest.mock('../../api/XcmetricsClient'); const client = require('../../api/XcmetricsClient'); @@ -26,6 +27,11 @@ jest.mock('../BuildListFilterComponent', () => ({ BuildListFilterComponent: () => 'BuildListFilterComponent', })); +jest.mock('../BuildDetailsComponent', () => ({ + withRequest: (component: any) => component, + BuildDetailsComponent: () => 'BuildDetailsComponent', +})); + describe('BuildListComponent', () => { it('should render', async () => { const rendered = await renderInTestApp( @@ -42,6 +48,23 @@ describe('BuildListComponent', () => { ).toBeInTheDocument(); }); + it('should show build details', async () => { + const rendered = await renderInTestApp( + + + , + ); + + userEvent.click( + (await rendered.findAllByLabelText('Detail panel visiblity toggle'))[0], + ); + expect( + await rendered.findByText('BuildDetailsComponent'), + ).toBeInTheDocument(); + }); + it('should show errors', async () => { const message = 'error'; client.XcmetricsClient.getFilteredBuilds = jest diff --git a/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.tsx b/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.tsx index c9ab028359..42f6bf4414 100644 --- a/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.tsx +++ b/plugins/xcmetrics/src/components/BuildListComponent/BuildListComponent.tsx @@ -17,12 +17,24 @@ import React, { useRef, useState } from 'react'; import { Table } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { BuildFilters, xcmetricsApiRef } from '../../api'; -import { Grid } from '@material-ui/core'; +import { createStyles, Grid, makeStyles } from '@material-ui/core'; import { BuildListFilterComponent as Filters } from '../BuildListFilterComponent'; import { DateTime } from 'luxon'; import { buildPageColumns } from '../BuildTableColumns'; +import { BuildDetailsComponent, withRequest } from '../BuildDetailsComponent'; +import { BackstageTheme } from '@backstage/theme'; + +const useStyles = makeStyles((theme: BackstageTheme) => + createStyles({ + detailPanel: { + padding: theme.spacing(2), + backgroundColor: theme.palette.background.paper, + }, + }), +); export const BuildListComponent = () => { + const classes = useStyles(); const client = useApi(xcmetricsApiRef); const tableRef = useRef(); @@ -68,6 +80,14 @@ export const BuildListComponent = () => { .catch(reason => reject(reason)); }); }} + detailPanel={rowData => { + const BuildDetails = withRequest(BuildDetailsComponent); + return ( +
+ +
+ ); + }} /> ); diff --git a/plugins/xcmetrics/src/components/BuildTableColumns.tsx b/plugins/xcmetrics/src/components/BuildTableColumns.tsx index 95c82ef372..a8bfd0166e 100644 --- a/plugins/xcmetrics/src/components/BuildTableColumns.tsx +++ b/plugins/xcmetrics/src/components/BuildTableColumns.tsx @@ -15,26 +15,16 @@ */ import { Chip } from '@material-ui/core'; -import React, { ReactChild } from 'react'; -import { - StatusOK, - StatusError, - StatusWarning, - TableColumn, -} from '@backstage/core-components'; -import { Build, BuildStatus } from '../api'; +import React from 'react'; +import { TableColumn } from '@backstage/core-components'; +import { Build } from '../api'; import { formatTime, formatDuration } from '../utils'; - -const STATUS_ICONS: { [key in BuildStatus]: ReactChild } = { - succeeded: , - failed: , - stopped: , -}; +import { StatusIconComponent as StatusIcon } from './StatusIconComponent'; const baseColumns: TableColumn[] = [ { field: 'buildStatus', - render: data => STATUS_ICONS[data.buildStatus], + render: data => , }, { title: 'Project', diff --git a/plugins/xcmetrics/src/components/BuildTimelineComponent/BuildTimelineComponent.test.tsx b/plugins/xcmetrics/src/components/BuildTimelineComponent/BuildTimelineComponent.test.tsx new file mode 100644 index 0000000000..3adb1ede58 --- /dev/null +++ b/plugins/xcmetrics/src/components/BuildTimelineComponent/BuildTimelineComponent.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { BuildTimelineComponent } from './BuildTimelineComponent'; + +jest.mock('../../api/XcmetricsClient'); +const client = require('../../api/XcmetricsClient'); + +describe('BuildTimelineComponent', () => { + it('should render', async () => { + const rendered = await renderInTestApp( + , + ); + expect( + await rendered.findByText(client.mockTarget.name), + ).toBeInTheDocument(); + }); + + it('should render a message if no targets are provided', async () => { + const rendered = await renderInTestApp( + , + ); + expect(rendered.getByText('No Targets')).toBeInTheDocument(); + }); +}); diff --git a/plugins/xcmetrics/src/components/BuildTimelineComponent/BuildTimelineComponent.tsx b/plugins/xcmetrics/src/components/BuildTimelineComponent/BuildTimelineComponent.tsx new file mode 100644 index 0000000000..0d0f249a95 --- /dev/null +++ b/plugins/xcmetrics/src/components/BuildTimelineComponent/BuildTimelineComponent.tsx @@ -0,0 +1,119 @@ +/* + * 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 { createStyles, makeStyles, useTheme } from '@material-ui/core'; +import React from 'react'; +import { + Bar, + BarChart, + CartesianGrid, + Legend, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { BackstageTheme } from '@backstage/theme'; +import { Target } from '../../api'; +import { formatSecondsInterval, formatPercentage } from '../../utils'; + +const EMPTY_HEIGHT = 100; + +const useStyles = makeStyles((theme: BackstageTheme) => + createStyles({ + toolTip: { + backgroundColor: theme.palette.background.paper, + opacity: 0.8, + padding: 8, + }, + }), +); + +const TargetToolTip = ({ active, payload, label }: any) => { + const classes = useStyles(); + + if (active && payload && payload.length === 2) { + const buildTime = payload[0].value[1] - payload[0].value[0]; + const compileTime = payload[1].value[1] - payload[1].value[0]; + return ( +
+ {`${label}: ${formatSecondsInterval(payload[0].value)}`} +
+ {buildTime > 0 && + `Compile time: ${formatPercentage(compileTime / buildTime)}`} +
+ ); + } + + return null; +}; + +const getTimelineData = (targets: Target[]) => { + const min = targets[0].startTimestampMicroseconds; + + return targets + .filter(target => target.fetchedFromCache === false) + .map(target => ({ + name: target.name, + buildTime: [ + target.startTimestampMicroseconds - min, + target.endTimestampMicroseconds - min, + ], + compileTime: [ + target.startTimestampMicroseconds - min, + target.compilationEndTimestampMicroseconds - min, + ], + })); +}; + +export interface BuildTimelineProps { + targets: Target[]; + height?: number; + width?: number; +} + +export const BuildTimelineComponent = ({ + targets, + height, + width, +}: BuildTimelineProps) => { + const theme = useTheme(); + if (!targets.length) return

No Targets

; + + const data = getTimelineData(targets); + + return ( + + + + + + } /> + + + + + + ); +}; diff --git a/plugins/xcmetrics/src/components/BuildTimelineComponent/index.ts b/plugins/xcmetrics/src/components/BuildTimelineComponent/index.ts new file mode 100644 index 0000000000..fe294f8467 --- /dev/null +++ b/plugins/xcmetrics/src/components/BuildTimelineComponent/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 { BuildTimelineComponent } from './BuildTimelineComponent'; diff --git a/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.test.tsx b/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.test.tsx new file mode 100644 index 0000000000..601f3ea6a2 --- /dev/null +++ b/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.test.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { BuildsPage } from './BuildsPage'; + +jest.mock('../BuildDetailsComponent', () => ({ + withRequest: (component: any) => component, + BuildDetailsComponent: () => 'BuildDetailsComponent', +})); + +jest.mock('../BuildListComponent', () => ({ + BuildListComponent: () => 'BuildListComponent', +})); + +describe('BuildPageComponent', () => { + it('should render BuildDetailsWithDataComponent if build id is provided in path', async () => { + const rendered = await renderInTestApp(, { + routeEntries: [`/buildId`], + }); + + expect(rendered.getByText('BuildDetailsComponent')).toBeInTheDocument(); + }); + + it('should render BuildListComponent if no build id is provided in path', async () => { + const rendered = await renderInTestApp(); + + expect(rendered.getByText('BuildListComponent')).toBeInTheDocument(); + }); +}); diff --git a/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.tsx b/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.tsx new file mode 100644 index 0000000000..ca3f023ff5 --- /dev/null +++ b/plugins/xcmetrics/src/components/BuildsPage/BuildsPage.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { useRouteRefParams } from '@backstage/core-plugin-api'; +import { buildsRouteRef } from '../../routes'; +import { BuildListComponent } from '../BuildListComponent'; +import { BuildDetailsComponent, withRequest } from '../BuildDetailsComponent'; +import { InfoCard } from '@backstage/core-components'; + +export const BuildsPage = () => { + const { '*': buildId } = useRouteRefParams(buildsRouteRef) ?? { '*': '' }; + + if (buildId) { + const BuildDetails = withRequest(BuildDetailsComponent); + + return ( + + + + ); + } + + return ; +}; diff --git a/plugins/xcmetrics/src/components/BuildsPage/index.ts b/plugins/xcmetrics/src/components/BuildsPage/index.ts new file mode 100644 index 0000000000..47fe9166df --- /dev/null +++ b/plugins/xcmetrics/src/components/BuildsPage/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 { BuildsPage } from './BuildsPage'; diff --git a/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.tsx b/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.tsx index 6877340a5b..854fbbd87f 100644 --- a/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.tsx +++ b/plugins/xcmetrics/src/components/DatePickerComponent/DatePickerComponent.tsx @@ -18,6 +18,7 @@ import { createStyles, InputBase, InputProps, + makeStyles, Theme, Typography, withStyles, @@ -50,6 +51,13 @@ const BootstrapInput = withStyles((theme: Theme) => }), )(InputBase); +const useStyles = makeStyles({ + root: { + display: 'flex', + flexDirection: 'column', + }, +}); + interface DatePickerProps { label: string; onDateChange?: (date: string) => void; @@ -59,15 +67,19 @@ export const DatePickerComponent = ({ label, onDateChange, ...inputProps -}: InputProps & DatePickerProps) => ( - <> - {label} - onDateChange?.(event.target.value)} - {...inputProps} - /> - -); +}: InputProps & DatePickerProps) => { + const classes = useStyles(); + + return ( +
+ {label} + onDateChange?.(event.target.value)} + {...inputProps} + /> +
+ ); +}; diff --git a/plugins/xcmetrics/src/components/PreformattedTextComponent/PreformattedTextComponent.tsx b/plugins/xcmetrics/src/components/PreformattedTextComponent/PreformattedTextComponent.tsx new file mode 100644 index 0000000000..b73e2cead5 --- /dev/null +++ b/plugins/xcmetrics/src/components/PreformattedTextComponent/PreformattedTextComponent.tsx @@ -0,0 +1,128 @@ +/* + * 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 { + Button, + createStyles, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + IconButton, + makeStyles, +} from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import React, { useState } from 'react'; +import { BackstageTheme } from '@backstage/theme'; +import { cn } from '../../utils'; + +const useStyles = makeStyles((theme: BackstageTheme) => + createStyles({ + pre: { + whiteSpace: 'pre-line', + wordBreak: 'break-all', + }, + expandable: { + cursor: 'pointer', + }, + fullPre: { + whiteSpace: 'pre-wrap', + }, + closeButton: { + position: 'absolute', + right: theme.spacing(1), + top: theme.spacing(1), + color: theme.palette.grey[500], + }, + }), +); + +interface PreformattedTextProps { + text: string; + maxChars: number; +} + +interface ExpandableProps extends PreformattedTextProps { + expandable: boolean; + title: string; +} + +interface NonExpandableProps extends PreformattedTextProps { + expandable?: never; + title?: string; +} + +export const PreformattedTextComponent = ({ + text, + maxChars, + expandable, + title, +}: NonExpandableProps | ExpandableProps) => { + const [open, setOpen] = useState(false); + const classes = useStyles(); + + const handleKeyUp = (event: React.KeyboardEvent) => { + if (expandable && event.key === 'Enter') { + setOpen(true); + } + }; + + return ( + <> +
expandable && setOpen(true)} + onKeyUp={handleKeyUp} + tabIndex={expandable ? 0 : undefined} + > +
+          {text.slice(0, maxChars - 1).trim()}
+          {text.length > maxChars - 1 && '…'}
+        
+
+ + {expandable && ( + setOpen(false)} + aria-labelledby="dialog-title" + aria-describedby="dialog-description" + maxWidth="xl" + fullWidth + > + + {title} + setOpen(false)} + > + + + + +
{text}
+
+ + + +
+ )} + + ); +}; diff --git a/plugins/xcmetrics/src/components/PreformattedTextComponent/index.ts b/plugins/xcmetrics/src/components/PreformattedTextComponent/index.ts new file mode 100644 index 0000000000..dbc27dd869 --- /dev/null +++ b/plugins/xcmetrics/src/components/PreformattedTextComponent/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 { PreformattedTextComponent } from './PreformattedTextComponent'; diff --git a/plugins/xcmetrics/src/components/StatusCellComponent/StatusCellComponent.tsx b/plugins/xcmetrics/src/components/StatusCellComponent/StatusCellComponent.tsx index 1ecc324683..201cb4febf 100644 --- a/plugins/xcmetrics/src/components/StatusCellComponent/StatusCellComponent.tsx +++ b/plugins/xcmetrics/src/components/StatusCellComponent/StatusCellComponent.tsx @@ -28,15 +28,16 @@ interface TooltipContentProps { const TooltipContent = ({ buildId }: TooltipContentProps) => { const client = useApi(xcmetricsApiRef); - const { - value: build, - loading, - error, - } = useAsync(async () => client.getBuild(buildId), []); + const { value, loading, error } = useAsync( + async () => client.getBuild(buildId), + [], + ); if (error) { return
{error.message}
; - } else if (loading || !build) { + } + + if (loading || !value?.build) { return ; } @@ -45,15 +46,15 @@ const TooltipContent = ({ buildId }: TooltipContentProps) => {
- + - + - +
Started{new Date(build.startTimestamp).toLocaleString()}{new Date(value.build.startTimestamp).toLocaleString()}
Duration{formatDuration(build.duration)}{formatDuration(value.build.duration)}
Status{formatStatus(build.buildStatus)}{formatStatus(value.build.buildStatus)}
diff --git a/plugins/xcmetrics/src/components/StatusIconComponent/StatusIconComponent.test.tsx b/plugins/xcmetrics/src/components/StatusIconComponent/StatusIconComponent.test.tsx new file mode 100644 index 0000000000..8833d828f0 --- /dev/null +++ b/plugins/xcmetrics/src/components/StatusIconComponent/StatusIconComponent.test.tsx @@ -0,0 +1,38 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { StatusIconComponent } from './StatusIconComponent'; + +describe('StatusIconComponent', () => { + it('should render', async () => { + let rendered = await renderInTestApp( + , + ); + expect(rendered.getByLabelText('Status ok')).toBeInTheDocument(); + + rendered = await renderInTestApp( + , + ); + expect(rendered.getByLabelText('Status error')).toBeInTheDocument(); + + rendered = await renderInTestApp( + , + ); + expect(rendered.getByLabelText('Status warning')).toBeInTheDocument(); + }); +}); diff --git a/plugins/xcmetrics/src/components/StatusIconComponent/StatusIconComponent.tsx b/plugins/xcmetrics/src/components/StatusIconComponent/StatusIconComponent.tsx new file mode 100644 index 0000000000..f680003ff4 --- /dev/null +++ b/plugins/xcmetrics/src/components/StatusIconComponent/StatusIconComponent.tsx @@ -0,0 +1,35 @@ +/* + * 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 { + StatusError, + StatusOK, + StatusWarning, +} from '@backstage/core-components'; +import { BuildStatus } from '../../api'; + +const STATUS_ICONS: { [key in BuildStatus]: JSX.Element } = { + succeeded: , + failed: , + stopped: , +}; + +interface StatusIconProps { + buildStatus: BuildStatus; +} + +export const StatusIconComponent = ({ buildStatus }: StatusIconProps) => + STATUS_ICONS[buildStatus]; diff --git a/plugins/xcmetrics/src/components/StatusIconComponent/index.ts b/plugins/xcmetrics/src/components/StatusIconComponent/index.ts new file mode 100644 index 0000000000..b69415bd78 --- /dev/null +++ b/plugins/xcmetrics/src/components/StatusIconComponent/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 { StatusIconComponent } from './StatusIconComponent'; diff --git a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx index 2ce634dc11..b0d81757a9 100644 --- a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx +++ b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx @@ -18,6 +18,7 @@ import { renderInTestApp } from '@backstage/test-utils'; import { ApiProvider, ApiRegistry } from '@backstage/core-app-api'; import { XcmetricsLayout } from './XcmetricsLayout'; import { xcmetricsApiRef } from '../../api'; +import userEvent from '@testing-library/user-event'; jest.mock('../../api/XcmetricsClient'); const client = require('../../api/XcmetricsClient'); @@ -44,5 +45,7 @@ describe('XcmetricsLayout', () => { expect(rendered.getByText('Builds')).toBeInTheDocument(); expect(rendered.getByText('OverviewComponent')).toBeInTheDocument(); + userEvent.click(rendered.getByText('Builds')); + expect(await rendered.findByText('BuildListComponent')).toBeInTheDocument(); }); }); diff --git a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx index 48faaaa3a6..33e9d64c6b 100644 --- a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx +++ b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx @@ -24,7 +24,7 @@ import { import { OverviewComponent } from '../OverviewComponent'; import { buildsRouteRef, rootRouteRef } from '../../routes'; import { RouteRef, SubRouteRef } from '@backstage/core-plugin-api'; -import { BuildListComponent } from '../BuildListComponent'; +import { BuildsPage } from '../BuildsPage'; export interface TabConfig { routeRef: RouteRef | SubRouteRef; @@ -41,7 +41,7 @@ const TABS: TabConfig[] = [ { routeRef: buildsRouteRef, title: 'Builds', - component: , + component: , }, ]; diff --git a/plugins/xcmetrics/src/utils/format.ts b/plugins/xcmetrics/src/utils/format.ts index 8c6dadb768..e21e95a7b2 100644 --- a/plugins/xcmetrics/src/utils/format.ts +++ b/plugins/xcmetrics/src/utils/format.ts @@ -18,8 +18,12 @@ import { BuildStatus } from '../api'; export const formatDuration = (seconds: number) => { const duration = Duration.fromObject({ - seconds: Math.round(seconds), - }).shiftTo('hours', 'minutes', 'seconds'); + seconds: seconds, + }).shiftTo('hours', 'minutes', 'seconds', 'milliseconds'); + + if (duration.hours + duration.minutes + duration.seconds === 0) { + return `${Math.round(duration.milliseconds)} ms`; + } const h = duration.hours ? `${duration.hours} h` : ''; const m = duration.minutes ? `${duration.minutes} m` : ''; @@ -28,6 +32,12 @@ export const formatDuration = (seconds: number) => { return `${h} ${m} ${s}`; }; +export const formatSecondsInterval = ([start, end]: [number, number]) => { + return `${Math.round(start * 100) / 100} s - ${ + Math.round(end * 100) / 100 + } s`; +}; + export const formatTime = (timestamp: string) => { return DateTime.fromISO(timestamp).toLocaleString( DateTime.DATETIME_SHORT_WITH_SECONDS, diff --git a/yarn.lock b/yarn.lock index e48efe4e81..7644cd7a46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2357,6 +2357,51 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" +"@backstage/core-components@^0.2.0": + version "0.2.0" + resolved "https://registry.npmjs.org/@backstage/core-components/-/core-components-0.2.0.tgz#d8c4f06b4b08d60292729b092ed743357802360f" + integrity sha512-pQipnJwZb7usojknLmY9vrMo4aSAo/r3ueGkdk1hPLRRP+8YI9XliMop2l558bHiLAqj7AjASD8ZGAfp8fpYog== + dependencies: + "@backstage/config" "^0.1.5" + "@backstage/core-plugin-api" "^0.1.4" + "@backstage/errors" "^0.1.1" + "@backstage/theme" "^0.2.9" + "@material-table/core" "^3.1.0" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.45" + "@testing-library/react-hooks" "^3.4.2" + "@types/dagre" "^0.7.44" + "@types/prop-types" "^15.7.3" + "@types/react" "^16.9" + "@types/react-sparklines" "^1.7.0" + "@types/react-text-truncate" "^0.14.0" + classnames "^2.2.6" + clsx "^1.1.0" + d3-selection "^2.0.0" + d3-shape "^2.0.0" + d3-zoom "^2.0.0" + dagre "^0.8.5" + immer "^9.0.1" + lodash "^4.17.15" + pluralize "^8.0.0" + prop-types "^15.7.2" + qs "^6.9.4" + rc-progress "^3.0.0" + react "^16.12.0" + react-dom "^16.12.0" + react-helmet "6.1.0" + react-hook-form "^6.15.4" + react-markdown "^5.0.2" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-sparklines "^1.7.0" + react-syntax-highlighter "^15.4.3" + react-text-truncate "^0.16.0" + react-use "^17.2.4" + remark-gfm "^1.0.0" + zen-observable "^0.8.15" + "@backstage/core@*": version "0.7.14" resolved "https://registry.npmjs.org/@backstage/core/-/core-0.7.14.tgz#863844fe40bb6a29bcc2d297e42055633b0e886f" @@ -2403,6 +2448,18 @@ remark-gfm "^1.0.0" zen-observable "^0.8.15" +"@backstage/integration@^0.5.8": + version "0.5.9" + resolved "https://registry.npmjs.org/@backstage/integration/-/integration-0.5.9.tgz#9eeb2f570794b196a8e769d8628b68937863a0fc" + integrity sha512-SfYcIHMurasJ5AlNzvq/4+8VLx5ONZ1vZqSG9zX/jvASCyrQY+Ifumaj13XBBuykTv61eHaqsLcEFtd0zgBtpQ== + dependencies: + "@backstage/config" "^0.1.6" + "@octokit/auth-app" "^3.4.0" + "@octokit/rest" "^18.5.3" + cross-fetch "^3.0.6" + git-url-parse "~11.4.4" + luxon "^1.25.0" + "@backstage/plugin-catalog-react@^0.3.0": version "0.3.1" resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-0.3.1.tgz#d30a063a4ceb4d446310a687d19c987f55824fdb" @@ -5128,15 +5185,15 @@ react-use "^17.2.4" "@roadiehq/backstage-plugin-github-insights@^1.1.20": - version "1.1.21" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-1.1.21.tgz#d43de9bc384c7a6e3d8ef13c77f4842e2ca69794" - integrity sha512-PctbxHscDmFK8jaM3MPUhoDw90TTQEydxTjiTQ+1+yY2KnlrEbxu5i8RgDk3UfRfZQIJw6JFDkEYGaTJIAv3Tw== + version "1.1.22" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-github-insights/-/backstage-plugin-github-insights-1.1.22.tgz#8d9a02d69ee55bfbf0c2ff84bf0579b372e6e8b1" + integrity sha512-KGLkJsVPKeF2hlBFNP0934mJ1kyB8Awx8bCKrZfoVw2mdBjkZqq5sB81WooMUBLpQ8QZJfCw+nvIQq22LA6cOg== dependencies: "@backstage/catalog-model" "^0.9.0" "@backstage/core-app-api" "^0.1.3" - "@backstage/core-components" "^0.1.3" + "@backstage/core-components" "^0.2.0" "@backstage/core-plugin-api" "^0.1.3" - "@backstage/plugin-catalog-react" "^0.3.0" + "@backstage/plugin-catalog-react" "^0.4.0" "@backstage/theme" "^0.2.7" "@date-io/core" "2.10.7" "@material-ui/core" "^4.11.0" @@ -6529,12 +6586,21 @@ resolved "https://registry.npmjs.org/@types/diff/-/diff-5.0.0.tgz#eb71e94feae62548282c4889308a3dfb57e36020" integrity sha512-jrm2K65CokCCX4NmowtA+MfXyuprZC13jbRuwprs6/04z/EcFg/MCwYdsHn+zgV4CQBiATiI7AEq7y1sZCtWKA== -"@types/dockerode@^3.2.1": - version "3.2.1" - resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.2.1.tgz#f713a8f6f1017c227845ab33239383da721207b9" - integrity sha512-AeZpdQMNqM8dtrEaaP81CbjdRVKNmFIMzgz5IlKIeS5uWEmjlEmENP444AGTEEF71r5TFuY9E4SkzZAO8lOF1A== +"@types/docker-modem@*": + version "3.0.1" + resolved "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.1.tgz#2a29c914dd64c4c9213510d2ce75fa85232934df" + integrity sha512-ZUXPF0WNnvs7AxoQRijt+DW2jsXnWCk8ac28tTYzTpBNnOEIAw83A+pYkCXjTFdJHMTc+wUmwNr71Zy2TRjlWg== dependencies: "@types/node" "*" + "@types/ssh2" "*" + +"@types/dockerode@^3.2.1": + version "3.2.7" + resolved "https://registry.npmjs.org/@types/dockerode/-/dockerode-3.2.7.tgz#0bc1f583b0e1edc4ca0c6179ef251382df35dcb3" + integrity sha512-Y8hMRQTwsOjz4qm6yilZKKjB/Y7+2EOiY3RPN1Xtu63wEUEDVv+3Ou+sgiisPE9+pVe3bmwhnF+E1Iwj/o4J6w== + dependencies: + "@types/docker-modem" "*" + "@types/node" "*" "@types/dompurify@^2.2.2": version "2.2.3" @@ -6995,10 +7061,10 @@ "@types/node" "*" form-data "^3.0.0" -"@types/node@*", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^14.14.31", "@types/node@^14.14.32", "@types/node@^14.14.33": - version "14.17.3" - resolved "https://registry.npmjs.org/@types/node/-/node-14.17.3.tgz#6d327abaa4be34a74e421ed6409a0ae2f47f4c3d" - integrity sha512-e6ZowgGJmTuXa3GyaPbTGxX17tnThl2aSSizrFthQ7m9uLGZBXiGhgE55cjRZTF5kjZvYn9EOPOMljdjwbflxw== +"@types/node@*", "@types/node@>= 8", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^14.0.10", "@types/node@^14.14.31", "@types/node@^14.14.32", "@types/node@^14.14.33": + version "14.17.8" + resolved "https://registry.npmjs.org/@types/node/-/node-14.17.8.tgz#813b73ab7d82ac06ddfd2458b13c88459a3b319f" + integrity sha512-0CHLt50GbUmH/6MrlBIKNdWCglvlyQKkorRf08/0DIi0ryuTPP+ijWLSI19SbDTHSKaagGDELiImY4BSikt61w== "@types/node@10.17.13": version "10.17.13" @@ -7015,11 +7081,6 @@ resolved "https://registry.npmjs.org/@types/node/-/node-12.12.58.tgz#46dae9b2b9ee5992818c8f7cee01ff4ce03ab44c" integrity sha512-Be46CNIHWAagEfINOjmriSxuv7IVcqbGe+sDSg2SYCEz/0CRBy7LRASGfRbD8KZkqoePU73Wsx3UvOSFcq/9hA== -"@types/node@^14.0.10": - version "14.17.8" - resolved "https://registry.npmjs.org/@types/node/-/node-14.17.8.tgz#813b73ab7d82ac06ddfd2458b13c88459a3b319f" - integrity sha512-0CHLt50GbUmH/6MrlBIKNdWCglvlyQKkorRf08/0DIi0ryuTPP+ijWLSI19SbDTHSKaagGDELiImY4BSikt61w== - "@types/normalize-package-data@^2.4.0": version "2.4.0" resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" @@ -7183,9 +7244,9 @@ "@types/react" "*" "@types/react-lazylog@^4.5.0": - version "4.5.0" - resolved "https://registry.npmjs.org/@types/react-lazylog/-/react-lazylog-4.5.0.tgz#1677ac520f29ada2e95b8cd5f8ec54b897227278" - integrity sha512-yg0IhKtDRGqUUtXJwi9Y/sSmgX/gmVP0vu0cr5uAECmJnRQbAgsGoVUntQGtwH0Z7Y2iiTh+QW5LkOFsmtIisg== + version "4.5.1" + resolved "https://registry.npmjs.org/@types/react-lazylog/-/react-lazylog-4.5.1.tgz#babb5d814f7035b5434518769975e12f299356a8" + integrity sha512-g4yeosa1zYhu2BUJmuu2H2o0dsdRj0o8Omw3pBiVHdLHJaeYIyArvyMRR3bI/MxZxG4EaiRl8AOQ6zeM8P46jA== dependencies: "@types/react" "*" immutable ">=3.8.2" @@ -7368,6 +7429,14 @@ dependencies: "@types/node" "*" +"@types/ssh2@*": + version "0.5.47" + resolved "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.47.tgz#67a8b35a0527b2bb668f6dea4c84be6ff1abdc19" + integrity sha512-ZhqJg8BRV7OsCi0KVqPr27lUMMmLEeHYw1VXUNGGDlQEDq9HTsKx+wYvi8E6oNC6gRZ7PV99ZMZmMr5vztcYYA== + dependencies: + "@types/node" "*" + "@types/ssh2-streams" "*" + "@types/ssh2@^0.5.45": version "0.5.46" resolved "https://registry.npmjs.org/@types/ssh2/-/ssh2-0.5.46.tgz#e12341a242aea0e98ac2dec89e039bf421fd3584" @@ -18654,15 +18723,15 @@ lunr@^2.3.9: resolved "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== -luxon@1.25.0: - version "1.25.0" - resolved "https://registry.npmjs.org/luxon/-/luxon-1.25.0.tgz#d86219e90bc0102c0eb299d65b2f5e95efe1fe72" - integrity sha512-hEgLurSH8kQRjY6i4YLey+mcKVAWXbDNlZRmM6AgWDJ1cY3atl8Ztf5wEY7VBReFbmGnwQPz7KYJblL8B2k0jQ== +luxon@2.0.2, luxon@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/luxon/-/luxon-2.0.2.tgz#11f2cd4a11655fdf92e076b5782d7ede5bcdd133" + integrity sha512-ZRioYLCgRHrtTORaZX1mx+jtxKtKuI5ZDvHNAmqpUzGqSrR+tL4FVLn/CUGMA3h0+AKD1MAxGI5GnCqR5txNqg== -luxon@^1.25.0, luxon@^1.26.0, luxon@^1.27.0: - version "1.27.0" - resolved "https://registry.npmjs.org/luxon/-/luxon-1.27.0.tgz#ae10c69113d85dab8f15f5e8390d0cbeddf4f00f" - integrity sha512-VKsFsPggTA0DvnxtJdiExAucKdAnwbCCNlMM5ENvHlxubqWd0xhZcdb4XgZ7QFNhaRhilXCFxHuoObP5BNA4PA== +luxon@^1.25.0, luxon@^1.27.0: + version "1.28.0" + resolved "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" + integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== lz-string@^1.4.4: version "1.4.4" @@ -21107,9 +21176,9 @@ path-key@^3.0.0, path-key@^3.1.0: integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-root-regex@^0.1.0: version "0.1.2"