diff --git a/.changeset/beige-cheetahs-jog.md b/.changeset/beige-cheetahs-jog.md deleted file mode 100644 index 87ef3ef44c..0000000000 --- a/.changeset/beige-cheetahs-jog.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Add context for versions:bump on what version it was bumped to. Updated tests for the same. diff --git a/.changeset/breezy-carrots-hunt.md b/.changeset/breezy-carrots-hunt.md deleted file mode 100644 index 0075009a2d..0000000000 --- a/.changeset/breezy-carrots-hunt.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-explore': patch ---- - -Add "Organization" tab with a diagram diff --git a/.changeset/calm-insects-melt.md b/.changeset/calm-insects-melt.md deleted file mode 100644 index 4b17764c33..0000000000 --- a/.changeset/calm-insects-melt.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/cli': patch -'@backstage/create-app': patch -'@backstage/plugin-catalog-graphql': patch ---- - -chore: bump `ts-node` versions to 9.1.1 diff --git a/.changeset/cyan-beans-marry.md b/.changeset/cyan-beans-marry.md deleted file mode 100644 index 99d7ad47ce..0000000000 --- a/.changeset/cyan-beans-marry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Renamed parameters to input in template schema diff --git a/.changeset/forty-pumpkins-agree.md b/.changeset/forty-pumpkins-agree.md deleted file mode 100644 index 2d8892c862..0000000000 --- a/.changeset/forty-pumpkins-agree.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -The apiBaseUrl setting for Bitbucket Server integrations will now be used when it is set. Otherwise, it will default back to the host setting. diff --git a/.changeset/grumpy-goats-refuse.md b/.changeset/grumpy-goats-refuse.md deleted file mode 100644 index 8dc2e390fc..0000000000 --- a/.changeset/grumpy-goats-refuse.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder': patch ---- - -Show error on task page if task does not exist. diff --git a/.changeset/lucky-pens-ring.md b/.changeset/lucky-pens-ring.md new file mode 100644 index 0000000000..04b0749fd0 --- /dev/null +++ b/.changeset/lucky-pens-ring.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-badges-backend': patch +--- + +Fix generated badge links diff --git a/.changeset/mean-taxis-leave.md b/.changeset/mean-taxis-leave.md new file mode 100644 index 0000000000..8c1e5ea3c3 --- /dev/null +++ b/.changeset/mean-taxis-leave.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-model': patch +--- + +Add the `status` field to the Entity envelope diff --git a/.changeset/ninety-pots-guess.md b/.changeset/ninety-pots-guess.md deleted file mode 100644 index 4b9a770c29..0000000000 --- a/.changeset/ninety-pots-guess.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -update plugins created to use react-use 17.2.4 diff --git a/.changeset/purple-hairs-kick.md b/.changeset/purple-hairs-kick.md new file mode 100644 index 0000000000..6e6d698d07 --- /dev/null +++ b/.changeset/purple-hairs-kick.md @@ -0,0 +1,5 @@ +--- +'@backstage/config-loader': patch +--- + +Updated dependencies diff --git a/.changeset/shaggy-drinks-hammer.md b/.changeset/shaggy-drinks-hammer.md deleted file mode 100644 index 67f284ea3f..0000000000 --- a/.changeset/shaggy-drinks-hammer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Paginate group members to only display 50 members maximum. diff --git a/.changeset/shaggy-glasses-lie.md b/.changeset/shaggy-glasses-lie.md new file mode 100644 index 0000000000..89570a93c2 --- /dev/null +++ b/.changeset/shaggy-glasses-lie.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-git-release-manager': patch +'@backstage/plugin-ilert': patch +--- + +[ImgBot] Optimize images diff --git a/.changeset/shaggy-melons-destroy.md b/.changeset/shaggy-melons-destroy.md new file mode 100644 index 0000000000..d202cab6cd --- /dev/null +++ b/.changeset/shaggy-melons-destroy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Skip adding entries to the `entities_search` table if their `key` exceeds a length limit. diff --git a/.changeset/short-bobcats-mate.md b/.changeset/short-bobcats-mate.md deleted file mode 100644 index d63e7058a9..0000000000 --- a/.changeset/short-bobcats-mate.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/core': patch -'@backstage/plugin-api-docs': patch ---- - -Fix state persisted in the URL make search input in the table toolbar lose their -focus. diff --git a/.changeset/smart-sheep-itch.md b/.changeset/smart-sheep-itch.md deleted file mode 100644 index 12a0ff5ad0..0000000000 --- a/.changeset/smart-sheep-itch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Improve error messages when schema validation fails diff --git a/.changeset/sour-kids-compete.md b/.changeset/sour-kids-compete.md deleted file mode 100644 index ce94305c48..0000000000 --- a/.changeset/sour-kids-compete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Move `jest-when` to the dev dependencies diff --git a/.changeset/stale-gifts-push.md b/.changeset/stale-gifts-push.md deleted file mode 100644 index f1e13ba14d..0000000000 --- a/.changeset/stale-gifts-push.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core': patch ---- - -Add count of older messages when multiple messages exist in AlertDisplay diff --git a/.changeset/stupid-balloons-change.md b/.changeset/stupid-balloons-change.md new file mode 100644 index 0000000000..98c40b4d60 --- /dev/null +++ b/.changeset/stupid-balloons-change.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Bug fix on sidebar position when Tab-Bar is enabled diff --git a/.changeset/tame-scissors-bow.md b/.changeset/tame-scissors-bow.md new file mode 100644 index 0000000000..14be080eea --- /dev/null +++ b/.changeset/tame-scissors-bow.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-github-deployments': patch +--- + +Support GHE by properly parsing enterprise instance hosts diff --git a/.changeset/techdocs-lemon-cooks-drum.md b/.changeset/techdocs-lemon-cooks-drum.md new file mode 100644 index 0000000000..d8f2954c2a --- /dev/null +++ b/.changeset/techdocs-lemon-cooks-drum.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Handle URLs with a `#hash` correctly when rewriting link URLs. diff --git a/.changeset/techdocs-spaces-in-objects.md b/.changeset/techdocs-spaces-in-objects.md deleted file mode 100644 index 7bbac5d2a8..0000000000 --- a/.changeset/techdocs-spaces-in-objects.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Fixed a bug that prevented loading static assets from GCS, S3, Azure, and OpenStackSwift whose keys contain spaces or other special characters. diff --git a/.changeset/ten-paws-ring.md b/.changeset/ten-paws-ring.md deleted file mode 100644 index 8ed2158f6c..0000000000 --- a/.changeset/ten-paws-ring.md +++ /dev/null @@ -1,44 +0,0 @@ ---- -'@backstage/core': patch -'@backstage/core-api': patch -'@backstage/integration-react': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-badges': patch -'@backstage/plugin-bitrise': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-circleci': patch -'@backstage/plugin-cloudbuild': patch -'@backstage/plugin-code-coverage': patch -'@backstage/plugin-config-schema': patch -'@backstage/plugin-cost-insights': patch -'@backstage/plugin-explore': patch -'@backstage/plugin-fossa': patch -'@backstage/plugin-gcp-projects': patch -'@backstage/plugin-github-actions': patch -'@backstage/plugin-github-deployments': patch -'@backstage/plugin-gitops-profiles': patch -'@backstage/plugin-graphiql': patch -'@backstage/plugin-jenkins': patch -'@backstage/plugin-kafka': patch -'@backstage/plugin-kubernetes': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-newrelic': patch -'@backstage/plugin-org': patch -'@backstage/plugin-pagerduty': patch -'@backstage/plugin-register-component': patch -'@backstage/plugin-rollbar': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-search': patch -'@backstage/plugin-sentry': patch -'@backstage/plugin-sonarqube': patch -'@backstage/plugin-splunk-on-call': patch -'@backstage/plugin-tech-radar': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-todo': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-welcome': patch ---- - -chore: bump `react-use` dependency in all packages diff --git a/.changeset/wild-moles-care.md b/.changeset/wild-moles-care.md deleted file mode 100644 index 35b886c2c1..0000000000 --- a/.changeset/wild-moles-care.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes-backend': patch ---- - -Add possibility to configure TLS verification for `gke` type clusters diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b07519824b..206c1a7883 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -16,6 +16,7 @@ /plugins/search-* @backstage/techdocs-core /plugins/techdocs @backstage/techdocs-core /plugins/techdocs-backend @backstage/techdocs-core +/plugins/ilert @yacut /packages/search-common @backstage/techdocs-core /packages/techdocs-common @backstage/techdocs-core /.changeset/cost-insights-* @backstage/silver-lining diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 694226b8cc..4e3c6d9a88 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -167,6 +167,7 @@ mailto maintainership makefile md +memcache microsite middleware minikube @@ -177,6 +178,7 @@ mkdocs monorepo monorepos msw +mysql namespace namespaced namespaces diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000000..31354ec138 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 0000000000..d2ae35e84b --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +yarn lint-staged diff --git a/OWNERS.md b/OWNERS.md index a79328dc20..cdbc234183 100644 --- a/OWNERS.md +++ b/OWNERS.md @@ -27,5 +27,5 @@ People that have made significant contributions to the project and earned write - Andrew Thauer - Wealthsimple (GitHub: [andrewthauer](https://github.com/andrewthauer)) - Oliver Sand - SDA SE (GitHub: [Fox32](https://github.com/Fox32)) - David Tuite - Roadie (GitHub: [dtuite](https://github.com/dtuite)) -- Adam Harvey - DXC Technology (GitHub: [adamdmharvey](https://github.com/adamdmharvey)) +- Adam Harvey - Cisco (GitHub: [adamdmharvey](https://github.com/adamdmharvey)) - Dominik Henneke - SDA SE (GitHub: [dhenneke](https://github.com/dhenneke)) diff --git a/app-config.yaml b/app-config.yaml index 3c4f2d7ea5..b1c915f85a 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -81,6 +81,13 @@ proxy: headers: Authorization: ${SENTRY_TOKEN} + '/ilert': + target: https://api.ilert.com + allowedMethods: ['GET', 'POST', 'PUT'] + allowedHeaders: ['Authorization'] + headers: + Authorization: ${ILERT_AUTH_HEADER} + organization: name: My Company diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md index eb087a9c5f..699574b986 100644 --- a/docs/features/software-catalog/descriptor-format.md +++ b/docs/features/software-catalog/descriptor-format.md @@ -23,6 +23,7 @@ we recommend that you name them `catalog-info.yaml`. - [Common to All Kinds: The Envelope](#common-to-all-kinds-the-envelope) - [Common to All Kinds: The Metadata](#common-to-all-kinds-the-metadata) - [Common to All Kinds: Relations](#common-to-all-kinds-relations) +- [Common to All Kinds: Status](#common-to-all-kinds-status) - [Kind: Component](#kind-component) - [Kind: Template](#kind-template) - [Kind: API](#kind-api) @@ -396,6 +397,54 @@ with it (such as the default kind being `Group` if not specified). See the [well-known relations section](well-known-relations.md) for a list of well-known / common relations and their semantics. +## Common to All Kinds: Status + +The `status` root field is a read-only set of statuses, pertaining to the +current state or health of the entity, described in the +[well-known statuses section](well-known-statuses.md). Each status field +contains a specific blob of data that describes some aspect of the state of the +entity, as seen from the point of view of some specific system. Different +systems may contribute to this status object, under their own respective keys. + +The current main use case for this field is for the ingestion processes of the +catalog itself to convey information about failures and warnings back to the +user. + +A status field as part of a single entity that's read out of the API may look as +follows. + +```js +{ + // ... + "status": { + "backstage.io/catalog-processing": { + "errors": [] + } + }, + "spec": { + // ... + } +} +``` + +The keys of the `status` object are arbitrary strings. We recommend that any +statuses that are not strictly private within the organization be namespaced to +avoid collisions. Statuses emitted by Backstage core processes will for example +be prefixed with `backstage.io/` as in the example above. + +The values of the `status` object are currently left unrestricted, except that +they must be objects. We reserve the right to extend this model in the future, +such that some fields of those value objects gain standardized meaning. We may +for example want to add a standard concept of "severity" or "level" to these. + +Entity descriptor YAML files are not supposed to contain this field. Instead, +catalog processors analyze the entity descriptor data and its surroundings, and +deduce status entries that are then attached onto the entity as read from the +catalog. + +See the [well-known statuses section](well-known-statuses.md) for a list of +well-known / common relations and their semantics. + ## Kind: Component Describes the following entity kind: diff --git a/docs/features/software-catalog/extending-the-model.md b/docs/features/software-catalog/extending-the-model.md index d4dca0c059..4cc1bc1110 100644 --- a/docs/features/software-catalog/extending-the-model.md +++ b/docs/features/software-catalog/extending-the-model.md @@ -1,6 +1,7 @@ --- id: extending-the-model title: Extending the model +# prettier-ignore description: Documentation on extending the catalog model --- @@ -301,9 +302,9 @@ Example intents: > "We have this concept of service maintainership, separate from ownership, that > we would like to make relations to individual users for." -> We feel that we want to explicitly model the team-to-global-department mapping -> as a relation, because it is core to our org setup and we frequently query for -> it. +> "We feel that we want to explicitly model the team-to-global-department +> mapping as a relation, because it is core to our org setup and we frequently +> query for it." Any processor can emit relations for entities as they are being processed, and new processors can be added when building the backend catalog using the @@ -349,3 +350,21 @@ If you want to extend the use of an established relation type in a way that has an effect outside of your organization, reach out to the Backstage maintainers or a support partner to discuss risk/impact. It may even be that one end of the relation could be considered for addition to the core. + +## Adding a New Status field + +Example intents: + +> "We would like to convey entity statuses through the catalog in a generic way, +> as an integration layer. Our monitoring and alerting system has a plugin with +> Backstage, and it would be useful if the entity's status field contained the +> current alert state close to the actual entity data for anyone to consume. + +While we are considering a mechanism for contributing generic statuses to +entities, no such mechanism has yet been built. If you are interested in that +topic, [this issue](https://github.com/backstage/backstage/issues/2292) contains +more context. + +But in general, errors emitted (and exceptions thrown) by any processor +including custom ones, end up in the [well known key](well-known-statuses.md) +for ingestion status. diff --git a/docs/features/software-catalog/well-known-statuses.md b/docs/features/software-catalog/well-known-statuses.md new file mode 100644 index 0000000000..7ddf320d26 --- /dev/null +++ b/docs/features/software-catalog/well-known-statuses.md @@ -0,0 +1,51 @@ +--- +id: well-known-statuses +title: Well-known Status fields of Catalog Entities +sidebar_label: Well-known Statuses +# prettier-ignore +description: Lists a number of well known entity statuses, that have defined semantics. They can be attached to catalog entities and consumed by plugins as needed. +--- + +This section lists a number of well known +[entity status fields](descriptor-format.md#common-to-all-kinds-status), that +have defined semantics. They can be attached to catalog entities and consumed by +plugins as needed. + +If you are looking to extend the set of statuses, see +[Extending the model](extending-the-model.md). + +## Common Fields + +The values of statuses are currently left unrestricted, except that they must be +objects. They therefore currently formally have no common fields. + +We reserve the right to extend this model in the future, such that some fields +of those value objects gain standardized meaning. We may for example want to add +a standard concept of "severity" or "level" to these. + +## Statuses + +This is a (non-exhaustive) list of statuses that are known to be in active use. + +### `backstage.io/catalog-processing` + +Contains the current status of the catalog's ingestion of this entity. Errors +that may appear here include inability to read from the remote SCM provider, +syntax errors in the YAML file, and similar. + +Note that the entity data itself may be of an older version, when errors are +present. The ingestion system keeps the old, valid entity data untouched when +possible, so the errors described in this state may not seem to align with the +rest of the entity, because they pertain to a remote that could not be +successfully ingested. This is normal. + +```yaml +# Example: +status: + backstage.io/catalog-processing: + errors: [] +``` + +This status is in active development and its format will change unexpectedly. Do +not consume it in your own code until such a time that this documentation has +been updated. diff --git a/microsite/data/plugins/ilert.yaml b/microsite/data/plugins/ilert.yaml new file mode 100644 index 0000000000..29cc4f6aa5 --- /dev/null +++ b/microsite/data/plugins/ilert.yaml @@ -0,0 +1,15 @@ +--- +title: iLert +author: iLert +authorUrl: https://www.ilert.com +category: Monitoring +description: iLert is a platform for alerting, on-call management and uptime monitoring targeted at DevOps and IT teams. +documentation: https://github.com/backstage/backstage/tree/master/plugins/ilert +iconUrl: https://avatars.githubusercontent.com/u/13230510?s=200&v=4 +npmPackageName: '@backstage/plugin-ilert' +tags: + - monitoring + - errors + - alerting + - uptime + - on-call diff --git a/microsite/sidebars.json b/microsite/sidebars.json index c7b049a53e..3544ebdca3 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -40,6 +40,7 @@ "features/software-catalog/references", "features/software-catalog/well-known-annotations", "features/software-catalog/well-known-relations", + "features/software-catalog/well-known-statuses", "features/software-catalog/extending-the-model", "features/software-catalog/external-integrations", "features/software-catalog/software-catalog-api" diff --git a/microsite/static/img/git-release-manager-logo.svg b/microsite/static/img/git-release-manager-logo.svg index b505d21560..b8e9cfe3ac 100644 --- a/microsite/static/img/git-release-manager-logo.svg +++ b/microsite/static/img/git-release-manager-logo.svg @@ -1,13 +1 @@ - - - Export - - - - - - - - - - \ No newline at end of file +Export \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 7826b3221d..0e2e78be26 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,6 +37,7 @@ nav: - Entity References: 'features/software-catalog/references.md' - Well-known Annotations: 'features/software-catalog/well-known-annotations.md' - Well-known Relations: 'features/software-catalog/well-known-relations.md' + - Well-known Statuses: 'features/software-catalog/well-known-statuses.md' - Extending the model: 'features/software-catalog/extending-the-model.md' - External integrations: 'features/software-catalog/external-integrations.md' - API: 'features/software-catalog/api.md' diff --git a/package.json b/package.json index b0f79031de..ed404565e4 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,8 @@ "prettier:check": "prettier --check .", "lerna": "lerna", "storybook": "yarn workspace storybook start", - "build-storybook": "yarn workspace storybook build-storybook" + "build-storybook": "yarn workspace storybook build-storybook", + "prepare": "husky install" }, "workspaces": { "packages": [ @@ -60,18 +61,13 @@ "command-exists": "^1.2.9", "concurrently": "^6.0.0", "fs-extra": "^9.0.0", - "husky": "^4.2.3", + "husky": "^6.0.0", "lerna": "^4.0.0", "lint-staged": "^10.1.0", "prettier": "^2.2.1", "recursive-readdir": "^2.2.2", "shx": "^0.3.2" }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, "prettier": "@spotify/prettier-config", "lint-staged": { "*.{js,jsx,ts,tsx}": [ diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 33ef204f63..1cc00e5ff9 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,57 @@ # example-app +## 0.2.28 + +### Patch Changes + +- Updated dependencies [062bbf90f] +- Updated dependencies [2cd70e164] +- Updated dependencies [0b033d07b] +- Updated dependencies [3be844496] +- Updated dependencies [5542de095] +- Updated dependencies [10c008a3a] +- Updated dependencies [81ef1d57b] +- Updated dependencies [ea21d46f0] +- Updated dependencies [e3fc89df6] +- Updated dependencies [f59a945b7] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/plugin-api-docs@0.4.13 + - @backstage/plugin-badges@0.2.1 + - @backstage/plugin-catalog@0.5.7 + - @backstage/plugin-catalog-import@0.5.6 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/plugin-circleci@0.2.14 + - @backstage/plugin-cloudbuild@0.2.14 + - @backstage/plugin-code-coverage@0.1.3 + - @backstage/plugin-cost-insights@0.9.1 + - @backstage/plugin-explore@0.3.5 + - @backstage/plugin-gcp-projects@0.2.6 + - @backstage/plugin-github-actions@0.4.6 + - @backstage/plugin-graphiql@0.2.11 + - @backstage/plugin-jenkins@0.4.3 + - @backstage/plugin-kafka@0.2.7 + - @backstage/plugin-kubernetes@0.4.4 + - @backstage/plugin-lighthouse@0.2.16 + - @backstage/plugin-newrelic@0.2.7 + - @backstage/plugin-org@0.3.13 + - @backstage/plugin-pagerduty@0.3.4 + - @backstage/plugin-rollbar@0.3.5 + - @backstage/plugin-scaffolder@0.9.4 + - @backstage/plugin-search@0.3.6 + - @backstage/plugin-sentry@0.3.10 + - @backstage/plugin-shortcuts@0.1.2 + - @backstage/plugin-tech-radar@0.3.11 + - @backstage/plugin-techdocs@0.9.2 + - @backstage/plugin-todo@0.1.1 + - @backstage/plugin-user-settings@0.2.10 + - @backstage/cli@0.6.11 + - @backstage/catalog-model@0.7.9 + ## 0.2.27 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index cbc8c5f1a3..6acba6f4cc 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,42 +1,42 @@ { "name": "example-app", - "version": "0.2.27", + "version": "0.2.28", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.7.8", - "@backstage/cli": "^0.6.10", - "@backstage/core": "^0.7.8", - "@backstage/integration-react": "^0.1.1", - "@backstage/plugin-api-docs": "^0.4.12", - "@backstage/plugin-badges": "^0.2.0", - "@backstage/plugin-catalog": "^0.5.6", - "@backstage/plugin-catalog-import": "^0.5.5", - "@backstage/plugin-catalog-react": "^0.1.5", - "@backstage/plugin-circleci": "^0.2.13", - "@backstage/plugin-cloudbuild": "^0.2.13", - "@backstage/plugin-code-coverage": "^0.1.2", - "@backstage/plugin-cost-insights": "^0.9.0", - "@backstage/plugin-explore": "^0.3.4", - "@backstage/plugin-gcp-projects": "^0.2.5", - "@backstage/plugin-github-actions": "^0.4.5", - "@backstage/plugin-graphiql": "^0.2.10", - "@backstage/plugin-jenkins": "^0.4.2", - "@backstage/plugin-kafka": "^0.2.6", - "@backstage/plugin-kubernetes": "^0.4.3", - "@backstage/plugin-lighthouse": "^0.2.15", - "@backstage/plugin-newrelic": "^0.2.6", - "@backstage/plugin-org": "^0.3.12", - "@backstage/plugin-pagerduty": "0.3.3", - "@backstage/plugin-rollbar": "^0.3.4", - "@backstage/plugin-scaffolder": "^0.9.3", - "@backstage/plugin-search": "^0.3.5", - "@backstage/plugin-sentry": "^0.3.9", - "@backstage/plugin-shortcuts": "^0.1.1", - "@backstage/plugin-tech-radar": "^0.3.10", - "@backstage/plugin-techdocs": "^0.9.1", - "@backstage/plugin-todo": "^0.1.0", - "@backstage/plugin-user-settings": "^0.2.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/cli": "^0.6.11", + "@backstage/core": "^0.7.9", + "@backstage/integration-react": "^0.1.2", + "@backstage/plugin-api-docs": "^0.4.13", + "@backstage/plugin-badges": "^0.2.1", + "@backstage/plugin-catalog": "^0.5.7", + "@backstage/plugin-catalog-import": "^0.5.6", + "@backstage/plugin-catalog-react": "^0.1.6", + "@backstage/plugin-circleci": "^0.2.14", + "@backstage/plugin-cloudbuild": "^0.2.14", + "@backstage/plugin-code-coverage": "^0.1.3", + "@backstage/plugin-cost-insights": "^0.9.1", + "@backstage/plugin-explore": "^0.3.5", + "@backstage/plugin-gcp-projects": "^0.2.6", + "@backstage/plugin-github-actions": "^0.4.6", + "@backstage/plugin-graphiql": "^0.2.11", + "@backstage/plugin-jenkins": "^0.4.3", + "@backstage/plugin-kafka": "^0.2.7", + "@backstage/plugin-kubernetes": "^0.4.4", + "@backstage/plugin-lighthouse": "^0.2.16", + "@backstage/plugin-newrelic": "^0.2.7", + "@backstage/plugin-org": "^0.3.13", + "@backstage/plugin-pagerduty": "0.3.4", + "@backstage/plugin-rollbar": "^0.3.5", + "@backstage/plugin-scaffolder": "^0.9.4", + "@backstage/plugin-search": "^0.3.6", + "@backstage/plugin-sentry": "^0.3.10", + "@backstage/plugin-shortcuts": "^0.1.2", + "@backstage/plugin-tech-radar": "^0.3.11", + "@backstage/plugin-techdocs": "^0.9.2", + "@backstage/plugin-todo": "^0.1.1", + "@backstage/plugin-user-settings": "^0.2.10", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -56,11 +56,11 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/test-utils": "^0.1.10", + "@backstage/test-utils": "^0.1.11", "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/jquery": "^3.3.34", "@types/node": "^14.14.32", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 3568adf099..9c66f387d9 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,62 @@ # @backstage/backend-common +## 0.8.0 + +### Minor Changes + +- 22fd8ce2a: Introducing: a standard API for App Integrators to configure cache stores and Plugin Developers to interact with them. + + Two cache stores are currently supported. + + - `memory`, which is a very simple in-memory key/value store, intended for local development. + - `memcache`, which can be used to connect to a memcache host. + + Configuring and working with cache stores is very similar to the process for database connections. + + ```yaml + backend: + cache: + store: memcache + connection: user:pass@cache.example.com:11211 + ``` + + ```typescript + import { CacheManager } from '@backstage/backend-common'; + + // Instantiating a cache client for a plugin. + const cacheManager = CacheManager.fromConfig(config); + const somePluginCache = cacheManager.forPlugin('somePlugin'); + const cacheClient = somePluginCache.getClient(); + + // Using the cache client: + const cachedValue = await cacheClient.get('someKey'); + if (cachedValue) { + return cachedValue; + } else { + const someValue = await someExpensiveProcess(); + await cacheClient.set('someKey', someValue); + } + await cacheClient.delete('someKey'); + ``` + + Cache clients deal with TTLs in milliseconds. A TTL can be provided as a defaultTtl when getting a client, or may be passed when setting specific objects. If no TTL is provided, data will be persisted indefinitely. + + ```typescript + // Getting a client with a default TTL + const cacheClient = somePluginCache.getClient({ + defaultTtl: 3600000, + }); + + // Setting a TTL on a per-object basis. + cacheClient.set('someKey', data, { ttl: 3600000 }); + ``` + + Configuring a cache store is optional. Even when no cache store is configured, the cache manager will dutifully pass plugins a manager that resolves a cache client that does not actually write or read any data. + +### Patch Changes + +- f9fb4a205: Prep work for mysql support in backend-common + ## 0.7.0 ### Minor Changes diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md index f0cb066dc0..23e22fe34b 100644 --- a/packages/backend-common/api-report.md +++ b/packages/backend-common/api-report.md @@ -16,6 +16,7 @@ import { GithubCredentialsProvider } from '@backstage/integration'; import { GitHubIntegration } from '@backstage/integration'; import { GitLabIntegration } from '@backstage/integration'; import * as http from 'http'; +import { JsonValue } from '@backstage/config'; import { Knex } from 'knex'; import { Logger } from 'winston'; import { MergeResult } from 'isomorphic-git'; @@ -62,6 +63,19 @@ export class BitbucketUrlReader implements UrlReader { toString(): string; } +// @public +export interface CacheClient { + delete(key: string): Promise; + get(key: string): Promise; + set(key: string, value: JsonValue, options?: CacheSetOptions): Promise; +} + +// @public +export class CacheManager { + forPlugin(pluginId: string): PluginCacheManager; + static fromConfig(config: Config): CacheManager; + } + // @public (undocumented) export const coloredFormat: winston.Logform.Format; @@ -238,6 +252,11 @@ export function loadBackendConfig(options: Options): Promise; // @public export function notFoundHandler(): RequestHandler; +// @public +export type PluginCacheManager = { + getClient: (options?: ClientOptions) => CacheClient; +}; + // @public export interface PluginDatabaseManager { getClient(): Promise; diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 41845f952e..875660a594 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -68,6 +68,20 @@ export interface Config { connection: string | object; }; + /** Cache connection configuration, select cache type using the `store` field */ + cache?: + | { + store: 'memory'; + } + | { + store: 'memcache'; + /** + * A memcache connection string in the form `user:pass@host:port`. + * @secret + */ + connection: string; + }; + cors?: { origin?: string | string[]; methods?: string | string[]; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index a144f1c23f..7e647e57d0 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.7.0", + "version": "0.8.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -51,6 +51,8 @@ "git-url-parse": "^11.4.4", "helmet": "^4.0.0", "isomorphic-git": "^1.8.0", + "keyv": "^4.0.3", + "keyv-memcache": "^1.2.5", "knex": "^0.95.1", "lodash": "^4.17.15", "logform": "^2.1.1", @@ -62,7 +64,8 @@ "stoppable": "^1.1.0", "tar": "^6.0.5", "unzipper": "^0.10.11", - "winston": "^3.2.1" + "winston": "^3.2.1", + "yn": "^4.0.0" }, "peerDependencies": { "pg-connection-string": "^2.3.0" @@ -73,8 +76,8 @@ } }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/test-utils": "^0.1.11", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", "@types/concat-stream": "^1.6.0", @@ -94,6 +97,7 @@ "jest": "^26.0.1", "mock-fs": "^4.13.0", "msw": "^0.21.2", + "mysql2": "^2.2.5", "recursive-readdir": "^2.2.2", "supertest": "^6.1.3" }, diff --git a/packages/backend-common/src/cache/CacheClient.test.ts b/packages/backend-common/src/cache/CacheClient.test.ts new file mode 100644 index 0000000000..81e8351b58 --- /dev/null +++ b/packages/backend-common/src/cache/CacheClient.test.ts @@ -0,0 +1,126 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { DefaultCacheClient } from './CacheClient'; +import Keyv from 'keyv'; + +describe('CacheClient', () => { + let client: Keyv; + const b64 = (k: string) => Buffer.from(k).toString('base64'); + + beforeEach(() => { + client = new Keyv(); + client.get = jest.fn(); + client.set = jest.fn(); + client.delete = jest.fn(); + }); + + afterEach(() => jest.resetAllMocks()); + + describe('CacheClient.get', () => { + it('calls client with normalized key', async () => { + const sut = new DefaultCacheClient({ client }); + const key = 'somekey'; + + await sut.get(key); + + expect(client.get).toHaveBeenCalledWith(b64(key)); + }); + + it('calls client with normalized key (very long key)', async () => { + const sut = new DefaultCacheClient({ client }); + const key = 'x'.repeat(251); + + await sut.get(key); + + const spy = client.get as jest.Mock; + const actualKey = spy.mock.calls[0][0]; + expect(actualKey).not.toEqual(b64(key)); + expect(actualKey.length).toBeLessThan(250); + }); + + it('rejects on underlying error', async () => { + const sut = new DefaultCacheClient({ client }); + const expectedError = new Error('Some runtime error'); + client.get = jest.fn().mockRejectedValue(expectedError); + + return expect(sut.get('someKey')).rejects.toEqual(expectedError); + }); + + it('resolves what underlying client resolves', async () => { + const sut = new DefaultCacheClient({ client }); + const expectedValue = 'some value'; + client.get = jest.fn().mockResolvedValue(expectedValue); + + const actualValue = await sut.get('someKey'); + + return expect(actualValue).toEqual(expectedValue); + }); + }); + + describe('CacheClient.set', () => { + it('calls client with normalized key', async () => { + const sut = new DefaultCacheClient({ client }); + const key = 'somekey'; + + await sut.set(key, {}); + + const spy = client.set as jest.Mock; + const actualKey = spy.mock.calls[0][0]; + expect(actualKey).toEqual(b64(key)); + }); + + it('passes ttl to client when given', async () => { + const sut = new DefaultCacheClient({ client }); + const expectedTtl = 3600; + + await sut.set('someKey', {}, { ttl: expectedTtl }); + + const spy = client.set as jest.Mock; + const actualTtl = spy.mock.calls[0][2]; + expect(actualTtl).toEqual(expectedTtl); + }); + + it('rejects on underlying error if configured', async () => { + const sut = new DefaultCacheClient({ client }); + const expectedError = new Error('Some runtime error'); + client.set = jest.fn().mockRejectedValue(expectedError); + + return expect(sut.set('someKey', {})).rejects.toEqual(expectedError); + }); + }); + + describe('CacheClient.delete', () => { + it('calls client with normalized key', async () => { + const sut = new DefaultCacheClient({ client }); + const key = 'somekey'; + + await sut.delete(key); + + const spy = client.delete as jest.Mock; + const actualKey = spy.mock.calls[0][0]; + expect(actualKey).toEqual(b64(key)); + }); + + it('rejects on underlying error if configured', async () => { + const sut = new DefaultCacheClient({ client }); + const expectedError = new Error('Some runtime error'); + client.delete = jest.fn().mockRejectedValue(expectedError); + + return expect(sut.delete('someKey')).rejects.toEqual(expectedError); + }); + }); +}); diff --git a/packages/backend-common/src/cache/CacheClient.ts b/packages/backend-common/src/cache/CacheClient.ts new file mode 100644 index 0000000000..49b5c367dd --- /dev/null +++ b/packages/backend-common/src/cache/CacheClient.ts @@ -0,0 +1,101 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { JsonValue } from '@backstage/config'; +import { createHash } from 'crypto'; +import Keyv from 'keyv'; + +type CacheClientArgs = { + client: Keyv; +}; + +type CacheSetOptions = { + /** + * Optional TTL in milliseconds. Defaults to the TTL provided when the client + * was set up (or no TTL if none are provided). + */ + ttl?: number; +}; + +/** + * A pre-configured, storage agnostic cache client suitable for use by + * Backstage plugins. + */ +export interface CacheClient { + /** + * Reads data from a cache store for the given key. If no data was found, + * returns undefined. + */ + get(key: string): Promise; + + /** + * Writes the given data to a cache store, associated with the given key. An + * optional TTL may also be provided, otherwise it defaults to the TTL that + * was provided when the client was instantiated. + */ + set(key: string, value: JsonValue, options?: CacheSetOptions): Promise; + + /** + * Removes the given key from the cache store. + */ + delete(key: string): Promise; +} + +/** + * A basic, concrete implementation of the CacheClient, suitable for almost + * all uses in Backstage. + */ +export class DefaultCacheClient implements CacheClient { + private readonly client: Keyv; + + constructor({ client }: CacheClientArgs) { + this.client = client; + } + + async get(key: string): Promise { + const k = this.getNormalizedKey(key); + return await this.client.get(k); + } + + async set( + key: string, + value: JsonValue, + opts: CacheSetOptions = {}, + ): Promise { + const k = this.getNormalizedKey(key); + await this.client.set(k, value, opts.ttl); + } + + async delete(key: string): Promise { + const k = this.getNormalizedKey(key); + await this.client.delete(k); + } + + /** + * Ensures keys are well-formed for any/all cache stores. + */ + private getNormalizedKey(candidateKey: string): string { + // Remove potentially invalid characters. + const wellFormedKey = Buffer.from(candidateKey).toString('base64'); + + // Memcache in particular doesn't do well with keys > 250 bytes. + if (wellFormedKey.length < 250) { + return wellFormedKey; + } + + return createHash('md5').update(candidateKey).digest('base64'); + } +} diff --git a/packages/backend-common/src/cache/CacheManager.test.ts b/packages/backend-common/src/cache/CacheManager.test.ts new file mode 100644 index 0000000000..ece4687ee6 --- /dev/null +++ b/packages/backend-common/src/cache/CacheManager.test.ts @@ -0,0 +1,166 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ConfigReader } from '@backstage/config'; +import Keyv from 'keyv'; +/* @ts-expect-error */ +import KeyvMemcache from 'keyv-memcache'; +import { DefaultCacheClient } from './CacheClient'; +import { CacheManager } from './CacheManager'; +import { NoStore } from './NoStore'; + +jest.createMockFromModule('keyv'); +jest.mock('keyv'); +jest.createMockFromModule('keyv-memcache'); +jest.mock('keyv-memcache'); +jest.mock('./CacheClient', () => { + return { + DefaultCacheClient: jest.fn(), + }; +}); + +describe('CacheManager', () => { + const defaultConfigOptions = { + backend: { + cache: { + store: 'memory', + }, + }, + }; + const defaultConfig = () => new ConfigReader(defaultConfigOptions); + + afterEach(() => jest.resetAllMocks()); + + describe('CacheManager.fromConfig', () => { + it('accesses the backend.cache key', () => { + const getOptionalString = jest.fn(); + const config = defaultConfig(); + config.getOptionalString = getOptionalString; + + CacheManager.fromConfig(config); + + expect(getOptionalString.mock.calls[0][0]).toEqual('backend.cache.store'); + expect(getOptionalString.mock.calls[1][0]).toEqual( + 'backend.cache.connection', + ); + }); + + it('does not require the backend.cache key', () => { + const config = new ConfigReader({ backend: {} }); + expect(() => { + CacheManager.fromConfig(config); + }).not.toThrowError(); + }); + + it('throws on unknown cache store', () => { + const config = new ConfigReader({ + backend: { cache: { store: 'notreal' } }, + }); + expect(() => { + CacheManager.fromConfig(config); + }).toThrowError(); + }); + }); + + describe('CacheManager.forPlugin', () => { + const manager = CacheManager.fromConfig(defaultConfig()); + + it('connects to a cache store scoped to the plugin', async () => { + const pluginId = 'test1'; + manager.forPlugin(pluginId).getClient(); + + const client = DefaultCacheClient as jest.Mock; + expect(client).toHaveBeenCalledTimes(1); + }); + + it('provides different plugins different cache clients', async () => { + const plugin1Id = 'test1'; + const plugin2Id = 'test2'; + const expectedTtl = 3600; + manager.forPlugin(plugin1Id).getClient({ defaultTtl: expectedTtl }); + manager.forPlugin(plugin2Id).getClient({ defaultTtl: expectedTtl }); + + const client = DefaultCacheClient as jest.Mock; + const cache = (Keyv as unknown) as jest.Mock; + expect(cache).toHaveBeenCalledTimes(2); + expect(client).toHaveBeenCalledTimes(2); + + const plugin1CallArgs = cache.mock.calls[0]; + const plugin2CallArgs = cache.mock.calls[1]; + expect(plugin1CallArgs[0].namespace).not.toEqual( + plugin2CallArgs[0].namespace, + ); + }); + }); + + describe('CacheManager.forPlugin stores', () => { + it('returns none client when no cache is configured', () => { + const manager = CacheManager.fromConfig( + new ConfigReader({ backend: {} }), + ); + const expectedNamespace = 'test-plugin'; + manager.forPlugin(expectedNamespace).getClient(); + + const cache = (Keyv as unknown) as jest.Mock; + const mockCalls = cache.mock.calls.splice(-1); + const callArgs = mockCalls[0]; + expect(callArgs[0].store).toBeInstanceOf(NoStore); + }); + + it('returns memory client when explicitly configured', () => { + const manager = CacheManager.fromConfig(defaultConfig()); + const expectedTtl = 3600; + const expectedNamespace = 'test-plugin'; + manager + .forPlugin(expectedNamespace) + .getClient({ defaultTtl: expectedTtl }); + + const cache = (Keyv as unknown) as jest.Mock; + const mockCalls = cache.mock.calls.splice(-1); + const callArgs = mockCalls[0]; + expect(callArgs[0]).toMatchObject({ + ttl: expectedTtl, + namespace: expectedNamespace, + }); + }); + + it('returns a memcache client when configured', () => { + const expectedHost = '127.0.0.1:11211'; + const manager = CacheManager.fromConfig( + new ConfigReader({ + backend: { + cache: { + store: 'memcache', + connection: expectedHost, + }, + }, + }), + ); + const expectedTtl = 3600; + manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); + + const cache = (Keyv as unknown) as jest.Mock; + const mockCacheCalls = cache.mock.calls.splice(-1); + expect(mockCacheCalls[0][0]).toMatchObject({ + ttl: expectedTtl, + }); + expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvMemcache); + const memcache = KeyvMemcache as jest.Mock; + const mockMemcacheCalls = memcache.mock.calls.splice(-1); + expect(mockMemcacheCalls[0][0]).toEqual(expectedHost); + }); + }); +}); diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-common/src/cache/CacheManager.ts new file mode 100644 index 0000000000..fbd7e3ba0a --- /dev/null +++ b/packages/backend-common/src/cache/CacheManager.ts @@ -0,0 +1,114 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import Keyv from 'keyv'; +// @ts-expect-error +import KeyvMemcache from 'keyv-memcache'; +import { DefaultCacheClient, CacheClient } from './CacheClient'; +import { NoStore } from './NoStore'; +import { PluginCacheManager } from './types'; + +/** + * Implements a Cache Manager which will automatically create new cache clients + * for plugins when requested. All requested cache clients are created with the + * connection details provided. + */ +export class CacheManager { + /** + * Keys represent supported `backend.cache.store` values, mapped to factories + * that return Keyv instances appropriate to the store. + */ + private readonly storeFactories = { + memcache: this.getMemcacheClient, + memory: this.getMemoryClient, + none: this.getNoneClient, + }; + + private readonly store: keyof CacheManager['storeFactories']; + private readonly connection: string; + + /** + * Creates a new CacheManager instance by reading from the `backend` config + * section, specifically the `.cache` key. + * + * @param config The loaded application configuration. + */ + static fromConfig(config: Config): CacheManager { + // If no `backend.cache` config is provided, instantiate the CacheManager + // with a "NoStore" cache client. + const store = config.getOptionalString('backend.cache.store') || 'none'; + const connectionString = + config.getOptionalString('backend.cache.connection') || ''; + return new CacheManager(store, connectionString); + } + + private constructor(store: string, connectionString: string) { + if (!this.storeFactories.hasOwnProperty(store)) { + throw new Error(`Unknown cache store: ${store}`); + } + this.store = store as keyof CacheManager['storeFactories']; + this.connection = connectionString; + } + + /** + * Generates a CacheManagerInstance for consumption by plugins. + * + * @param pluginId The plugin that the cache manager should be created for. Plugin names should be unique. + */ + forPlugin(pluginId: string): PluginCacheManager { + return { + getClient: (opts = {}): CacheClient => { + const concreteClient = this.getClientWithTtl(pluginId, opts.defaultTtl); + return new DefaultCacheClient({ + client: concreteClient, + }); + }, + }; + } + + private getClientWithTtl(pluginId: string, ttl: number | undefined): Keyv { + return this.storeFactories[this.store].call(this, pluginId, ttl); + } + + private getMemcacheClient( + pluginId: string, + defaultTtl: number | undefined, + ): Keyv { + return new Keyv({ + namespace: pluginId, + ttl: defaultTtl, + store: new KeyvMemcache(this.connection), + }); + } + + private getMemoryClient( + pluginId: string, + defaultTtl: number | undefined, + ): Keyv { + return new Keyv({ + namespace: pluginId, + ttl: defaultTtl, + }); + } + + private getNoneClient(pluginId: string): Keyv { + return new Keyv({ + namespace: pluginId, + store: new NoStore(), + }); + } +} diff --git a/packages/backend-common/src/cache/NoStore.ts b/packages/backend-common/src/cache/NoStore.ts new file mode 100644 index 0000000000..c89e814c93 --- /dev/null +++ b/packages/backend-common/src/cache/NoStore.ts @@ -0,0 +1,41 @@ +/* + * Copyright 2021 Spotify AB + * + * 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. + */ + +/** + * Storage class compatible with Keyv which always results in a no-op. This is + * used when no cache store is configured in a Backstage backend instance. + */ +export class NoStore extends Map { + clear(): void { + return; + } + + delete(_key: string): boolean { + return false; + } + + get(_key: string) { + return; + } + + has(_key: string): boolean { + return false; + } + + set(_key: string, _value: any): this { + return this; + } +} diff --git a/packages/backend-common/src/cache/index.ts b/packages/backend-common/src/cache/index.ts new file mode 100644 index 0000000000..0cc8178b1a --- /dev/null +++ b/packages/backend-common/src/cache/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { CacheClient } from './CacheClient'; +export { CacheManager } from './CacheManager'; +export type { PluginCacheManager } from './types'; diff --git a/packages/backend-common/src/cache/types.ts b/packages/backend-common/src/cache/types.ts new file mode 100644 index 0000000000..4dd3cf7d1b --- /dev/null +++ b/packages/backend-common/src/cache/types.ts @@ -0,0 +1,40 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { CacheClient } from './CacheClient'; + +type ClientOptions = { + /** + * An optional default TTL (in milliseconds) to be set when getting a client + * instance. If not provided, data will persist indefinitely by default (or + * can be configured per entry at set-time). + */ + defaultTtl?: number; +}; + +/** + * The PluginCacheManager manages access to cache stores that Plugins get. + */ +export type PluginCacheManager = { + /** + * getClient provides backend plugins cache connections for itself. + * + * The purpose of this method is to allow plugins to get isolated data + * stores so that plugins are discouraged from cache-level integration + * and/or cache key collisions. + */ + getClient: (options?: ClientOptions) => CacheClient; +}; diff --git a/packages/backend-common/src/database/connection.test.ts b/packages/backend-common/src/database/connection.test.ts index 9810ff9bef..6ab163ff7f 100644 --- a/packages/backend-common/src/database/connection.test.ts +++ b/packages/backend-common/src/database/connection.test.ts @@ -46,11 +46,11 @@ describe('database connection', () => { ).toBeTruthy(); }); - it('tries to create a mysql connection as a passthrough', () => { + it('returns a mysql connection', () => { expect(() => createDatabaseClient( new ConfigReader({ - client: 'mysql', + client: 'mysql2', connection: { host: '127.0.0.1', user: 'foo', @@ -59,7 +59,7 @@ describe('database connection', () => { }, }), ), - ).toThrowError(/Cannot find module 'mysql'/); + ).toBeTruthy(); }); it('accepts overrides', () => { diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts index 3502e21674..fb16915cf4 100644 --- a/packages/backend-common/src/database/connection.ts +++ b/packages/backend-common/src/database/connection.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import knexFactory, { Knex } from 'knex'; import { Config } from '@backstage/config'; +import knexFactory, { Knex } from 'knex'; import { mergeDatabaseConfig } from './config'; +import { createMysqlDatabaseClient, ensureMysqlDatabaseExists } from './mysql'; import { createPgDatabaseClient, ensurePgDatabaseExists } from './postgres'; import { createSqliteDatabaseClient } from './sqlite3'; @@ -36,6 +37,8 @@ export function createDatabaseClient( if (client === 'pg') { return createPgDatabaseClient(dbConfig, overrides); + } else if (client === 'mysql' || client === 'mysql2') { + return createMysqlDatabaseClient(dbConfig, overrides); } else if (client === 'sqlite3') { return createSqliteDatabaseClient(dbConfig, overrides); } @@ -60,6 +63,8 @@ export async function ensureDatabaseExists( if (client === 'pg') { return ensurePgDatabaseExists(dbConfig, ...databases); + } else if (client === 'mysql' || client === 'mysql2') { + return ensureMysqlDatabaseExists(dbConfig, ...databases); } return undefined; diff --git a/packages/backend-common/src/database/mysql.test.ts b/packages/backend-common/src/database/mysql.test.ts new file mode 100644 index 0000000000..9e23585d5b --- /dev/null +++ b/packages/backend-common/src/database/mysql.test.ts @@ -0,0 +1,188 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config, ConfigReader } from '@backstage/config'; +import { + buildMysqlDatabaseConfig, + createMysqlDatabaseClient, + getMysqlConnectionConfig, + parseMysqlConnectionString, +} from './mysql'; + +describe('mysql', () => { + const createMockConnection = () => ({ + host: 'acme', + user: 'foo', + password: 'bar', + database: 'foodb', + }); + + const createMockConnectionString = () => 'mysql://foo:bar@acme:3306/foodb'; + + const createConfig = (connection: any): Config => + new ConfigReader({ client: 'mysql2', connection }); + + describe('buildMysqlDatabaseConfig', () => { + it('builds a mysql config', () => { + const mockConnection = createMockConnection(); + + expect(buildMysqlDatabaseConfig(createConfig(mockConnection))).toEqual({ + client: 'mysql2', + connection: mockConnection, + useNullAsDefault: true, + }); + }); + + it('builds a connection string config', () => { + const mockConnectionString = createMockConnectionString(); + + expect( + buildMysqlDatabaseConfig(createConfig(mockConnectionString)), + ).toEqual({ + client: 'mysql2', + connection: mockConnectionString, + useNullAsDefault: true, + }); + }); + + it('overrides the database name', () => { + const mockConnection = createMockConnection(); + + expect( + buildMysqlDatabaseConfig(createConfig(mockConnection), { + connection: { database: 'other_db' }, + }), + ).toEqual({ + client: 'mysql2', + connection: { + ...mockConnection, + database: 'other_db', + }, + useNullAsDefault: true, + }); + }); + + it('adds additional config settings', () => { + const mockConnection = createMockConnection(); + + expect( + buildMysqlDatabaseConfig(createConfig(mockConnection), { + connection: { database: 'other_db' }, + pool: { min: 0, max: 7 }, + debug: true, + }), + ).toEqual({ + client: 'mysql2', + connection: { + ...mockConnection, + database: 'other_db', + }, + useNullAsDefault: true, + pool: { min: 0, max: 7 }, + debug: true, + }); + }); + + it('overrides the database from connection string', () => { + const mockConnectionString = createMockConnectionString(); + const mockConnection = createMockConnection(); + + expect( + buildMysqlDatabaseConfig(createConfig(mockConnectionString), { + connection: { database: 'other_db' }, + }), + ).toEqual({ + client: 'mysql2', + connection: { + ...mockConnection, + port: 3306, + database: 'other_db', + }, + useNullAsDefault: true, + }); + }); + }); + + describe('getMysqlConnectionConfig', () => { + it('returns the connection object back', () => { + const mockConnection = createMockConnection(); + const config = createConfig(mockConnection); + + expect(getMysqlConnectionConfig(config)).toEqual(mockConnection); + }); + + it('does not parse the connection string', () => { + const mockConnection = createMockConnection(); + const config = createConfig(mockConnection); + + expect(getMysqlConnectionConfig(config, true)).toEqual(mockConnection); + }); + + it('automatically parses the connection string', () => { + const mockConnection = createMockConnection(); + const mockConnectionString = createMockConnectionString(); + const config = createConfig(mockConnectionString); + + expect(getMysqlConnectionConfig(config)).toEqual({ + ...mockConnection, + port: 3306, + }); + }); + + it('parses the connection string', () => { + const mockConnection = createMockConnection(); + const mockConnectionString = createMockConnectionString(); + const config = createConfig(mockConnectionString); + + expect(getMysqlConnectionConfig(config, true)).toEqual({ + ...mockConnection, + port: 3306, + }); + }); + }); + + describe('createMysqlDatabaseClient', () => { + it('creates a mysql knex instance', () => { + expect( + createMysqlDatabaseClient( + createConfig({ + host: 'acme', + user: 'foo', + password: 'bar', + database: 'foodb', + }), + ), + ).toBeTruthy(); + }); + }); + + describe('parseMysqlConnectionString', () => { + it('parses a connection string uri', () => { + expect( + parseMysqlConnectionString( + 'mysql://u:pass@foobar:3307/dbname?ssl=require', + ), + ).toEqual({ + host: 'foobar', + user: 'u', + password: 'pass', + port: 3307, + database: 'dbname', + ssl: 'require', + }); + }); + }); +}); diff --git a/packages/backend-common/src/database/mysql.ts b/packages/backend-common/src/database/mysql.ts new file mode 100644 index 0000000000..be4632baf6 --- /dev/null +++ b/packages/backend-common/src/database/mysql.ts @@ -0,0 +1,161 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import knexFactory, { Knex } from 'knex'; +import { mergeDatabaseConfig } from './config'; +import yn from 'yn'; + +/** + * Creates a knex mysql database connection + * + * @param dbConfig The database config + * @param overrides Additional options to merge with the config + */ +export function createMysqlDatabaseClient( + dbConfig: Config, + overrides?: Knex.Config, +) { + const knexConfig = buildMysqlDatabaseConfig(dbConfig, overrides); + const database = knexFactory(knexConfig); + return database; +} + +/** + * Builds a knex mysql database connection + * + * @param dbConfig The database config + * @param overrides Additional options to merge with the config + */ +export function buildMysqlDatabaseConfig( + dbConfig: Config, + overrides?: Knex.Config, +) { + return mergeDatabaseConfig( + dbConfig.get(), + { + connection: getMysqlConnectionConfig(dbConfig, !!overrides), + useNullAsDefault: true, + }, + overrides, + ); +} + +/** + * Gets the mysql connection config + * + * @param dbConfig The database config + * @param parseConnectionString Flag to explicitly control connection string parsing + */ +export function getMysqlConnectionConfig( + dbConfig: Config, + parseConnectionString?: boolean, +): Knex.MySqlConnectionConfig | string { + const connection = dbConfig.get('connection') as any; + const isConnectionString = + typeof connection === 'string' || connection instanceof String; + const autoParse = typeof parseConnectionString !== 'boolean'; + + const shouldParseConnectionString = autoParse + ? isConnectionString + : parseConnectionString && isConnectionString; + + return shouldParseConnectionString + ? parseMysqlConnectionString(connection as string) + : connection; +} + +/** + * Parses a mysql connection string. + * + * e.g. mysql://examplename:somepassword@examplehost:3306/dbname + * @param connectionString The mysql connection string + */ +export function parseMysqlConnectionString( + connectionString: string, +): Knex.MySqlConnectionConfig { + try { + const { + protocol, + username, + password, + port, + hostname, + pathname, + searchParams, + } = new URL(connectionString); + + if (protocol !== 'mysql:') { + throw new Error(`Unknown protocol ${protocol}`); + } else if (!username || !password) { + throw new Error(`Missing username/password`); + } else if (!pathname.match(/^\/[^/]+$/)) { + throw new Error(`Expected single path segment`); + } + + const result: Knex.MySqlConnectionConfig = { + user: username, + password, + host: hostname, + port: Number(port || 3306), + database: decodeURIComponent(pathname.substr(1)), + }; + + const ssl = searchParams.get('ssl'); + if (ssl) { + result.ssl = ssl; + } + + const debug = searchParams.get('debug'); + if (debug) { + result.debug = yn(debug); + } + + return result; + } catch (e) { + throw new InputError( + `Error while parsing MySQL connection string, ${e}`, + e, + ); + } +} + +/** + * Creates the missing mysql database if it does not exist + * + * @param dbConfig The database config + * @param databases The names of the databases to create + */ +export async function ensureMysqlDatabaseExists( + dbConfig: Config, + ...databases: Array +) { + const admin = createMysqlDatabaseClient(dbConfig, { + connection: { + database: (null as unknown) as string, + }, + }); + + try { + const ensureDatabase = async (database: string) => { + await admin.raw(`CREATE DATABASE IF NOT EXISTS ??`, [database]); + }; + await Promise.all(databases.map(ensureDatabase)); + } finally { + await admin.destroy(); + } +} diff --git a/packages/backend-common/src/index.ts b/packages/backend-common/src/index.ts index 29b1062750..dab981a824 100644 --- a/packages/backend-common/src/index.ts +++ b/packages/backend-common/src/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +export * from './cache'; export * from './config'; export * from './database'; export * from './discovery'; diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index ac65632142..977588b555 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,36 @@ # example-backend +## 0.2.28 + +### Patch Changes + +- Updated dependencies [062bbf90f] +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [82ca1ac22] +- Updated dependencies [f9fb4a205] +- Updated dependencies [9a207f052] +- Updated dependencies [16be1d093] +- Updated dependencies [fd39d4662] +- Updated dependencies [f9f9d633d] + - @backstage/plugin-scaffolder-backend@0.11.1 + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + - @backstage/plugin-catalog-backend@0.9.0 + - @backstage/plugin-kubernetes-backend@0.3.7 + - example-app@0.2.28 + - @backstage/plugin-app-backend@0.3.13 + - @backstage/plugin-auth-backend@0.3.10 + - @backstage/plugin-badges-backend@0.1.4 + - @backstage/plugin-code-coverage-backend@0.1.5 + - @backstage/plugin-graphql-backend@0.1.8 + - @backstage/plugin-kafka-backend@0.2.5 + - @backstage/plugin-proxy-backend@0.2.8 + - @backstage/plugin-rollbar-backend@0.1.11 + - @backstage/plugin-search-backend@0.1.5 + - @backstage/plugin-techdocs-backend@0.8.1 + - @backstage/plugin-todo-backend@0.1.5 + ## 0.2.27 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index 6f70db7b19..4044a6632f 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.27", + "version": "0.2.28", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -27,30 +27,30 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/catalog-client": "^0.3.11", - "@backstage/catalog-model": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", - "@backstage/plugin-app-backend": "^0.3.12", - "@backstage/plugin-auth-backend": "^0.3.9", - "@backstage/plugin-badges-backend": "^0.1.3", - "@backstage/plugin-catalog-backend": "^0.8.2", - "@backstage/plugin-code-coverage-backend": "^0.1.4", - "@backstage/plugin-graphql-backend": "^0.1.7", - "@backstage/plugin-kubernetes-backend": "^0.3.6", - "@backstage/plugin-kafka-backend": "^0.2.4", - "@backstage/plugin-proxy-backend": "^0.2.7", - "@backstage/plugin-rollbar-backend": "^0.1.10", - "@backstage/plugin-scaffolder-backend": "^0.11.0", - "@backstage/plugin-search-backend": "^0.1.4", + "@backstage/plugin-app-backend": "^0.3.13", + "@backstage/plugin-auth-backend": "^0.3.10", + "@backstage/plugin-badges-backend": "^0.1.4", + "@backstage/plugin-catalog-backend": "^0.9.0", + "@backstage/plugin-code-coverage-backend": "^0.1.5", + "@backstage/plugin-graphql-backend": "^0.1.8", + "@backstage/plugin-kubernetes-backend": "^0.3.7", + "@backstage/plugin-kafka-backend": "^0.2.5", + "@backstage/plugin-proxy-backend": "^0.2.8", + "@backstage/plugin-rollbar-backend": "^0.1.11", + "@backstage/plugin-scaffolder-backend": "^0.11.1", + "@backstage/plugin-search-backend": "^0.1.5", "@backstage/plugin-search-backend-node": "^0.1.3", - "@backstage/plugin-techdocs-backend": "^0.8.0", - "@backstage/plugin-todo-backend": "^0.1.4", + "@backstage/plugin-techdocs-backend": "^0.8.1", + "@backstage/plugin-todo-backend": "^0.1.5", "@gitbeaker/node": "^28.0.2", "@octokit/rest": "^18.5.3", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.1", - "example-app": "^0.2.27", + "example-app": "^0.2.28", "express": "^4.17.1", "express-promise-router": "^4.1.0", "knex": "^0.95.1", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/dockerode": "^3.2.1", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 00ed248df5..d43c8f0101 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -24,6 +24,7 @@ import Router from 'express-promise-router'; import { + CacheManager, createServiceBuilder, getRootLogger, loadBackendConfig, @@ -59,11 +60,13 @@ function makeCreateEnv(config: Config) { root.info(`Created UrlReader ${reader}`); const databaseManager = SingleConnectionDatabaseManager.fromConfig(config); + const cacheManager = CacheManager.fromConfig(config); return (plugin: string): PluginEnvironment => { const logger = root.child({ type: 'plugin', plugin }); const database = databaseManager.forPlugin(plugin); - return { logger, database, config, reader, discovery }; + const cache = cacheManager.forPlugin(plugin); + return { logger, cache, database, config, reader, discovery }; }; } diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index f63b9dd780..356dd08d5f 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -17,6 +17,7 @@ import { Logger } from 'winston'; import { Config } from '@backstage/config'; import { + PluginCacheManager, PluginDatabaseManager, PluginEndpointDiscovery, UrlReader, @@ -24,6 +25,7 @@ import { export type PluginEnvironment = { logger: Logger; + cache: PluginCacheManager; database: PluginDatabaseManager; config: Config; reader: UrlReader; diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index e289c5a0b3..312a329649 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/catalog-model +## 0.7.9 + +### Patch Changes + +- 10c008a3a: Renamed parameters to input in template schema +- 16be1d093: Improve error messages when schema validation fails + ## 0.7.8 ### Patch Changes diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 2b56879f48..00eaf8410e 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -112,6 +112,7 @@ export type Entity = { metadata: EntityMeta; spec?: JsonObject; relations?: EntityRelation[]; + status?: Record; }; // @public diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index ac3b046a32..6b5b096cb6 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.7.8", + "version": "0.7.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,7 +39,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/catalog-model/src/entity/Entity.ts b/packages/catalog-model/src/entity/Entity.ts index 7abe1f9c0c..1b5d38dee9 100644 --- a/packages/catalog-model/src/entity/Entity.ts +++ b/packages/catalog-model/src/entity/Entity.ts @@ -48,6 +48,14 @@ export type Entity = { * The relations that this entity has with other entities. */ relations?: EntityRelation[]; + + /** + * The current status of the entity, as claimed by various sources. + * + * The keys are implementation defined and the values can be any JSON object + * with semantics that match that implementation. + */ + status?: Record; }; /** diff --git a/packages/catalog-model/src/entity/util.ts b/packages/catalog-model/src/entity/util.ts index 2c63cc4c49..116913c85c 100644 --- a/packages/catalog-model/src/entity/util.ts +++ b/packages/catalog-model/src/entity/util.ts @@ -42,11 +42,9 @@ export function generateEntityEtag(): string { * the next version of this entity. * * Significance, in this case, means that we do not compare generated fields - * such as uid, etag and generation, and we only check that no new annotations - * are added or existing annotations were changed (since they are effectively - * merged when doing updates). + * such as uid, etag and generation. * - * Note that this comparison does NOT take state, relations or similar into + * Note that this comparison does NOT take status, relations or similar into * account. It only compares the actual input entity data, i.e. metadata and * spec. * @@ -86,7 +84,9 @@ export function entityHasChanges(previous: Entity, next: Entity): boolean { // Remove things that we explicitly do not compare delete e1.relations; + delete e1.status; delete e2.relations; + delete e2.status; return !lodash.isEqual(e1, e2); } diff --git a/packages/catalog-model/src/schema/Entity.schema.json b/packages/catalog-model/src/schema/Entity.schema.json index 803e025796..e6b004dfd0 100644 --- a/packages/catalog-model/src/schema/Entity.schema.json +++ b/packages/catalog-model/src/schema/Entity.schema.json @@ -62,6 +62,15 @@ "items": { "$ref": "common#relation" } + }, + "status": { + "type": "object", + "description": "The current status of the entity, as claimed by various sources.", + "patternProperties": { + "^.+$": { + "$ref": "common#status" + } + } } } } diff --git a/packages/catalog-model/src/schema/shared/common.schema.json b/packages/catalog-model/src/schema/shared/common.schema.json index 84ae8a4147..cb4d82cdb9 100644 --- a/packages/catalog-model/src/schema/shared/common.schema.json +++ b/packages/catalog-model/src/schema/shared/common.schema.json @@ -29,7 +29,7 @@ "$id": "#relation", "type": "object", "description": "A directed relation from one entity to another.", - "required": ["type", "source", "target"], + "required": ["type", "target"], "additionalProperties": false, "properties": { "type": { @@ -38,13 +38,16 @@ "pattern": "^\\w+$", "description": "The type of relation." }, - "source": { - "$ref": "#reference" - }, "target": { "$ref": "#reference" } } + }, + "status": { + "$id": "#status", + "type": "object", + "description": "A specific status of an entity.", + "additionalProperties": true } } } diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 996513f60e..cf28960f13 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/cli +## 0.6.11 + +### Patch Changes + +- 2cd70e164: Add context for versions:bump on what version it was bumped to. Updated tests for the same. +- 3be844496: chore: bump `ts-node` versions to 9.1.1 +- e3fc89df6: update plugins created to use react-use 17.2.4 + ## 0.6.10 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index cd0385196d..b17bc8cbda 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.6.10", + "version": "0.6.11", "private": false, "publishConfig": { "access": "public" @@ -117,11 +117,11 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/config": "^0.1.5", - "@backstage/core": "^0.7.8", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/core": "^0.7.9", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@backstage/theme": "^0.2.7", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index 1b4f8ea6ab..1d2766eab4 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -39,7 +39,7 @@ "@backstage/test-utils": "^{{version '@backstage/test-utils'}}", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "msw": "^0.21.2", diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 16d7923d0b..77646ee521 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -36,7 +36,7 @@ "ajv": "^7.0.3", "fs-extra": "^9.0.0", "json-schema": "^0.3.0", - "json-schema-merge-allof": "^0.7.0", + "json-schema-merge-allof": "^0.8.1", "typescript-json-schema": "^0.49.0", "yaml": "^1.9.2", "yup": "^0.29.3" diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index fab82a77e5..3652cf6bc0 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/core-api +## 0.2.18 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages + ## 0.2.17 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 13d7efe77b..b5cd8cba69 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.17", + "version": "0.2.18", "private": false, "publishConfig": { "access": "public", @@ -42,13 +42,13 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.9", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/test-utils": "^0.1.11", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/zen-observable": "^0.8.0", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index a215c7cb8e..51c6b3f935 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/core +## 0.7.9 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 889d89b6e: Fix state persisted in the URL make search input in the table toolbar lose their + focus. +- 3f988cb63: Add count of older messages when multiple messages exist in AlertDisplay +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [675a569a9] + - @backstage/core-api@0.2.18 + ## 0.7.8 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 315441a87c..e418a969ce 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.7.8", + "version": "0.7.9", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.5", - "@backstage/core-api": "^0.2.17", + "@backstage/core-api": "^0.2.18", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", @@ -71,11 +71,11 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/classnames": "^2.2.9", "@types/d3-selection": "^2.0.0", "@types/d3-shape": "^2.0.0", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 8c3dc14245..2e62d43436 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/create-app +## 0.3.22 + +### Patch Changes + +- 3be844496: chore: bump `ts-node` versions to 9.1.1 +- Updated dependencies [062bbf90f] +- Updated dependencies [2cd70e164] +- Updated dependencies [0b033d07b] +- Updated dependencies [3be844496] +- Updated dependencies [5542de095] +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [82ca1ac22] +- Updated dependencies [81ef1d57b] +- Updated dependencies [f9fb4a205] +- Updated dependencies [e3fc89df6] +- Updated dependencies [9a207f052] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [fd39d4662] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/test-utils@0.1.11 + - @backstage/plugin-api-docs@0.4.13 + - @backstage/plugin-catalog@0.5.7 + - @backstage/plugin-catalog-import@0.5.6 + - @backstage/plugin-explore@0.3.5 + - @backstage/plugin-github-actions@0.4.6 + - @backstage/plugin-lighthouse@0.2.16 + - @backstage/plugin-scaffolder@0.9.4 + - @backstage/plugin-scaffolder-backend@0.11.1 + - @backstage/plugin-search@0.3.6 + - @backstage/plugin-tech-radar@0.3.11 + - @backstage/plugin-techdocs@0.9.2 + - @backstage/plugin-user-settings@0.2.10 + - @backstage/cli@0.6.11 + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + - @backstage/plugin-catalog-backend@0.9.0 + - @backstage/plugin-app-backend@0.3.13 + - @backstage/plugin-auth-backend@0.3.10 + - @backstage/plugin-proxy-backend@0.2.8 + - @backstage/plugin-rollbar-backend@0.1.11 + - @backstage/plugin-techdocs-backend@0.8.1 + ## 0.3.21 ### Patch Changes @@ -15,8 +62,6 @@ + import { DockerContainerRunner } from '@backstage/backend-common'; - // ... - export default async function createPlugin({ logger, config, @@ -76,7 +121,6 @@ + SingleHostDiscovery, + } from '@backstage/backend-common'; - export default async function createPlugin({ logger, config, diff --git a/packages/create-app/package.json b/packages/create-app/package.json index b96b4e2065..b850893f9c 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.21", + "version": "0.3.22", "private": false, "publishConfig": { "access": "public" diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 3e8b8a96a0..ac88847489 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/dev-utils +## 0.1.14 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/test-utils@0.1.11 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.1.13 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 3845da7e9b..fb1f8a26dc 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.1.13", + "version": "0.1.14", "private": false, "publishConfig": { "access": "public", @@ -29,17 +29,17 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.0", - "@backstage/catalog-model": "^0.7.3", - "@backstage/integration-react": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.1.1", - "@backstage/test-utils": "^0.1.8", + "@backstage/core": "^0.7.9", + "@backstage/catalog-model": "^0.7.9", + "@backstage/integration-react": "^0.1.2", + "@backstage/plugin-catalog-react": "^0.1.6", + "@backstage/test-utils": "^0.1.11", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/react": "^16.9", "react": "^16.12.0", "react-dom": "^16.12.0", @@ -48,7 +48,7 @@ "react-router-dom": "6.0.0-beta.0" }, "devDependencies": { - "@backstage/cli": "^0.6.3", + "@backstage/cli": "^0.6.11", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md new file mode 100644 index 0000000000..54aa3b3ab1 --- /dev/null +++ b/packages/integration-react/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/integration-react + +## 0.1.2 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 7833952e8e..fe831ca24f 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/config": "^0.1.2", - "@backstage/core": "^0.7.0", + "@backstage/core": "^0.7.9", "@backstage/integration": "^0.5.0", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -32,12 +32,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.3", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.8", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "msw": "^0.21.2", diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 644a3ebb5f..ec47f22475 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/techdocs-common +## 0.6.1 + +### Patch Changes + +- e04f1ccfb: Fixed a bug that prevented loading static assets from GCS, S3, Azure, and OpenStackSwift whose keys contain spaces or other special characters. +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.6.0 ### Minor Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index 7c12f6f675..e6debbb817 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.6.0", + "version": "0.6.1", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -38,8 +38,8 @@ "dependencies": { "@azure/identity": "^1.2.2", "@azure/storage-blob": "^12.4.0", - "@backstage/backend-common": "^0.7.0", - "@backstage/catalog-model": "^0.7.8", + "@backstage/backend-common": "^0.8.0", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.2", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/fs-extra": "^9.0.5", "@types/git-url-parse": "^9.0.0", "@types/js-yaml": "^4.0.0", diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index d2c2748449..9a77665e28 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/test-utils +## 0.1.11 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- Updated dependencies [062bbf90f] +- Updated dependencies [675a569a9] + - @backstage/core-api@0.2.18 + ## 0.1.10 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index a81455df01..7b7b73a64a 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.10", + "version": "0.1.11", "private": false, "publishConfig": { "access": "public", @@ -29,13 +29,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-api": "^0.2.16", + "@backstage/core-api": "^0.2.18", "@backstage/test-utils-core": "^0.1.1", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/react": "^16.9", "msw": "^0.21.3", "react": "^16.12.0", @@ -45,7 +45,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.7", + "@backstage/cli": "^0.6.11", "@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 f268ba1c4d..94fd3fe74a 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-api-docs +## 0.4.13 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 889d89b6e: Fix state persisted in the URL make search input in the table toolbar lose their + focus. +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.4.12 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 9cccb07d56..9610ae4e10 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.4.12", + "version": "0.4.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ }, "dependencies": { "@asyncapi/react-component": "^0.23.0", - "@backstage/catalog-model": "^0.7.7", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.4", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.11.0", @@ -49,12 +49,12 @@ "swagger-ui-react": "^3.37.2" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/swagger-ui-react": "^3.23.3", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 73fa9fc3b1..ede16b6a5e 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-app-backend +## 0.3.13 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [f9fb4a205] + - @backstage/backend-common@0.8.0 + ## 0.3.12 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 84fe743550..60fa678dd0 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.3.12", + "version": "0.3.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/config-loader": "^0.6.1", "@backstage/config": "^0.1.5", "@types/express": "^4.17.6", @@ -40,7 +40,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/supertest": "^2.0.8", "msw": "^0.20.5", "supertest": "^6.1.3" diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index e51942f742..1003140dec 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-auth-backend +## 0.3.10 + +### Patch Changes + +- Updated dependencies [062bbf90f] +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/test-utils@0.1.11 + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.3.9 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 41a47c7788..1b0ad25a84 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.9", + "version": "0.3.10", "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.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/catalog-client": "^0.3.11", - "@backstage/catalog-model": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/test-utils": "^0.1.10", + "@backstage/test-utils": "^0.1.11", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", "compression": "^1.7.4", @@ -68,7 +68,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index abbc4e70bd..d171d0d590 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-badges-backend +## 0.1.4 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.1.3 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 347f8b9219..d7e0df88b5 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-badges-backend", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/catalog-client": "^0.3.11", - "@backstage/catalog-model": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@types/express": "^4.17.6", @@ -45,7 +45,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/badges-backend/src/badges.test.ts b/plugins/badges-backend/src/badges.test.ts index 15be96154e..1d5e57141d 100644 --- a/plugins/badges-backend/src/badges.test.ts +++ b/plugins/badges-backend/src/badges.test.ts @@ -65,4 +65,28 @@ describe('BadgeFactories', () => { expect(badge.kind).toEqual('entity'); } }); + + it('returns valid link for entity', () => { + const entity: Entity = { + apiVersion: 'v1', + kind: 'service', + metadata: { + name: 'test', + }, + }; + + const context: BadgeContext = { + badgeUrl: '/dummy/url', + config, + entity, + }; + + expect.assertions(Object.keys(badgeFactories).length); + for (const badgeFactory of Object.values(badgeFactories)) { + const badge = badgeFactory.createBadge(context); + expect(badge.link).toContain( + 'http://localhost/catalog/default/service/test', + ); + } + }); }); diff --git a/plugins/badges-backend/src/badges.ts b/plugins/badges-backend/src/badges.ts index f8637dfe88..ccd5f55855 100644 --- a/plugins/badges-backend/src/badges.ts +++ b/plugins/badges-backend/src/badges.ts @@ -24,8 +24,8 @@ function appTitle(context: BadgeContext): string { function entityUrl(context: BadgeContext): string { const e = context.entity!; - const entityUri = `${e.kind}/${ - e.metadata.namespace || ENTITY_DEFAULT_NAMESPACE + const entityUri = `${e.metadata.namespace || ENTITY_DEFAULT_NAMESPACE}/${ + e.kind }/${e.metadata.name}`; const catalogUrl = `${context.config.getString('app.baseUrl')}/catalog`; return `${catalogUrl}/${entityUri}`.toLowerCase(); diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index a0efd43ccc..c5ac19b4ed 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-badges +## 0.2.1 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.2.0 ### Minor Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 24660899e2..72aa2df787 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-badges", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.6", - "@backstage/core": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -34,12 +34,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index ecb26efd50..61a0af36ed 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-bitrise +## 0.1.3 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.1.2 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 45b32a11d1..1414f729fd 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitrise", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.2", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.2", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -37,12 +37,12 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/recharts": "^1.8.15", diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 8d40d7c4bb..0018d83399 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend +## 0.9.0 + +### Minor Changes + +- 9a207f052: Port `GithubOrgReaderProcessor` to support configuration via + [`integrations`](https://backstage.io/docs/integrations/github/locations) in + addition to [`catalog.processors.githubOrg.providers`](https://backstage.io/docs/integrations/github/org#configuration). + The `integrations` package supports authentication with both personal access + tokens and GitHub apps. + + This deprecates the `catalog.processors.githubOrg.providers` configuration. + A [`integrations` configuration](https://backstage.io/docs/integrations/github/locations) + for the same host takes precedence over the provider configuration. + You might need to add additional scopes for the credentials. + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.8.2 ### Patch Changes diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 564dd7511e..20fcd975cf 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -114,6 +114,8 @@ export interface Config { processors?: { /** * GithubOrgReaderProcessor configuration + * + * @deprecated Configure a GitHub integration instead. */ githubOrg?: { /** diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index c8e17bb943..dc9397de82 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.8.2", + "version": "0.9.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ }, "dependencies": { "@azure/msal-node": "^1.0.0-beta.3", - "@backstage/backend-common": "^0.7.0", - "@backstage/catalog-model": "^0.7.8", + "@backstage/backend-common": "^0.8.0", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.2", @@ -63,8 +63,8 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/test-utils": "^0.1.9", + "@backstage/cli": "^0.6.11", + "@backstage/test-utils": "^0.1.11", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-backend/src/database/search.test.ts b/plugins/catalog-backend/src/database/search.test.ts index c6793cbdf4..656da8b6f5 100644 --- a/plugins/catalog-backend/src/database/search.test.ts +++ b/plugins/catalog-backend/src/database/search.test.ts @@ -101,6 +101,12 @@ describe('search', () => { expect(output).toEqual([{ entity_id: 'eid', key: 'foo', value: 'bar' }]); }); + it('skips very large keys', () => { + const input = [{ key: 'a'.repeat(10000), value: 'foo' }]; + const output = mapToRows(input, 'eid'); + expect(output).toEqual([]); + }); + it('skips very large values', () => { const input = [{ key: 'foo', value: 'a'.repeat(10000) }]; const output = mapToRows(input, 'eid'); diff --git a/plugins/catalog-backend/src/database/search.ts b/plugins/catalog-backend/src/database/search.ts index 9e58d2468b..67b6d14b4f 100644 --- a/plugins/catalog-backend/src/database/search.ts +++ b/plugins/catalog-backend/src/database/search.ts @@ -31,6 +31,7 @@ const SPECIAL_KEYS = [ // The maximum length allowed for search values. These columns are indexed, and // database engines do not like to index on massive values. For example, // postgres will balk after 8191 byte line sizes. +const MAX_KEY_LENGTH = 200; const MAX_VALUE_LENGTH = 200; type Kv = { @@ -136,7 +137,7 @@ export function mapToRows( result.push({ entity_id: entityId, key, value: null }); } else { const value = String(rawValue).toLowerCase(); - if (value.length <= MAX_VALUE_LENGTH) { + if (key.length <= MAX_KEY_LENGTH && value.length <= MAX_VALUE_LENGTH) { result.push({ entity_id: entityId, key, value }); } } diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts index 19a08d219f..9b724def28 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts @@ -16,6 +16,11 @@ import { getVoidLogger } from '@backstage/backend-common'; import { LocationSpec } from '@backstage/catalog-model'; +import { + GitHubIntegration, + ScmIntegrations, + ScmIntegrationsGroup, +} from '@backstage/integration'; import { GithubOrgReaderProcessor, parseUrl } from './GithubOrgReaderProcessor'; describe('GithubOrgReaderProcessor', () => { @@ -29,6 +34,20 @@ describe('GithubOrgReaderProcessor', () => { }); describe('implementation', () => { + let integrations: ScmIntegrations; + let github: jest.Mocked>; + + beforeEach(() => { + github = { + byHost: jest.fn(), + byUrl: jest.fn(), + list: jest.fn(), + }; + integrations = ({ + github, + } as Partial) as ScmIntegrations; + }); + it('rejects unknown types', async () => { const processor = new GithubOrgReaderProcessor({ providers: [ @@ -37,6 +56,7 @@ describe('GithubOrgReaderProcessor', () => { apiBaseUrl: 'https://api.github.com', }, ], + integrations, logger: getVoidLogger(), }); const location: LocationSpec = { @@ -48,7 +68,7 @@ describe('GithubOrgReaderProcessor', () => { ).resolves.toBeFalsy(); }); - it('rejects unknown targets', async () => { + it('rejects unknown targets from providers', async () => { const processor = new GithubOrgReaderProcessor({ providers: [ { @@ -56,6 +76,24 @@ describe('GithubOrgReaderProcessor', () => { apiBaseUrl: 'https://api.github.com', }, ], + integrations, + logger: getVoidLogger(), + }); + const location: LocationSpec = { + type: 'github-org', + target: 'https://not.github.com/apa', + }; + await expect( + processor.readLocation(location, false, () => {}), + ).rejects.toThrow( + /There is no GitHub Org provider that matches https:\/\/not.github.com\/apa/, + ); + }); + + it('rejects unknown targets from integrations', async () => { + const processor = new GithubOrgReaderProcessor({ + providers: [], + integrations, logger: getVoidLogger(), }); const location: LocationSpec = { diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts index 6a83d705f6..14204adde7 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts @@ -16,6 +16,10 @@ import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; +import { + GithubCredentialsProvider, + ScmIntegrations, +} from '@backstage/integration'; import { graphql } from '@octokit/graphql'; import { Logger } from 'winston'; import { @@ -28,22 +32,33 @@ import * as results from './results'; import { CatalogProcessor, CatalogProcessorEmit } from './types'; import { buildOrgHierarchy } from './util/org'; +type GraphQL = typeof graphql; + /** * Extracts teams and users out of a GitHub org. */ export class GithubOrgReaderProcessor implements CatalogProcessor { private readonly providers: ProviderConfig[]; + private readonly integrations: ScmIntegrations; private readonly logger: Logger; static fromConfig(config: Config, options: { logger: Logger }) { + const integrations = ScmIntegrations.fromConfig(config); + return new GithubOrgReaderProcessor({ ...options, providers: readGithubConfig(config), + integrations, }); } - constructor(options: { providers: ProviderConfig[]; logger: Logger }) { + constructor(options: { + providers: ProviderConfig[]; + integrations: ScmIntegrations; + logger: Logger; + }) { this.providers = options.providers; + this.integrations = options.integrations; this.logger = options.logger; } @@ -56,24 +71,8 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { return false; } - const provider = this.providers.find(p => - location.target.startsWith(`${p.target}/`), - ); - if (!provider) { - throw new Error( - `There is no GitHub Org provider that matches ${location.target}. Please add a configuration entry for it under catalog.processors.githubOrg.providers.`, - ); - } - + const client = await this.createClient(location.target); const { org } = parseUrl(location.target); - const client = !provider.token - ? graphql - : graphql.defaults({ - baseUrl: provider.apiBaseUrl, - headers: { - authorization: `token ${provider.token}`, - }, - }); // Read out all of the raw data const startTimestamp = Date.now(); @@ -112,6 +111,67 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { return true; } + + private async createClient(orgUrl: string): Promise { + let client = await this.createClientFromIntegrations(orgUrl); + + if (!client) { + client = await this.createClientFromProvider(orgUrl); + } + + if (!client) { + throw new Error( + `There is no GitHub Org provider that matches ${orgUrl}. Please add a configuration for an integration or add an entry for it under catalog.processors.githubOrg.providers.`, + ); + } + + return client; + } + + private async createClientFromProvider( + orgUrl: string, + ): Promise { + const provider = this.providers.find(p => + orgUrl.startsWith(`${p.target}/`), + ); + + if (!provider) { + return undefined; + } + + this.logger.warn( + 'GithubOrgReaderProcessor uses provider defined in catalog.processors.githubOrg.providers, migrate to integrations instead. See https://backstage.io/docs/integrations/github/locations', + ); + + return !provider.token + ? graphql + : graphql.defaults({ + baseUrl: provider.apiBaseUrl, + headers: { + authorization: `token ${provider.token}`, + }, + }); + } + + private async createClientFromIntegrations( + orgUrl: string, + ): Promise { + const gitHubConfig = this.integrations.github.byUrl(orgUrl)?.config; + if (!gitHubConfig) { + return undefined; + } + + const credentialsProvider = GithubCredentialsProvider.create(gitHubConfig); + + const { headers } = await credentialsProvider.getCredentials({ + url: orgUrl, + }); + + return graphql.defaults({ + baseUrl: gitHubConfig.apiBaseUrl, + headers, + }); + } } /* diff --git a/plugins/catalog-backend/src/next/Stitcher.test.ts b/plugins/catalog-backend/src/next/Stitcher.test.ts index f38af384fa..644284f3e4 100644 --- a/plugins/catalog-backend/src/next/Stitcher.test.ts +++ b/plugins/catalog-backend/src/next/Stitcher.test.ts @@ -91,6 +91,9 @@ describe('Stitcher', () => { }, }, ], + status: { + 'backstage.io/catalog-processing': {}, + }, apiVersion: 'a', kind: 'k', metadata: { @@ -171,6 +174,9 @@ describe('Stitcher', () => { }, }, ]), + status: { + 'backstage.io/catalog-processing': {}, + }, apiVersion: 'a', kind: 'k', metadata: { diff --git a/plugins/catalog-backend/src/next/Stitcher.ts b/plugins/catalog-backend/src/next/Stitcher.ts index 2bddf41ac9..d7d4325ad0 100644 --- a/plugins/catalog-backend/src/next/Stitcher.ts +++ b/plugins/catalog-backend/src/next/Stitcher.ts @@ -15,6 +15,7 @@ */ import { Entity, parseEntityRef } from '@backstage/catalog-model'; +import { JsonObject } from '@backstage/config'; import { ConflictError } from '@backstage/errors'; import { createHash } from 'crypto'; import stableStringify from 'fast-json-stable-stringify'; @@ -35,12 +36,21 @@ export type DbFinalEntitiesRow = { final_entity: string; }; +type ProcessingStatus = { + errors?: JsonObject[]; +}; + function generateStableHash(entity: Entity) { return createHash('sha1') .update(stableStringify({ ...entity })) .digest('hex'); } +/** + * Performs the act of stitching - to take all of the various outputs from the + * ingestion process, and stitching them together into the final entity JSON + * shape. + */ export class Stitcher { constructor( private readonly database: Knex, @@ -109,7 +119,7 @@ export class Stitcher { const { entityId, processedEntity, - // errors, + errors, incomingReferenceCount, previousHash, } = result[0]; @@ -129,6 +139,7 @@ export class Stitcher { // it const entity = JSON.parse(processedEntity) as Entity; const isOrphan = Number(incomingReferenceCount) === 0; + const processingStatus: ProcessingStatus = {}; if (isOrphan) { this.logger.debug(`${entityRef} is an orphan`); @@ -137,6 +148,12 @@ export class Stitcher { ['backstage.io/orphan']: 'true', }; } + if (errors) { + const parsedErrors = JSON.parse(errors); + if (Array.isArray(parsedErrors) && parsedErrors.length) { + processingStatus.errors = parsedErrors; + } + } // TODO: entityRef is lower case and should be uppercase in the final // result @@ -146,6 +163,10 @@ export class Stitcher { type: row.relationType!, target: parseEntityRef(row.relationTarget!), })); + entity.status = { + ...entity.status, + 'backstage.io/catalog-processing': processingStatus, + }; // If the output entity was actually not changed, just abort const hash = generateStableHash(entity); diff --git a/plugins/catalog-backend/src/next/search.test.ts b/plugins/catalog-backend/src/next/search.test.ts index 2e0b07b4a9..be896319b7 100644 --- a/plugins/catalog-backend/src/next/search.test.ts +++ b/plugins/catalog-backend/src/next/search.test.ts @@ -104,6 +104,12 @@ describe('search', () => { expect(output).toEqual([{ entity_id: 'eid', key: 'foo', value: 'bar' }]); }); + it('skips very large keys', () => { + const input = [{ key: 'a'.repeat(10000), value: 'foo' }]; + const output = mapToRows(input, 'eid'); + expect(output).toEqual([]); + }); + it('skips very large values', () => { const input = [{ key: 'foo', value: 'a'.repeat(10000) }]; const output = mapToRows(input, 'eid'); diff --git a/plugins/catalog-backend/src/next/search.ts b/plugins/catalog-backend/src/next/search.ts index 383b29fd70..0681bffa17 100644 --- a/plugins/catalog-backend/src/next/search.ts +++ b/plugins/catalog-backend/src/next/search.ts @@ -43,6 +43,7 @@ const SPECIAL_KEYS = [ // The maximum length allowed for search values. These columns are indexed, and // database engines do not like to index on massive values. For example, // postgres will balk after 8191 byte line sizes. +const MAX_KEY_LENGTH = 200; const MAX_VALUE_LENGTH = 200; type Kv = { @@ -145,7 +146,7 @@ export function mapToRows(input: Kv[], entityId: string): DbSearchRow[] { result.push({ entity_id: entityId, key, value: null }); } else { const value = String(rawValue).toLocaleLowerCase('en-US'); - if (value.length <= MAX_VALUE_LENGTH) { + if (key.length <= MAX_KEY_LENGTH && value.length <= MAX_VALUE_LENGTH) { result.push({ entity_id: entityId, key, value }); } } diff --git a/plugins/catalog-graphql/CHANGELOG.md b/plugins/catalog-graphql/CHANGELOG.md index 8140724840..c04cc8e3d2 100644 --- a/plugins/catalog-graphql/CHANGELOG.md +++ b/plugins/catalog-graphql/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-graphql +## 0.2.9 + +### Patch Changes + +- 3be844496: chore: bump `ts-node` versions to 9.1.1 +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.2.8 ### Patch Changes diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 983604ba03..5e0b8e49c2 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.2.8", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", - "@backstage/catalog-model": "^0.7.8", + "@backstage/backend-common": "^0.8.0", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@graphql-modules/core": "^0.7.17", "apollo-server": "^2.16.1", @@ -42,8 +42,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/test-utils": "^0.1.9", + "@backstage/cli": "^0.6.11", + "@backstage/test-utils": "^0.1.11", "@graphql-codegen/cli": "^1.21.3", "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 59baabfbc8..e41b135014 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-catalog-import +## 0.5.6 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.5.5 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index c5fcdd4d37..196b3d6e83 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.5", + "version": "0.5.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,12 +30,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", "@backstage/catalog-client": "^0.3.11", - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/integration": "^0.5.2", - "@backstage/integration-react": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.1.5", + "@backstage/integration-react": "^0.1.2", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -53,13 +53,13 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx index 3e918db419..03882d48ce 100644 --- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx +++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx @@ -98,7 +98,11 @@ describe('', () => { ); await act(async () => { - userEvent.click(getByRole('button', { name: /Analyze/i })); + try { + userEvent.click(getByRole('button', { name: /Analyze/i })); + } catch { + return; + } }); expect(catalogImportApi.analyzeUrl).toBeCalledTimes(0); diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index e5a857eca0..c50b72d059 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,20 @@ # @backstage/plugin-catalog-react +## 0.1.6 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/catalog-model@0.7.9 + ## 0.1.5 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index a9092ef75a..abce5a1996 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "0.1.5", + "version": "0.1.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,8 +29,8 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.11", - "@backstage/catalog-model": "^0.7.8", - "@backstage/core": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", "@material-ui/core": "^4.11.0", "@types/react": "^16.9", "lodash": "^4.17.15", @@ -40,13 +40,13 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.9", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index d6254a347a..160440c69f 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-catalog +## 0.5.7 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 5542de095: This makes the CatalogTable configurable with custom columns, passed through the CatalogPage component rendered on the home page. +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.5.6 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index f1a0e6979d..6354f00c5d 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.5.6", + "version": "0.5.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,12 +31,12 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.10", - "@backstage/catalog-model": "^0.7.7", - "@backstage/core": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.1", - "@backstage/integration-react": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.1.4", + "@backstage/integration-react": "^0.1.2", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -52,14 +52,14 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@microsoft/microsoft-graph-types": "^1.25.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index fd3d63edd7..bbb26d7be7 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -20,6 +20,7 @@ import { ContentHeader, errorApiRef, SupportButton, + TableColumn, useApi, useRouteRef, } from '@backstage/core'; @@ -42,6 +43,7 @@ import { CatalogFilterType, } from '../CatalogFilter/CatalogFilter'; import { CatalogTable } from '../CatalogTable/CatalogTable'; +import { EntityRow } from '../CatalogTable/types'; import { ResultsFilter } from '../ResultsFilter/ResultsFilter'; import { useOwnUser } from '../useOwnUser'; import CatalogLayout from './CatalogLayout'; @@ -61,6 +63,7 @@ const useStyles = makeStyles(theme => ({ export type CatalogPageProps = { initiallySelectedFilter?: string; + columns?: TableColumn[]; }; const CatalogPageContents = (props: CatalogPageProps) => { @@ -210,6 +213,7 @@ const CatalogPageContents = (props: CatalogPageProps) => { [] = [ - { - title: 'Name', - field: 'resolved.name', - highlight: true, - render: ({ entity }) => ( - - ), - }, - { - title: 'System', - field: 'resolved.partOfSystemRelationTitle', - render: ({ resolved }) => ( - - ), - }, - { - title: 'Owner', - field: 'resolved.ownedByRelationsTitle', - render: ({ resolved }) => ( - - ), - }, - { - title: 'Type', - field: 'entity.spec.type', - hidden: true, - }, - { - title: 'Lifecycle', - field: 'entity.spec.lifecycle', - }, - { - title: 'Description', - field: 'entity.metadata.description', - render: ({ entity }) => ( - - ), - width: 'auto', - }, - { - title: 'Tags', - field: 'entity.metadata.tags', - cellStyle: { - padding: '0px 16px 0px 20px', - }, - render: ({ entity }) => ( - <> - {entity.metadata.tags && - entity.metadata.tags.map(t => ( - - ))} - - ), - }, +const defaultColumns: TableColumn[] = [ + columnFactories.createNameColumn(), + columnFactories.createSystemColumn(), + columnFactories.createOwnerColumn(), + columnFactories.createSpecTypeColumn(), + columnFactories.createSpecLifecycleColumn(), + columnFactories.createMetadataDescriptionColumn(), + columnFactories.createTagsColumn(), ]; type CatalogTableProps = { @@ -136,6 +60,7 @@ type CatalogTableProps = { loading: boolean; error?: any; view?: string; + columns?: TableColumn[]; }; export const CatalogTable = ({ @@ -144,6 +69,7 @@ export const CatalogTable = ({ error, titlePreamble, view, + columns, }: CatalogTableProps) => { const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); @@ -224,7 +150,7 @@ export const CatalogTable = ({ }; }); - const typeColumn = columns.find(c => c.title === 'Type'); + const typeColumn = (columns || defaultColumns).find(c => c.title === 'Type'); if (typeColumn) { typeColumn.hidden = view !== 'Other'; } @@ -232,7 +158,7 @@ export const CatalogTable = ({ return ( isLoading={loading} - columns={columns} + columns={columns || defaultColumns} options={{ paging: true, pageSize: 20, @@ -248,3 +174,5 @@ export const CatalogTable = ({ /> ); }; + +CatalogTable.columns = columnFactories; diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx new file mode 100644 index 0000000000..4f65e4b0c7 --- /dev/null +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -0,0 +1,110 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { EntityRefLink, EntityRefLinks } from '@backstage/plugin-catalog-react'; +import { OverflowTooltip, TableColumn } from '@backstage/core'; +import { Chip } from '@material-ui/core'; +import { EntityRow } from './types'; + +export function createNameColumn(): TableColumn { + return { + title: 'Name', + field: 'resolved.name', + highlight: true, + render: ({ entity }) => ( + + ), + }; +} + +export function createSystemColumn(): TableColumn { + return { + title: 'System', + field: 'resolved.partOfSystemRelationTitle', + render: ({ resolved }) => ( + + ), + }; +} + +export function createOwnerColumn(): TableColumn { + return { + title: 'Owner', + field: 'resolved.ownedByRelationsTitle', + render: ({ resolved }) => ( + + ), + }; +} + +export function createSpecTypeColumn(): TableColumn { + return { + title: 'Type', + field: 'entity.spec.type', + hidden: true, + }; +} + +export function createSpecLifecycleColumn(): TableColumn { + return { + title: 'Lifecycle', + field: 'entity.spec.lifecycle', + }; +} + +export function createMetadataDescriptionColumn(): TableColumn { + return { + title: 'Description', + field: 'entity.metadata.description', + render: ({ entity }) => ( + + ), + width: 'auto', + }; +} + +export function createTagsColumn(): TableColumn { + return { + title: 'Tags', + field: 'entity.metadata.tags', + cellStyle: { + padding: '0px 16px 0px 20px', + }, + render: ({ entity }) => ( + <> + {entity.metadata.tags && + entity.metadata.tags.map(t => ( + + ))} + + ), + }; +} diff --git a/plugins/catalog/src/components/CatalogTable/index.ts b/plugins/catalog/src/components/CatalogTable/index.ts new file mode 100644 index 0000000000..9148a22a41 --- /dev/null +++ b/plugins/catalog/src/components/CatalogTable/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { CatalogTable } from './CatalogTable'; diff --git a/plugins/catalog/src/components/CatalogTable/types.ts b/plugins/catalog/src/components/CatalogTable/types.ts new file mode 100644 index 0000000000..b1a0ce2739 --- /dev/null +++ b/plugins/catalog/src/components/CatalogTable/types.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Entity, EntityName } from '@backstage/catalog-model'; + +export type EntityRow = { + entity: Entity; + resolved: { + name: string; + partOfSystemRelationTitle?: string; + partOfSystemRelations: EntityName[]; + ownedByRelationsTitle?: string; + ownedByRelations: EntityName[]; + }; +}; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 7ab7b95dd7..fb269fc484 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -17,6 +17,7 @@ export { AboutCard } from './components/AboutCard'; export { EntityLayout } from './components/EntityLayout'; export { EntityPageLayout } from './components/EntityPageLayout'; +export { CatalogTable } from './components/CatalogTable'; export * from './components/EntitySwitch'; export { Router } from './components/Router'; export { diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 8c6d6e094d..d1347fde2a 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-circleci +## 0.2.14 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.2.13 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 98af012a49..e9b18c43d0 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.2.13", + "version": "0.2.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.2", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -50,12 +50,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/react-lazylog": "^4.5.0", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index dea818c97d..a7540f2e72 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-cloudbuild +## 0.2.14 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.2.13 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 325dfb4e06..510f417bc2 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cloudbuild", - "version": "0.2.13", + "version": "0.2.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", - "@backstage/plugin-catalog-react": "^0.1.2", - "@backstage/core": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,12 +47,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 18e78c7454..3322186cbe 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-code-coverage-backend +## 0.1.5 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.1.4 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index db53096932..5b73fe45f8 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.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,9 +19,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/catalog-client": "^0.3.11", - "@backstage/catalog-model": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.2", @@ -36,7 +36,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.21.2", diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index 4a55b7f0f5..6d1ea253a0 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-code-coverage +## 0.1.3 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.1.2 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 9b227c1df1..f90985145f 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-code-coverage", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,11 +20,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.7", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.4", - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.1.4", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -39,12 +39,12 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/highlightjs": "^10.1.0", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md new file mode 100644 index 0000000000..e54fc1a802 --- /dev/null +++ b/plugins/config-schema/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/plugin-config-schema + +## 0.1.2 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index caa03b9ffb..c878f418d0 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/config": "^0.1.4", - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", @@ -34,12 +34,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "msw": "^0.21.2", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index e6a351f2a4..a94910cfb2 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-cost-insights +## 0.9.1 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + ## 0.9.0 ### Minor Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 3a5601041c..a467fc0542 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cost-insights", - "version": "0.9.0", + "version": "0.9.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/config": "^0.1.5", - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -55,12 +55,12 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/pluralize": "^0.0.29", diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md index 4141daf2b6..17e3775c6d 100644 --- a/plugins/explore-react/CHANGELOG.md +++ b/plugins/explore-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-explore-react +## 0.0.5 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + ## 0.0.4 ### Patch Changes diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index b6852ea0f0..5a8d082abe 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore-react", - "version": "0.0.4", + "version": "0.0.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,15 +28,15 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/core": "^0.7.1" + "@backstage/core": "^0.7.9" }, "devDependencies": { - "@backstage/cli": "^0.6.4", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.6", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 78275577f5..c3a756f928 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-explore +## 0.3.5 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 0b033d07b: Add "Organization" tab with a diagram +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/plugin-explore-react@0.0.5 + - @backstage/catalog-model@0.7.9 + ## 0.3.4 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 2a25a9db41..d96c2f4d18 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.3.4", + "version": "0.3.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.7.5", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.4", - "@backstage/plugin-explore-react": "^0.0.4", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", + "@backstage/plugin-explore-react": "^0.0.5", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -45,12 +45,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index bd7af91dca..1c5de31c0c 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-fossa +## 0.2.7 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.2.6 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 95762e52d7..51a6c5cf55 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-fossa", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.1.1", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,12 +47,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index 8445cbb02b..3843a9519a 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-gcp-projects +## 0.2.6 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + ## 0.2.5 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index fd6048ab2a..6b0c625501 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcp-projects", - "version": "0.2.5", + "version": "0.2.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,12 +41,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 9aca27d1a9..cccb452b34 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/integration": "^0.5.1", "@backstage/theme": "^0.2.7", "recharts": "^1.8.5", @@ -36,14 +36,14 @@ "react": "^16.13.1" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@types/recharts": "^1.8.15", "@testing-library/react-hooks": "^3.4.2", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/git-release-manager/src/features/Info/flow.png b/plugins/git-release-manager/src/features/Info/flow.png index e70df0d7fa..04fe5bbe34 100644 Binary files a/plugins/git-release-manager/src/features/Info/flow.png and b/plugins/git-release-manager/src/features/Info/flow.png differ diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index aa2fec5512..f435ff31bc 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-github-actions +## 0.4.6 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.4.5 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 95608c3c49..841bb3ebc5 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.5", + "version": "0.4.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.5", - "@backstage/core": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", + "@backstage/core": "^0.7.9", "@backstage/integration": "^0.5.2", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", @@ -50,12 +50,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 23c8943349..3212382684 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-github-deployments +## 0.1.5 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- f5a916c49: Support for GitHub Enterprise with GithubDeploymentsPlugin +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.1.4 ### Patch Changes diff --git a/plugins/github-deployments/README.md b/plugins/github-deployments/README.md index 8284ceba56..3c5435b4a6 100644 --- a/plugins/github-deployments/README.md +++ b/plugins/github-deployments/README.md @@ -52,3 +52,19 @@ spec: owner: CNCF lifecycle: experimental ``` + +### Self-hosted / Enterprise GitHub + +The plugin will try to use `backstage.io/source-location` or `backstage.io/managed-by-location` +annotations to figure out the location of the source code. + +1. Add the `host` and `apiBaseUrl` to your `app-config.yaml` + +```yaml +# app-config.yaml + +integrations: + github: + - host: 'your-github-host.com' + apiBaseUrl: 'https://api.your-github-host.com' +``` diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 84a0fe1660..1058a30fed 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.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.6", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.3", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/errors": "^0.1.1", + "@backstage/integration": "^0.5.1", + "@backstage/integration-react": "^0.1.2", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -34,12 +37,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/github-deployments/src/api/index.ts b/plugins/github-deployments/src/api/index.ts index 7496370bc9..d7e7e2fca6 100644 --- a/plugins/github-deployments/src/api/index.ts +++ b/plugins/github-deployments/src/api/index.ts @@ -13,9 +13,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { parseLocationReference } from '@backstage/catalog-model'; import { createApiRef, OAuthApi } from '@backstage/core'; +import { InputError } from '@backstage/errors'; +import { ScmIntegrationRegistry } from '@backstage/integration'; import { graphql } from '@octokit/graphql'; +const getBaseUrl = ( + scmIntegrationsApi: ScmIntegrationRegistry, + host: string | undefined, +): string => { + if (host === undefined) { + return 'https://api.github.com'; + } + + const location = parseLocationReference(host); + if (location.type !== 'github' && location.type !== 'url') { + return 'https://api.github.com'; + } + + const config = scmIntegrationsApi.github.byUrl(location.target); + + if (!config) { + throw new InputError( + `No matching GitHub integration configuration for host ${ + new URL(location.target).hostname + }, please check your integrations config`, + ); + } + + if (!config.config.apiBaseUrl) { + throw new InputError( + `No apiBaseUrl available for host ${ + new URL(location.target).hostname + }, please check your integrations config`, + ); + } + + return config?.config.apiBaseUrl; +}; + export type GithubDeployment = { environment: string; state: string; @@ -30,12 +67,15 @@ export type GithubDeployment = { payload: string; }; +type QueryParams = { + host: string | undefined; + owner: string; + repo: string; + last: number; +}; + export interface GithubDeploymentsApi { - listDeployments(options: { - owner: string; - repo: string; - last: number; - }): Promise; + listDeployments(params: QueryParams): Promise; } export const githubDeploymentsApiRef = createApiRef({ @@ -45,6 +85,7 @@ export const githubDeploymentsApiRef = createApiRef({ export type Options = { githubAuthApi: OAuthApi; + scmIntegrationsApi: ScmIntegrationRegistry; }; const deploymentsQuery = ` @@ -79,19 +120,19 @@ export type QueryResponse = { export class GithubDeploymentsApiClient implements GithubDeploymentsApi { private readonly githubAuthApi: OAuthApi; + private readonly scmIntegrationsApi: ScmIntegrationRegistry; constructor(options: Options) { this.githubAuthApi = options.githubAuthApi; + this.scmIntegrationsApi = options.scmIntegrationsApi; } - async listDeployments(options: { - owner: string; - repo: string; - last: number; - }): Promise { + async listDeployments(params: QueryParams): Promise { + const baseUrl = getBaseUrl(this.scmIntegrationsApi, params.host); const token = await this.githubAuthApi.getAccessToken(['repo']); const graphQLWithAuth = graphql.defaults({ + baseUrl, headers: { authorization: `token ${token}`, }, @@ -99,7 +140,7 @@ export class GithubDeploymentsApiClient implements GithubDeploymentsApi { const response: QueryResponse = await graphQLWithAuth( deploymentsQuery, - options, + params, ); return response.repository?.deployments?.nodes?.reverse() || []; } diff --git a/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx b/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx index f128e6274b..68f5b5df43 100644 --- a/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx +++ b/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx @@ -43,18 +43,48 @@ import { import { setupServer } from 'msw/node'; import { graphql } from 'msw'; +import { ScmIntegrations } from '@backstage/integration'; +import { Entity } from '@backstage/catalog-model'; import { GithubDeploymentsTable } from './GithubDeploymentsTable'; import { Box } from '@material-ui/core'; +let entity: { entity: Entity }; + jest.mock('@backstage/plugin-catalog-react', () => ({ useEntity: () => { - return entityStub; + return entity; }, })); const errorApiMock = { post: jest.fn(), error$: jest.fn() }; -const configApi: ConfigApi = new ConfigReader({}); +const configApi: ConfigApi = new ConfigReader({ + integrations: { + github: [ + { + host: 'my-github-1.com', + apiBaseUrl: 'https://api.my-github-1.com', + }, + { + host: 'my-github-2.com', + apiBaseUrl: 'https://api.my-github-2.com', + }, + { + host: 'my-github-3.com', + }, + ], + }, +}); + +const GRAPHQL_GITHUB_API = graphql.link('https://api.github.com/graphql'); +const GRAPHQL_CUSTOM_API_1 = graphql.link( + 'https://api.my-github-1.com/graphql', +); +const GRAPHQL_CUSTOM_API_2 = graphql.link( + 'https://api.my-github-2.com/graphql', +); + +const scmIntegrationsApi = ScmIntegrations.fromConfig(configApi); const githubAuthApi: OAuthApi = { getAccessToken: async _ => 'access_token', }; @@ -62,14 +92,42 @@ const githubAuthApi: OAuthApi = { const apis = ApiRegistry.from([ [configApiRef, configApi], [errorApiRef, errorApiMock], - [githubDeploymentsApiRef, new GithubDeploymentsApiClient({ githubAuthApi })], + [ + githubDeploymentsApiRef, + new GithubDeploymentsApiClient({ scmIntegrationsApi, githubAuthApi }), + ], ]); +const assertFetchedData = async () => { + const rendered = await renderInTestApp( + + + , + ); + + expect(await rendered.findByText('GitHub Deployments')).toBeInTheDocument(); + + expect(await rendered.findByText('active')).toBeInTheDocument(); + expect(await rendered.findByText('prd')).toBeInTheDocument(); + expect(await rendered.findByText('12345')).toHaveAttribute( + 'href', + 'https://exampleapi.com/123456789', + ); + + expect(await rendered.findByText('pending')).toBeInTheDocument(); + expect(await rendered.findByText('lab')).toBeInTheDocument(); + expect(await rendered.findByText('54321')).toHaveAttribute( + 'href', + 'https://exampleapi.com/543212345', + ); +}; + describe('github-deployments', () => { const worker = setupServer(); msw.setupDefaultHandlers(worker); beforeEach(() => { + worker.resetHandlers(); jest.resetAllMocks(); }); @@ -80,40 +138,27 @@ describe('github-deployments', () => { }); describe('GithubDeploymentsCard', () => { - it('should display fetched data', async () => { + beforeEach(() => { + entity = entityStub; + entity.entity.metadata.annotations = { + 'github.com/project-slug': 'org/repo', + }; + }); + + it('displays fetched data using default github api', async () => { worker.use( - graphql.query('deployments', (_, res, ctx) => + GRAPHQL_GITHUB_API.query('deployments', (_, res, ctx) => res(ctx.data(responseStub)), ), ); - const rendered = await renderInTestApp( - - - , - ); - - expect( - await rendered.findByText('GitHub Deployments'), - ).toBeInTheDocument(); - expect(await rendered.findByText('active')).toBeInTheDocument(); - expect(await rendered.findByText('prd')).toBeInTheDocument(); - expect(await rendered.findByText('12345')).toHaveAttribute( - 'href', - 'https://exampleapi.com/123456789', - ); - - expect(await rendered.findByText('pending')).toBeInTheDocument(); - expect(await rendered.findByText('lab')).toBeInTheDocument(); - expect(await rendered.findByText('54321')).toHaveAttribute( - 'href', - 'https://exampleapi.com/543212345', - ); + await assertFetchedData(); + expect.assertions(7); }); it('should display empty state when no data', async () => { worker.use( - graphql.query('deployments', (_, res, ctx) => + GRAPHQL_GITHUB_API.query('deployments', (_, res, ctx) => res(ctx.data(noDataResponseStub)), ), ); @@ -132,9 +177,9 @@ describe('github-deployments', () => { ).toBeInTheDocument(); }); - it('should shows new data on reload', async () => { + it('should show new data on reload', async () => { worker.use( - graphql.query('deployments', (_, res, ctx) => + GRAPHQL_GITHUB_API.query('deployments', (_, res, ctx) => res(ctx.data(responseStub)), ), ); @@ -152,7 +197,7 @@ describe('github-deployments', () => { worker.resetHandlers(); worker.use( - graphql.query('deployments', (_, res, ctx) => + GRAPHQL_GITHUB_API.query('deployments', (_, res, ctx) => res(ctx.data(refreshedResponseStub)), ), ); @@ -199,5 +244,147 @@ describe('github-deployments', () => { expect(await rendered.findByText('moon')).toBeInTheDocument(); expect(await rendered.findByText('sun')).toBeInTheDocument(); }); + + describe('entity with source location', () => { + beforeEach(() => { + entity = entityStub; + entity.entity.metadata.annotations = { + 'github.com/project-slug': 'org/repo', + 'backstage.io/source-location': + 'github:https://my-github-1.com/org/repo/tree/master/', + }; + }); + + it('should fetch data using custom api', async () => { + worker.use( + GRAPHQL_CUSTOM_API_1.query('deployments', (_, res, ctx) => + res(ctx.data(responseStub)), + ), + ); + + await assertFetchedData(); + expect.assertions(7); + }); + }); + + describe('entity with managed by location', () => { + beforeEach(() => { + entity = entityStub; + entity.entity.metadata.annotations = { + 'github.com/project-slug': 'org/repo', + 'backstage.io/managed-by-location': + 'github:https://my-github-2.com/org/repo/blob/master/catalog-info.yaml', + }; + }); + + it('should fetch data using custom api', async () => { + worker.use( + GRAPHQL_CUSTOM_API_2.query('deployments', (_, res, ctx) => + res(ctx.data(responseStub)), + ), + ); + + await assertFetchedData(); + expect.assertions(7); + }); + }); + + describe('entity with location without baseApiURL', () => { + beforeEach(() => { + entity = entityStub; + entity.entity.metadata.annotations = { + 'github.com/project-slug': 'org/repo', + 'backstage.io/managed-by-location': + 'github:https://my-github-3.com/org/repo/blob/master/catalog-info.yaml', + }; + }); + + it('shows no apiBaseUrl error', async () => { + const rendered = await renderInTestApp( + + + , + ); + + expect( + await rendered.findByText( + 'Warning: No apiBaseUrl available for host my-github-3.com, please check your integrations config', + ), + ).toBeInTheDocument(); + }); + }); + + describe('entity with location without matching host', () => { + beforeEach(() => { + entity = entityStub; + entity.entity.metadata.annotations = { + 'github.com/project-slug': 'org/repo', + 'backstage.io/managed-by-location': + 'github:https://my-github-unknown.com/org/repo/blob/master/catalog-info.yaml', + }; + }); + + it('shows no matching host error', async () => { + const rendered = await renderInTestApp( + + + , + ); + + expect( + await rendered.findByText( + 'Warning: No matching GitHub integration configuration for host my-github-unknown.com, please check your integrations config', + ), + ).toBeInTheDocument(); + }); + }); + + describe('entity with url location type', () => { + beforeEach(() => { + entity = entityStub; + entity.entity.metadata.annotations = { + 'github.com/project-slug': 'org/repo', + 'backstage.io/source-location': + 'url:https://my-github-1.com/org/repo/tree/master/', + 'backstage.io/managed-by-location': + 'url:https://my-github-1.com/org/repo/blob/master/catalog-info.yaml', + }; + }); + + it('should fetch data using custom api', async () => { + worker.use( + GRAPHQL_CUSTOM_API_1.query('deployments', (_, res, ctx) => + res(ctx.data(responseStub)), + ), + ); + + await assertFetchedData(); + expect.assertions(7); + }); + }); + + describe('entity with other location types', () => { + beforeEach(() => { + entity = entityStub; + entity.entity.metadata.annotations = { + 'github.com/project-slug': 'org/repo', + 'backstage.io/source-location': + 'some-other-managed-location:my-favourite-location/org/repo', + 'backstage.io/managed-by-location': + 'file:my-favourite-file/org/repo.yaml', + }; + }); + + it('displays fetched data using default github api', async () => { + worker.use( + GRAPHQL_GITHUB_API.query('deployments', (_, res, ctx) => + res(ctx.data(responseStub)), + ), + ); + + await assertFetchedData(); + expect.assertions(7); + }); + }); }); }); diff --git a/plugins/github-deployments/src/components/GithubDeploymentsCard.tsx b/plugins/github-deployments/src/components/GithubDeploymentsCard.tsx index 073f78af2e..a13c205084 100644 --- a/plugins/github-deployments/src/components/GithubDeploymentsCard.tsx +++ b/plugins/github-deployments/src/components/GithubDeploymentsCard.tsx @@ -28,21 +28,27 @@ import { isGithubDeploymentsAvailable, } from '../Router'; import { GithubDeploymentsTable } from './GithubDeploymentsTable/GithubDeploymentsTable'; +import { + LOCATION_ANNOTATION, + SOURCE_LOCATION_ANNOTATION, +} from '@backstage/catalog-model'; const GithubDeploymentsComponent = ({ projectSlug, last, columns, + host, }: { projectSlug: string; last: number; columns: TableColumn[]; + host: string | undefined; }) => { const api = useApi(githubDeploymentsApiRef); const [owner, repo] = projectSlug.split('/'); const { loading, value, error, retry: reload } = useAsyncRetry( - async () => await api.listDeployments({ owner, repo, last }), + async () => await api.listDeployments({ host, owner, repo, last }), ); if (error) { @@ -67,6 +73,10 @@ export const GithubDeploymentsCard = ({ columns?: TableColumn[]; }) => { const { entity } = useEntity(); + const [host] = [ + entity?.metadata.annotations?.[SOURCE_LOCATION_ANNOTATION], + entity?.metadata.annotations?.[LOCATION_ANNOTATION], + ].filter(Boolean); return !isGithubDeploymentsAvailable(entity) ? ( @@ -76,6 +86,7 @@ export const GithubDeploymentsCard = ({ entity?.metadata.annotations?.[GITHUB_PROJECT_SLUG_ANNOTATION] || '' } last={last || 10} + host={host} columns={columns || GithubDeploymentsTable.defaultDeploymentColumns} /> ); diff --git a/plugins/github-deployments/src/mocks/mocks.ts b/plugins/github-deployments/src/mocks/mocks.ts index c0afb325db..b2a75ac7b0 100644 --- a/plugins/github-deployments/src/mocks/mocks.ts +++ b/plugins/github-deployments/src/mocks/mocks.ts @@ -13,15 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { Entity } from '@backstage/catalog-model'; import { QueryResponse } from '../api'; -export const entityStub = { +export const entityStub: { entity: Entity } = { entity: { metadata: { namespace: 'default', - annotations: { - 'github.com/project-slug': 'org/repo', - }, + annotations: {}, name: 'sample-service', description: 'Sample service', uid: 'g0h33dd9-56h7-835b-b63v-7x5da3j64851', diff --git a/plugins/github-deployments/src/plugin.ts b/plugins/github-deployments/src/plugin.ts index 3707b1cf68..943d5e0bc6 100644 --- a/plugins/github-deployments/src/plugin.ts +++ b/plugins/github-deployments/src/plugin.ts @@ -19,6 +19,7 @@ import { createPlugin, githubAuthApiRef, } from '@backstage/core'; +import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { githubDeploymentsApiRef, GithubDeploymentsApiClient } from './api'; export const githubDeploymentsPlugin = createPlugin({ @@ -26,9 +27,12 @@ export const githubDeploymentsPlugin = createPlugin({ apis: [ createApiFactory({ api: githubDeploymentsApiRef, - deps: { githubAuthApi: githubAuthApiRef }, - factory: ({ githubAuthApi }) => - new GithubDeploymentsApiClient({ githubAuthApi }), + deps: { + scmIntegrationsApi: scmIntegrationsApiRef, + githubAuthApi: githubAuthApiRef, + }, + factory: ({ scmIntegrationsApi, githubAuthApi }) => + new GithubDeploymentsApiClient({ scmIntegrationsApi, githubAuthApi }), }), ], }); diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index b72e3c23c5..ae6672581f 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-gitops-profiles +## 0.2.7 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + ## 0.2.6 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index c00dbde482..dbb84e4994 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gitops-profiles", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,12 +42,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index 681005b13f..e0086af107 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-graphiql +## 0.2.11 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + ## 0.2.10 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index ae3c0dfe8f..66c8790b8f 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.10", + "version": "0.2.11", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,12 +43,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/codemirror": "^0.0.108", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", diff --git a/plugins/graphql/CHANGELOG.md b/plugins/graphql/CHANGELOG.md index fc31d831dc..2604a15230 100644 --- a/plugins/graphql/CHANGELOG.md +++ b/plugins/graphql/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-graphql-backend +## 0.1.8 + +### Patch Changes + +- Updated dependencies [3be844496] +- Updated dependencies [22fd8ce2a] +- Updated dependencies [f9fb4a205] + - @backstage/plugin-catalog-graphql@0.2.9 + - @backstage/backend-common@0.8.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 0df78298d7..619df1d33b 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-graphql-backend", - "version": "0.1.7", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/config": "^0.1.5", - "@backstage/plugin-catalog-graphql": "^0.2.8", + "@backstage/plugin-catalog-graphql": "^0.2.9", "@graphql-modules/core": "^0.7.17", "@types/express": "^4.17.6", "apollo-server": "^2.16.1", @@ -45,7 +45,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.20.5", diff --git a/plugins/ilert/.eslintrc.js b/plugins/ilert/.eslintrc.js new file mode 100644 index 0000000000..4d51616e98 --- /dev/null +++ b/plugins/ilert/.eslintrc.js @@ -0,0 +1,6 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], + rules: { + quotes: ['error', 'single'], + }, +}; diff --git a/plugins/ilert/README.md b/plugins/ilert/README.md new file mode 100644 index 0000000000..da019d6bb6 --- /dev/null +++ b/plugins/ilert/README.md @@ -0,0 +1,128 @@ +# @backstage/plugin-ilert + +## Introduction + +[iLert](https://www.ilert.com) is a platform for alerting, on-call management and uptime monitoring. It helps teams to reduce response times to critical incidents by extending monitoring tools with reliable alerting, automatic escalations, on-call schedules and other features to support the incident response process, such as informing stakeholders or creating tickets in external incident management tools. + +## Overview + +This plugin gives an overview about ongoing iLert incidents, on-call and uptime monitor status. +See who is on-call, which incidents are active and trigger incidents directly from backstage for the configured alert sources. + +In detail this plugin provides: + +- Information details about the person on-call (all escalation levels of the current time) +- A way to override the current on-call person +- A list of active incidents +- A way to trigger a new incident +- A way to reassign/acknowledge/resolve an incident +- A way to trigger an incident action +- A way to trigger an immediate maintenance +- A way to disable/enable an alert source +- A list of uptime monitors + +## Setup instructions + +Install the plugin: + +```bash +# From the Backstage repository root +cd packages/app +yarn add @backstage/plugin-ilert +``` + +Add it to the `EntityPage.tsx`: + +```ts +import { + isPluginApplicableToEntity as isILertAvailable, + EntityILertCard, +} from '@backstage/plugin-ilert'; + +// ... + + + + + + +; +// ... +``` + +> To force an iLert card for each entity just add the `` component. An instruction card will appear if no integration key is set. + +## Add iLert explorer to the app sidebar + +Modify your app routes in [`App.tsx`](https://github.com/backstage/backstage/blob/master/packages/app/src/App.tsx) to include the Router component exported by the plugin - for example: + +```tsx +import { ILertPage } from '@backstage/plugin-ilert'; + + // ... + } /> + // ... +; +``` + +Modify your sidebar in [`Root.tsx`](https://github.com/backstage/backstage/blob/master/packages/app/src/components/Root/Root.tsx) to include the icon component exported by the plugin - for example: + +```tsx +import { ILertIcon } from '@backstage/plugin-ilert'; + + // ... + + // ... +; +``` + +## Client configuration + +If you want to override the default URL for api calls and detail pages, you can add it to `app-config.yaml`. + +In `app-config.yaml`: + +```yaml +ilert: + baseUrl: https://my-org.ilert.com/ +``` + +## Providing the Authorization Header + +In order to make the API calls, you need to provide a new proxy config which will redirect to the [iLert API](https://api.ilert.com/api-docs/) endpoint. It needs an [Authorization Header](https://api.ilert.com/api-docs/#section/Authentication). + +Add the proxy configuration in `app-config.yaml` + +```yaml +proxy: + ... + '/ilert': + target: https://api.ilert.com + allowedMethods: ['GET', 'POST', 'PUT'] + allowedHeaders: ['Authorization'] + headers: + Authorization: ${ILERT_AUTH_HEADER} +``` + +Then start the backend, passing the authorization header (bearer token or basic auth) as environment variable: + +```bash +$ ILERT_AUTH_HEADER='' yarn start +``` + +## Integration Key + +The information displayed for each entity is based on the alert source integration key. + +### Adding the integration key to the entity annotation + +If you want to use this plugin for an entity, you need to label it with the below annotation: + +```yml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example + annotations: + ilert.com/integration-key: [INTEGRATION_KEY] +``` diff --git a/plugins/ilert/config.d.ts b/plugins/ilert/config.d.ts new file mode 100644 index 0000000000..6bf3f569ab --- /dev/null +++ b/plugins/ilert/config.d.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export interface Config { + ilert?: { + /** + * Domain used by users to access iLert web UI. + * Example: https://my-app.ilert.com/ + * @visibility frontend + */ + baseUrl: string; + + /** + * Path to use for requests via the proxy, defaults to /ilert/api + * @visibility frontend + */ + proxyPath?: string; + }; +} diff --git a/plugins/ilert/dev/index.tsx b/plugins/ilert/dev/index.tsx new file mode 100644 index 0000000000..737666e650 --- /dev/null +++ b/plugins/ilert/dev/index.tsx @@ -0,0 +1,27 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { createDevApp } from '@backstage/dev-utils'; +import { ilertPlugin } from '../src/plugin'; +import { ILertPage } from '../src'; + +createDevApp() + .registerPlugin(ilertPlugin) + .addPage({ + element: , + title: 'Root Page', + }) + .render(); diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json new file mode 100644 index 0000000000..24376e03a8 --- /dev/null +++ b/plugins/ilert/package.json @@ -0,0 +1,56 @@ +{ + "name": "@backstage/plugin-ilert", + "version": "0.1.1", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "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/catalog-model": "^0.7.6", + "@backstage/core": "^0.7.9", + "@backstage/errors": "^0.1.1", + "@backstage/plugin-catalog-react": "^0.1.4", + "@backstage/theme": "^0.2.7", + "@date-io/luxon": "1.x", + "@material-ui/core": "^4.11.0", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "@material-ui/pickers": "^3.3.10", + "humanize-duration": "^3.26.0", + "luxon": "^1.26.0", + "react": "^16.13.1", + "react-dom": "^16.13.1", + "react-use": "^17.2.4" + }, + "devDependencies": { + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", + "@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.21.2" + }, + "files": [ + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/ilert/src/api/client.ts b/plugins/ilert/src/api/client.ts new file mode 100644 index 0000000000..ed1e671bc0 --- /dev/null +++ b/plugins/ilert/src/api/client.ts @@ -0,0 +1,535 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { ConfigApi, createApiRef, DiscoveryApi } from '@backstage/core'; +import { AuthenticationError, ResponseError } from '@backstage/errors'; +import { + AlertSource, + EscalationPolicy, + Incident, + IncidentAction, + IncidentResponder, + OnCall, + Schedule, + UptimeMonitor, + User, +} from '../types'; +import { + ILertApi, + GetIncidentsOpts, + GetIncidentsCountOpts, + Options, + EventRequest, +} from './types'; +import { DateTime as dt } from 'luxon'; + +export const ilertApiRef = createApiRef({ + id: 'plugin.ilert.service', + description: 'Used to make requests towards iLert API', +}); + +const DEFAULT_PROXY_PATH = '/ilert'; +const JSON_HEADERS = { + 'Content-Type': 'application/json', + Accept: 'application/json', +}; + +export class ILertClient implements ILertApi { + private readonly discoveryApi: DiscoveryApi; + private readonly proxyPath: string; + private readonly baseUrl: string; + + static fromConfig(configApi: ConfigApi, discoveryApi: DiscoveryApi) { + const baseUrl: string = + configApi.getOptionalString('ilert.baseUrl') ?? 'https://app.ilert.com'; + + return new ILertClient({ + discoveryApi: discoveryApi, + baseUrl, + proxyPath: + configApi.getOptionalString('ilert.proxyPath') ?? DEFAULT_PROXY_PATH, + }); + } + + constructor(opts: Options) { + this.discoveryApi = opts.discoveryApi; + this.baseUrl = opts.baseUrl; + this.proxyPath = opts.proxyPath; + } + + private async fetch(input: string, init?: RequestInit): Promise { + const apiUrl = await this.apiUrl(); + + const response = await fetch(`${apiUrl}${input}`, init); + if (response.status === 401) { + throw new AuthenticationError( + 'This request requires HTTP authentication.', + ); + } + if (!response.ok || response.status >= 400) { + throw await ResponseError.fromResponse(response); + } + + return await response.json(); + } + + async fetchIncidents(opts?: GetIncidentsOpts): Promise { + const init = { + headers: JSON_HEADERS, + }; + const query = new URLSearchParams(); + if (opts?.maxResults !== undefined) { + query.append('max-results', String(opts.maxResults)); + } + if (opts?.startIndex !== undefined) { + query.append('start-index', String(opts.startIndex)); + } + if (opts?.alertSources !== undefined && Array.isArray(opts.alertSources)) { + opts.alertSources.forEach((a: number) => { + if (a) { + query.append('alert-source', String(a)); + } + }); + } + + if (opts?.states !== undefined && Array.isArray(opts.states)) { + opts.states.forEach(state => { + query.append('state', state); + }); + } + const response = await this.fetch( + `/api/v1/incidents?${query.toString()}`, + init, + ); + + return response; + } + + async fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise { + const init = { + headers: JSON_HEADERS, + }; + const query = new URLSearchParams(); + if (opts && opts.states && Array.isArray(opts.states)) { + opts.states.forEach(state => { + query.append('state', state); + }); + } + const response = await this.fetch( + `/api/v1/incidents/count?${query.toString()}`, + init, + ); + + return response && response.count ? response.count : 0; + } + + async fetchIncident(id: number): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response = await this.fetch( + `/api/v1/incidents/${encodeURIComponent(id)}`, + init, + ); + + return response; + } + + async fetchIncidentResponders( + incident: Incident, + ): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response = await this.fetch( + `/api/v1/incidents/${encodeURIComponent(incident.id)}/responders`, + init, + ); + + return response; + } + + async fetchIncidentActions(incident: Incident): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response = await this.fetch( + `/api/v1/incidents/${encodeURIComponent(incident.id)}/actions`, + init, + ); + + return response; + } + + async acceptIncident( + incident: Incident, + userName: string, + ): Promise { + const init = { + method: 'POST', + headers: JSON_HEADERS, + body: JSON.stringify({ + apiKey: incident.alertSource?.integrationKey || '', + incidentKey: incident.incidentKey, + summary: `from ${userName} via Backstage plugin`, + eventType: 'ACCEPT', + }), + }; + + await this.fetch('/api/v1/events', init); + return this.fetchIncident(incident.id); + } + + async resolveIncident( + incident: Incident, + userName: string, + ): Promise { + const init = { + method: 'POST', + headers: JSON_HEADERS, + body: JSON.stringify({ + apiKey: incident.alertSource?.integrationKey || '', + incidentKey: incident.incidentKey, + summary: `from ${userName} via Backstage plugin`, + eventType: 'RESOLVE', + }), + }; + + await this.fetch('/api/v1/events', init); + return this.fetchIncident(incident.id); + } + + async assignIncident( + incident: Incident, + responder: IncidentResponder, + ): Promise { + const init = { + method: 'PUT', + headers: JSON_HEADERS, + }; + + const query = new URLSearchParams(); + switch (responder.group) { + case 'ESCALATION_POLICY': + query.append('policy-id', String(responder.id)); + break; + case 'ON_CALL_SCHEDULE': + query.append('schedule-id', String(responder.id)); + break; + default: + query.append('user-id', String(responder.id)); + break; + } + + const response = await this.fetch( + `/api/v1/incidents/${encodeURIComponent( + incident.id, + )}/assign?${query.toString()}`, + init, + ); + + return response; + } + + async triggerIncidentAction( + incident: Incident, + action: IncidentAction, + ): Promise { + const init = { + method: 'POST', + headers: JSON_HEADERS, + body: JSON.stringify({ + webhookId: action.webhookId, + extensionId: action.extensionId, + type: action.type, + name: action.name, + }), + }; + + await this.fetch( + `/api/v1/incidents/${encodeURIComponent(incident.id)}/actions`, + init, + ); + } + + async createIncident(eventRequest: EventRequest): Promise { + const init = { + method: 'POST', + headers: JSON_HEADERS, + body: JSON.stringify({ + apiKey: eventRequest.integrationKey, + summary: eventRequest.summary, + details: eventRequest.details, + eventType: 'ALERT', + links: [ + { + href: eventRequest.source, + text: 'Backstage Url', + }, + ], + customDetails: { + userName: eventRequest.userName, + }, + }), + }; + + const response = await this.fetch('/api/v1/events', init); + return response; + } + + async fetchUptimeMonitors(): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response = await this.fetch('/api/v1/uptime-monitors', init); + + return response; + } + + async fetchUptimeMonitor(id: number): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response: UptimeMonitor = await this.fetch( + `/api/v1/uptime-monitors/${encodeURIComponent(id)}`, + init, + ); + + return response; + } + + async pauseUptimeMonitor( + uptimeMonitor: UptimeMonitor, + ): Promise { + const init = { + method: 'PUT', + headers: JSON_HEADERS, + body: JSON.stringify({ ...uptimeMonitor, paused: true }), + }; + + const response = await this.fetch( + `/api/v1/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, + init, + ); + + return response; + } + + async resumeUptimeMonitor( + uptimeMonitor: UptimeMonitor, + ): Promise { + const init = { + method: 'PUT', + headers: JSON_HEADERS, + body: JSON.stringify({ ...uptimeMonitor, paused: false }), + }; + + const response = await this.fetch( + `/api/v1/uptime-monitors/${encodeURIComponent(uptimeMonitor.id)}`, + init, + ); + + return response; + } + + async fetchAlertSources(): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response = await this.fetch('/api/v1/alert-sources', init); + + return response; + } + + async fetchAlertSource( + idOrIntegrationKey: number | string, + ): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response = await this.fetch( + `/api/v1/alert-sources/${encodeURIComponent(idOrIntegrationKey)}`, + init, + ); + + return response; + } + + async fetchAlertSourceOnCalls(alertSource: AlertSource): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response = await this.fetch( + `/api/v1/on-calls?policies=${encodeURIComponent( + alertSource.escalationPolicy.id, + )}&expand=user&expand=escalationPolicy&timezone=${dt.local().zoneName}`, + init, + ); + + return response; + } + + async enableAlertSource(alertSource: AlertSource): Promise { + const init = { + method: 'PUT', + headers: JSON_HEADERS, + body: JSON.stringify({ ...alertSource, active: true }), + }; + + const response = await this.fetch( + `/api/v1/alert-sources/${encodeURIComponent(alertSource.id)}`, + init, + ); + + return response; + } + + async disableAlertSource(alertSource: AlertSource): Promise { + const init = { + method: 'PUT', + headers: JSON_HEADERS, + body: JSON.stringify({ ...alertSource, active: false }), + }; + + const response = await this.fetch( + `/api/v1/alert-sources/${encodeURIComponent(alertSource.id)}`, + init, + ); + + return response; + } + + async addImmediateMaintenance( + alertSourceId: number, + minutes: number, + ): Promise { + const init = { + method: 'POST', + headers: JSON_HEADERS, + body: JSON.stringify({ + start: dt.utc().toISO(), + end: dt.utc().plus({ minutes }).toISO(), + description: `Immediate maintenance window for ${minutes} minutes. Backstage — iLert plugin.`, + createdBy: 'Backstage', + timezone: dt.local().zoneName, + alertSources: [{ id: alertSourceId }], + }), + }; + + const response = await this.fetch('/api/v1/maintenance-windows', init); + + return response; + } + + async fetchOnCallSchedules(): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response = await this.fetch('/api/v1/schedules', init); + + return response; + } + + async fetchUsers(): Promise { + const init = { + headers: JSON_HEADERS, + }; + + const response = await this.fetch('/api/v1/users', init); + + return response; + } + + async overrideShift( + scheduleId: number, + userId: number, + start: string, + end: string, + ): Promise { + const init = { + method: 'PUT', + headers: JSON_HEADERS, + body: JSON.stringify({ user: { id: userId }, start, end }), + }; + + const response = await this.fetch( + `/api/v1/schedules/${encodeURIComponent(scheduleId)}/overrides`, + init, + ); + + return response; + } + + getIncidentDetailsURL(incident: Incident): string { + return `${this.baseUrl}/incident/view.jsf?id=${encodeURIComponent( + incident.id, + )}`; + } + + getAlertSourceDetailsURL(alertSource: AlertSource | null): string { + if (!alertSource) { + return ''; + } + return `${this.baseUrl}/source/view.jsf?id=${encodeURIComponent( + alertSource.id, + )}`; + } + + getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string { + return `${this.baseUrl}/policy/view.jsf?id=${encodeURIComponent( + escalationPolicy.id, + )}`; + } + + getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string { + return `${this.baseUrl}/uptime/view.jsf?id=${encodeURIComponent( + uptimeMonitor.id, + )}`; + } + + getScheduleDetailsURL(schedule: Schedule): string { + return `${this.baseUrl}/schedule/view.jsf?id=${encodeURIComponent( + schedule.id, + )}`; + } + + getUserPhoneNumber(user: User | null) { + return user?.mobile?.number || user?.landline?.number || ''; + } + + getUserInitials(user: User | null) { + if (!user) { + return ''; + } + if (!user.firstName && !user.lastName) { + return user.username; + } + return `${user.firstName} ${user.lastName} (${user.username})`; + } + + private async apiUrl() { + const proxyUrl = await this.discoveryApi.getBaseUrl('proxy'); + return proxyUrl + this.proxyPath; + } +} diff --git a/plugins/ilert/src/api/index.ts b/plugins/ilert/src/api/index.ts new file mode 100644 index 0000000000..65c3571bff --- /dev/null +++ b/plugins/ilert/src/api/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { ILertClient, ilertApiRef } from './client'; +export type { + ILertApi, + GetIncidentsCountOpts, + GetIncidentsOpts, + TableState, +} from './types'; diff --git a/plugins/ilert/src/api/types.ts b/plugins/ilert/src/api/types.ts new file mode 100644 index 0000000000..ab9cca3dc6 --- /dev/null +++ b/plugins/ilert/src/api/types.ts @@ -0,0 +1,120 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { DiscoveryApi } from '@backstage/core'; +import { + AlertSource, + Incident, + User, + IncidentStatus, + UptimeMonitor, + EscalationPolicy, + Schedule, + IncidentResponder, + IncidentAction, + OnCall, +} from '../types'; + +export type TableState = { + page: number; + pageSize: number; +}; + +export type GetIncidentsOpts = { + maxResults?: number; + startIndex?: number; + states?: IncidentStatus[]; + alertSources?: number[]; +}; + +export type GetIncidentsCountOpts = { + states?: IncidentStatus[]; +}; + +export type EventRequest = { + integrationKey: string; + summary: string; + details: string; + userName: string; + source: string; +}; + +export interface ILertApi { + fetchIncidents(opts?: GetIncidentsOpts): Promise; + fetchIncidentsCount(opts?: GetIncidentsCountOpts): Promise; + fetchIncident(id: number): Promise; + fetchIncidentResponders(incident: Incident): Promise; + fetchIncidentActions(incident: Incident): Promise; + acceptIncident(incident: Incident, userName: string): Promise; + resolveIncident(incident: Incident, userName: string): Promise; + assignIncident( + incident: Incident, + responder: IncidentResponder, + ): Promise; + createIncident(eventRequest: EventRequest): Promise; + triggerIncidentAction( + incident: Incident, + action: IncidentAction, + ): Promise; + + fetchUptimeMonitors(): Promise; + pauseUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; + resumeUptimeMonitor(uptimeMonitor: UptimeMonitor): Promise; + fetchUptimeMonitor(id: number): Promise; + + fetchAlertSources(): Promise; + fetchAlertSource(idOrIntegrationKey: number | string): Promise; + fetchAlertSourceOnCalls(alertSource: AlertSource): Promise; + enableAlertSource(alertSource: AlertSource): Promise; + disableAlertSource(alertSource: AlertSource): Promise; + + addImmediateMaintenance( + alertSourceId: number, + minutes: number, + ): Promise; + + fetchOnCallSchedules(): Promise; + fetchUsers(): Promise; + + overrideShift( + scheduleId: number, + userId: number, + start: string, + end: string, + ): Promise; + + getIncidentDetailsURL(incident: Incident): string; + getAlertSourceDetailsURL(alertSource: AlertSource | null): string; + getEscalationPolicyDetailsURL(escalationPolicy: EscalationPolicy): string; + getUptimeMonitorDetailsURL(uptimeMonitor: UptimeMonitor): string; + getScheduleDetailsURL(schedule: Schedule): string; + getUserPhoneNumber(user: User | null): string; + getUserInitials(user: User | null): string; +} + +export type Options = { + discoveryApi: DiscoveryApi; + + /** + * URL used by users to access iLert web UI. + * Example: https://my-org.ilert.com/ + */ + baseUrl: string; + + /** + * Path to use for requests via the proxy, defaults to /ilert/api + */ + proxyPath: string; +}; diff --git a/plugins/ilert/src/assets/ilert.icon.svg b/plugins/ilert/src/assets/ilert.icon.svg new file mode 100644 index 0000000000..5109286355 --- /dev/null +++ b/plugins/ilert/src/assets/ilert.icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/ilert/src/components/AlertSource/AlertSourceLink.tsx b/plugins/ilert/src/components/AlertSource/AlertSourceLink.tsx new file mode 100644 index 0000000000..f7f6dbb78c --- /dev/null +++ b/plugins/ilert/src/components/AlertSource/AlertSourceLink.tsx @@ -0,0 +1,70 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { useApi, Link } from '@backstage/core'; +import Grid from '@material-ui/core/Grid'; +import { AlertSource } from '../../types'; +import { ilertApiRef } from '../../api'; +import { makeStyles } from '@material-ui/core/styles'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; + +const useStyles = makeStyles({ + root: { + display: 'flex', + maxWidth: '100%', + }, + image: { + height: 22, + paddingRight: 4, + }, + link: { + lineHeight: '22px', + }, +}); + +export const AlertSourceLink = ({ + alertSource, +}: { + alertSource: AlertSource | null; +}) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); + + if (!alertSource) { + return null; + } + + return ( + + + {alertSource.name} + + + + {alertSource.name} + + + + ); +}; diff --git a/plugins/ilert/src/components/Errors/MissingAuthorizationHeaderError.tsx b/plugins/ilert/src/components/Errors/MissingAuthorizationHeaderError.tsx new file mode 100644 index 0000000000..6b3d463d2a --- /dev/null +++ b/plugins/ilert/src/components/Errors/MissingAuthorizationHeaderError.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { EmptyState } from '@backstage/core'; +import { Button } from '@material-ui/core'; + +export const MissingAuthorizationHeaderError = () => ( + + Read More + + } + /> +); diff --git a/plugins/ilert/src/components/Errors/index.ts b/plugins/ilert/src/components/Errors/index.ts new file mode 100644 index 0000000000..f20de48b90 --- /dev/null +++ b/plugins/ilert/src/components/Errors/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { MissingAuthorizationHeaderError } from './MissingAuthorizationHeaderError'; diff --git a/plugins/ilert/src/components/EscalationPolicy/EscalationPolicyLink.tsx b/plugins/ilert/src/components/EscalationPolicy/EscalationPolicyLink.tsx new file mode 100644 index 0000000000..9e7ff102f8 --- /dev/null +++ b/plugins/ilert/src/components/EscalationPolicy/EscalationPolicyLink.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { useApi, Link } from '@backstage/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { EscalationPolicy } from '../../types'; +import { ilertApiRef } from '../../api'; + +const useStyles = makeStyles({ + link: { + lineHeight: '22px', + }, +}); + +export const EscalationPolicyLink = ({ + escalationPolicy, +}: { + escalationPolicy: EscalationPolicy | null; +}) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + if (!escalationPolicy) { + return null; + } + + return ( + + {escalationPolicy.name} + + ); +}; diff --git a/plugins/ilert/src/components/ILertCard/ILertCard.tsx b/plugins/ilert/src/components/ILertCard/ILertCard.tsx new file mode 100644 index 0000000000..bbc82c2b2d --- /dev/null +++ b/plugins/ilert/src/components/ILertCard/ILertCard.tsx @@ -0,0 +1,142 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { Entity } from '@backstage/catalog-model'; +import { AuthenticationError } from '@backstage/errors'; +import { ResponseErrorPanel } from '@backstage/core'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import CardHeader from '@material-ui/core/CardHeader'; +import Divider from '@material-ui/core/Divider'; +import { makeStyles } from '@material-ui/core/styles'; +import { ILERT_INTEGRATION_KEY_ANNOTATION } from '../../constants'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { useIncidents } from '../../hooks/useIncidents'; +import { IncidentsTable } from '../IncidentsPage'; +import { IncidentNewModal } from '../Incident/IncidentNewModal'; +import { ILertCardActionsHeader } from './ILertCardActionsHeader'; +import { useAlertSource } from '../../hooks/useAlertSource'; +import { useILertEntity } from '../../hooks'; +import { ILertCardHeaderStatus } from './ILertCardHeaderStatus'; +import { ILertCardMaintenanceModal } from './ILertCardMaintenanceModal'; +import { ILertCardEmptyState } from './ILertCardEmptyState'; +import { ILertCardOnCall } from './ILertCardOnCall'; + +export const isPluginApplicableToEntity = (entity: Entity) => + Boolean(entity.metadata.annotations?.[ILERT_INTEGRATION_KEY_ANNOTATION]); + +const useStyles = makeStyles({ + content: { + paddingLeft: '0 !important', + paddingRight: '0 !important', + paddingBottom: '0 !important', + paddingTop: '0 !important', + '& div div': { + boxShadow: 'none !important', + }, + }, +}); + +export const ILertCard = () => { + const classes = useStyles(); + const { integrationKey, name } = useILertEntity(); + const [ + { alertSource, uptimeMonitor }, + { setAlertSource, refetchAlertSource }, + ] = useAlertSource(integrationKey); + const [ + { tableState, states, incidents, incidentsCount, isLoading, error }, + { + onIncidentStatesChange, + onChangePage, + onChangeRowsPerPage, + onIncidentChanged, + refetchIncidents, + setIsLoading, + }, + ] = useIncidents(false, true, alertSource); + + const [ + isNewIncidentModalOpened, + setIsNewIncidentModalOpened, + ] = React.useState(false); + const [ + isMaintenanceModalOpened, + setIsMaintenanceModalOpened, + ] = React.useState(false); + + if (error) { + if (error instanceof AuthenticationError) { + return ; + } + + return ; + } + + if (!integrationKey) { + return ; + } + + return ( + <> + + + } + action={} + /> + + + + + + + + + + ); +}; diff --git a/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx new file mode 100644 index 0000000000..c19c31839a --- /dev/null +++ b/plugins/ilert/src/components/ILertCard/ILertCardActionsHeader.tsx @@ -0,0 +1,203 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { + HeaderIconLinkRow, + IconLinkVerticalProps, + useApi, + alertApiRef, +} from '@backstage/core'; +import Alert from '@material-ui/lab/Alert'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import AlarmAddIcon from '@material-ui/icons/AlarmAdd'; +import BuildIcon from '@material-ui/icons/Build'; +import PauseIcon from '@material-ui/icons/Pause'; +import PlayArrowIcon from '@material-ui/icons/PlayArrow'; +import TimelineIcon from '@material-ui/icons/Timeline'; +import WebIcon from '@material-ui/icons/Web'; +import Typography from '@material-ui/core/Typography'; +import { ilertApiRef } from '../../api'; +import { AlertSource, UptimeMonitor } from '../../types'; + +export const ILertCardActionsHeader = ({ + alertSource, + setAlertSource, + setIsNewIncidentModalOpened, + setIsMaintenanceModalOpened, + uptimeMonitor, +}: { + alertSource: AlertSource | null; + setAlertSource: (alertSource: AlertSource) => void; + setIsNewIncidentModalOpened: (isOpen: boolean) => void; + setIsMaintenanceModalOpened: (isOpen: boolean) => void; + uptimeMonitor: UptimeMonitor | null; +}) => { + const ilertApi = useApi(ilertApiRef); + const alertApi = useApi(alertApiRef); + const [isLoading, setIsLoading] = React.useState(false); + const [isDisableModalOpened, setIsDisableModalOpened] = React.useState(false); + + const handleCreateNewIncident = () => { + setIsNewIncidentModalOpened(true); + }; + + const handleEnableAlertSource = async () => { + try { + if (!alertSource) { + return; + } + setIsLoading(true); + const newAlertSource = await ilertApi.enableAlertSource(alertSource); + alertApi.post({ message: 'Alert source enabled.' }); + setIsLoading(false); + setAlertSource(newAlertSource); + } catch (err) { + setIsLoading(false); + alertApi.post({ message: err, severity: 'error' }); + } + }; + const handleDisableAlertSource = async () => { + try { + if (!alertSource) { + return; + } + setIsDisableModalOpened(false); + setIsLoading(true); + const newAlertSource = await ilertApi.disableAlertSource(alertSource); + alertApi.post({ message: 'Alert source disabled.' }); + setIsLoading(false); + setAlertSource(newAlertSource); + } catch (err) { + setIsLoading(false); + alertApi.post({ message: err, severity: 'error' }); + } + }; + + const handleDisableAlertSourceWarningOpen = () => { + setIsDisableModalOpened(true); + }; + + const handleDisableAlertSourceWarningClose = () => { + setIsDisableModalOpened(false); + }; + + const handleMaintenanceAlertSource = () => { + setIsMaintenanceModalOpened(true); + }; + + const alertSourceLink: IconLinkVerticalProps = { + label: 'Alert Source', + href: ilertApi.getAlertSourceDetailsURL(alertSource), + icon: , + }; + + const createIncidentLink: IconLinkVerticalProps = { + label: 'Create Incident', + onClick: handleCreateNewIncident, + icon: , + color: 'secondary', + disabled: + !alertSource || + alertSource.status === 'DISABLED' || + alertSource.status === 'IN_MAINTENANCE', + }; + + const enableAlertSourceLink: IconLinkVerticalProps = { + label: 'Enable', + onClick: handleEnableAlertSource, + icon: , + disabled: !alertSource || isLoading, + }; + + const disableAlertSourceLink: IconLinkVerticalProps = { + label: 'Disable', + onClick: handleDisableAlertSourceWarningOpen, + icon: , + disabled: !alertSource || isLoading, + }; + + const maintenanceAlertSourceLink: IconLinkVerticalProps = { + label: 'Immediate maintenance', + onClick: handleMaintenanceAlertSource, + icon: , + disabled: !alertSource || isLoading, + }; + + const uptimeMonitorReportLink: IconLinkVerticalProps = { + label: 'Uptime Report', + href: uptimeMonitor ? uptimeMonitor.shareUrl : '', + icon: , + disabled: !alertSource || !uptimeMonitor || isLoading, + }; + + const links: IconLinkVerticalProps[] = [ + alertSourceLink, + createIncidentLink, + alertSource && alertSource.active + ? disableAlertSourceLink + : enableAlertSourceLink, + ]; + + if (alertSource && alertSource.integrationType === 'MONITOR') { + links.push(uptimeMonitorReportLink); + } + + if (alertSource && alertSource.status !== 'IN_MAINTENANCE') { + links.push(maintenanceAlertSourceLink); + } + + return ( + <> + + + + Disable alert source + + + + + Do you really want to disable this alert source? A disabled alert + source cannot create new incidents. + + + + + + + + + + ); +}; diff --git a/plugins/ilert/src/components/ILertCard/ILertCardEmptyState.tsx b/plugins/ilert/src/components/ILertCard/ILertCardEmptyState.tsx new file mode 100644 index 0000000000..955d514564 --- /dev/null +++ b/plugins/ilert/src/components/ILertCard/ILertCardEmptyState.tsx @@ -0,0 +1,84 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { CodeSnippet } from '@backstage/core'; +import { BackstageTheme } from '@backstage/theme'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import CardHeader from '@material-ui/core/CardHeader'; +import Button from '@material-ui/core/Button'; +import Divider from '@material-ui/core/Divider'; +import Typography from '@material-ui/core/Typography'; +import { makeStyles } from '@material-ui/core/styles'; +import React from 'react'; + +const ENTITY_YAML = `apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: example + description: example.com + annotations: + ilert.com/integration-key: [INTEGRATION_KEY] +spec: + type: website + lifecycle: production + owner: guest`; + +const useStyles = makeStyles(theme => ({ + code: { + borderRadius: 6, + margin: theme.spacing(2, 0), + background: theme.palette.type === 'dark' ? '#444' : '#fff', + }, + header: { + display: 'inline-block', + padding: theme.spacing(2, 2, 2, 2.5), + }, +})); + +export const ILertCardEmptyState = () => { + const classes = useStyles(); + + return ( + + + + + + No integration key defined for this entity. You can add integration + key to your entity YAML as shown in the highlighted example below: + +
+ +
+ +
+
+ ); +}; diff --git a/plugins/ilert/src/components/ILertCard/ILertCardHeaderStatus.tsx b/plugins/ilert/src/components/ILertCard/ILertCardHeaderStatus.tsx new file mode 100644 index 0000000000..2c1b44551e --- /dev/null +++ b/plugins/ilert/src/components/ILertCard/ILertCardHeaderStatus.tsx @@ -0,0 +1,46 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import Chip from '@material-ui/core/Chip'; +import { withStyles } from '@material-ui/core/styles'; +import { AlertSource } from '../../types'; + +const MaintenanceChip = withStyles({ + root: { + backgroundColor: '#92949c', + color: 'white', + marginTop: 8, + }, +})(Chip); + +export const ILertCardHeaderStatus = ({ + alertSource, +}: { + alertSource: AlertSource | null; +}) => { + if (!alertSource) { + return null; + } + + switch (alertSource.status) { + case 'IN_MAINTENANCE': + return ; + case 'DISABLED': + return ; + default: + return null; + } +}; diff --git a/plugins/ilert/src/components/ILertCard/ILertCardMaintenanceModal.tsx b/plugins/ilert/src/components/ILertCard/ILertCardMaintenanceModal.tsx new file mode 100644 index 0000000000..54d465a83c --- /dev/null +++ b/plugins/ilert/src/components/ILertCard/ILertCardMaintenanceModal.tsx @@ -0,0 +1,138 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { alertApiRef, useApi } from '@backstage/core'; +import Button from '@material-ui/core/Button'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogContentText from '@material-ui/core/DialogContentText'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import MenuItem from '@material-ui/core/MenuItem'; +import TextField from '@material-ui/core/TextField'; +import { ilertApiRef } from '../../api'; +import { AlertSource } from '../../types'; + +export const ILertCardMaintenanceModal = ({ + alertSource, + refetchAlertSource, + isModalOpened, + setIsModalOpened, +}: { + alertSource: AlertSource | null; + refetchAlertSource: () => void; + isModalOpened: boolean; + setIsModalOpened: (isModalOpened: boolean) => void; +}) => { + const ilertApi = useApi(ilertApiRef); + const alertApi = useApi(alertApiRef); + const [minutes, setMinutes] = React.useState(5); + + const handleClose = () => { + setIsModalOpened(false); + }; + + const handleImmediateMaintenance = () => { + if (!alertSource) { + return; + } + setIsModalOpened(false); + setTimeout(async () => { + try { + await ilertApi.addImmediateMaintenance(alertSource.id, minutes); + alertApi.post({ message: 'Maintenance started.' }); + refetchAlertSource(); + } catch (err) { + alertApi.post({ message: err, severity: 'error' }); + } + }, 250); + }; + + const handleMinutesChange = (event: any) => { + setMinutes(event.target.value); + }; + + const minuteOptions = [ + { + value: 5, + label: '5 minutes', + }, + { + value: 10, + label: '10 minutes', + }, + { + value: 15, + label: '15 minutes', + }, + { + value: 30, + label: '30 minutes', + }, + { + value: 60, + label: '60 minutes', + }, + ]; + + if (!alertSource) { + return null; + } + + return ( + + + New maintenance window + + + + Keep your alert sources quiet, when your systems are under + maintenance. + + + {minuteOptions.map(option => ( + + {option.label} + + ))} + + + + + + + + ); +}; diff --git a/plugins/ilert/src/components/ILertCard/ILertCardOnCall.tsx b/plugins/ilert/src/components/ILertCard/ILertCardOnCall.tsx new file mode 100644 index 0000000000..8f28790375 --- /dev/null +++ b/plugins/ilert/src/components/ILertCard/ILertCardOnCall.tsx @@ -0,0 +1,117 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListSubheader from '@material-ui/core/ListSubheader'; +import Typography from '@material-ui/core/Typography'; +import ReplayIcon from '@material-ui/icons/Replay'; +import { makeStyles } from '@material-ui/core/styles'; +import { AlertSource } from '../../types'; +import { useAlertSourceOnCalls } from '../../hooks/useAlertSourceOnCalls'; +import { Progress } from '@backstage/core'; +import { ILertCardOnCallEmptyState } from './ILertCardOnCallEmptyState'; +import { ILertCardOnCallItem } from './ILertCardOnCallItem'; + +const useStyles = makeStyles(theme => ({ + repeatText: { + fontStyle: 'italic', + }, + repeatIcon: { + marginLeft: theme.spacing(1), + }, +})); + +export const ILertCardOnCall = ({ + alertSource, +}: { + alertSource: AlertSource | null; +}) => { + const classes = useStyles(); + const [{ onCalls, isLoading }, {}] = useAlertSourceOnCalls(alertSource); + + if (isLoading) { + return ; + } + + if (!alertSource || !onCalls) { + return null; + } + + const repeatInfo = () => { + if ( + !alertSource || + !alertSource.escalationPolicy || + !alertSource.escalationPolicy.repeating || + !alertSource.escalationPolicy.frequency + ) { + return null; + } + + return ( + + + + + + {`Repeat ${alertSource.escalationPolicy.frequency} times`} + + } + /> + + ); + }; + + if (!onCalls.length) { + return ( + ON CALL}> + + + ); + } + + if (onCalls.length === 1) { + ON CALL}> + + ; + } + + return ( + ON CALL}> + {onCalls.map((onCall, index) => ( + + ))} + {repeatInfo()} + + ); +}; diff --git a/plugins/ilert/src/components/ILertCard/ILertCardOnCallEmptyState.tsx b/plugins/ilert/src/components/ILertCard/ILertCardOnCallEmptyState.tsx new file mode 100644 index 0000000000..2d43aeccb8 --- /dev/null +++ b/plugins/ilert/src/components/ILertCard/ILertCardOnCallEmptyState.tsx @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 ListItem from '@material-ui/core/ListItem'; +import ListItemText from '@material-ui/core/ListItemText'; +import Typography from '@material-ui/core/Typography'; + +const useStyles = makeStyles({ + text: { + fontStyle: 'italic', + }, +}); + +export const ILertCardOnCallEmptyState = () => { + const classes = useStyles(); + + return ( + + + + Nobody + + + + ); +}; diff --git a/plugins/ilert/src/components/ILertCard/ILertCardOnCallItem.tsx b/plugins/ilert/src/components/ILertCard/ILertCardOnCallItem.tsx new file mode 100644 index 0000000000..994666084b --- /dev/null +++ b/plugins/ilert/src/components/ILertCard/ILertCardOnCallItem.tsx @@ -0,0 +1,169 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 Divider from '@material-ui/core/Divider'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import Avatar from '@material-ui/core/Avatar'; +import ListItemText from '@material-ui/core/ListItemText'; +import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import Tooltip from '@material-ui/core/Tooltip'; +import IconButton from '@material-ui/core/IconButton'; +import Typography from '@material-ui/core/Typography'; +import EmailIcon from '@material-ui/icons/Email'; +import PhoneIcon from '@material-ui/icons/Phone'; +import { makeStyles } from '@material-ui/core/styles'; +import { OnCall } from '../../types'; +import { useApi } from '@backstage/core'; +import { ilertApiRef } from '../../api'; +import { DateTime as dt } from 'luxon'; + +const useStyles = makeStyles({ + listItemPrimary: { + fontWeight: 'bold', + }, + fistItemLine: { + position: 'absolute', + bottom: 0, + height: '50%', + left: 36, + }, + lastItemLine: { + position: 'absolute', + top: 0, + height: '50%', + left: 36, + }, + itemLine: { + position: 'absolute', + top: 0, + bottom: 0, + height: '100%', + left: 36, + }, +}); + +export const ILertCardOnCallItem = ({ + onCall, + fistItem = false, + lastItem = false, +}: { + onCall: OnCall; + fistItem?: boolean; + lastItem?: boolean; +}) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + if (!onCall || !onCall.user) { + return null; + } + + const phoneNumber = ilertApi.getUserPhoneNumber(onCall.user); + const escalationRepeating = onCall.escalationPolicy.repeating; + const escalationSeconds = + onCall.escalationPolicy.escalationRules[onCall.escalationLevel - 1] + .escalationTimeout; + const escalationHoursOnly = Math.floor(escalationSeconds / 60); + const escalationMinutesOnly = escalationSeconds % 60; + + let escalationText = ''; + if (!lastItem || (lastItem && escalationRepeating)) { + escalationText = 'escalate'; + if (escalationSeconds === 0) { + escalationText += ' immediately'; + } else { + escalationText += ' after'; + if (escalationHoursOnly > 0) { + escalationText += ` ${escalationHoursOnly} ${ + escalationHoursOnly === 1 ? 'hour' : 'hours' + }`; + } + if (escalationMinutesOnly > 0 || escalationSeconds === 0) { + escalationText += ` ${escalationMinutesOnly} ${ + escalationMinutesOnly === 1 ? 'minute' : 'minutes' + }`; + } + } + } + + return ( + + {fistItem ? ( + + ) : null} + {lastItem ? ( + + ) : null} + {!fistItem && !lastItem ? ( + + ) : null} + + + {onCall.escalationLevel} + + + {onCall.schedule ? ( + + + {ilertApi.getUserInitials(onCall.user)} + + } + secondary={escalationText} + /> + + ) : ( + + {ilertApi.getUserInitials(onCall.user)} + + } + secondary={escalationText} + /> + )} + + {phoneNumber ? ( + + + + + + ) : null} + + + + + + + + ); +}; diff --git a/plugins/ilert/src/components/ILertCard/index.ts b/plugins/ilert/src/components/ILertCard/index.ts new file mode 100644 index 0000000000..4ae259bfc5 --- /dev/null +++ b/plugins/ilert/src/components/ILertCard/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './ILertCard'; diff --git a/plugins/ilert/src/components/ILertPage/ILertPage.tsx b/plugins/ilert/src/components/ILertPage/ILertPage.tsx new file mode 100644 index 0000000000..40934bfdfb --- /dev/null +++ b/plugins/ilert/src/components/ILertPage/ILertPage.tsx @@ -0,0 +1,68 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { + Page, + Header, + HeaderTabs, + HeaderLabel, + Content, +} from '@backstage/core'; +import { IncidentsPage } from '../IncidentsPage'; +import { UptimeMonitorsPage } from '../UptimeMonitorsPage'; +import { OnCallSchedulesPage } from '../OnCallSchedulesPage'; + +export const ILertPage = () => { + const [selectedTab, setSelectedTab] = React.useState(0); + const tabs = [ + { label: 'Who is on call?' }, + { label: 'Incidents' }, + { label: 'Uptime Monitors' }, + ]; + const renderTab = () => { + switch (selectedTab) { + case 0: + return ; + case 1: + return ; + case 2: + return ; + default: + return null; + } + }; + + return ( + +
+ + +
+ setSelectedTab(index)} + tabs={tabs.map(({ label }, index) => ({ + id: index.toString(), + label, + }))} + /> + + {renderTab()} +
+ ); +}; + +export default ILertPage; diff --git a/plugins/ilert/src/components/ILertPage/index.ts b/plugins/ilert/src/components/ILertPage/index.ts new file mode 100644 index 0000000000..5de46b63d4 --- /dev/null +++ b/plugins/ilert/src/components/ILertPage/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './ILertPage'; diff --git a/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx b/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx new file mode 100644 index 0000000000..5b06cc4eb6 --- /dev/null +++ b/plugins/ilert/src/components/Incident/IncidentActionsMenu.tsx @@ -0,0 +1,203 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { + alertApiRef, + Progress, + useApi, + identityApiRef, + Link, +} from '@backstage/core'; +import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import { ilertApiRef } from '../../api'; +import { Incident, IncidentAction } from '../../types'; +import { IncidentAssignModal } from './IncidentAssignModal'; +import { useIncidentActions } from '../../hooks/useIncidentActions'; + +export const IncidentActionsMenu = ({ + incident, + onIncidentChanged, + setIsLoading, +}: { + incident: Incident; + onIncidentChanged?: (incident: Incident) => void; + setIsLoading?: (isLoading: boolean) => void; +}) => { + const ilertApi = useApi(ilertApiRef); + const alertApi = useApi(alertApiRef); + const identityApi = useApi(identityApiRef); + const userName = identityApi.getUserId(); + const [anchorEl, setAnchorEl] = React.useState(null); + const callback = onIncidentChanged || ((_: Incident): void => {}); + const setProcessing = setIsLoading || ((_: boolean): void => {}); + const [ + isAssignIncidentModalOpened, + setIsAssignIncidentModalOpened, + ] = React.useState(false); + + const [{ incidentActions, isLoading }] = useIncidentActions( + incident, + Boolean(anchorEl), + ); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleCloseMenu = () => { + setAnchorEl(null); + }; + + const handleAccept = async (): Promise => { + try { + handleCloseMenu(); + setProcessing(true); + const newIncident = await ilertApi.acceptIncident(incident, userName); + alertApi.post({ message: 'Incident accepted.' }); + + callback(newIncident); + setProcessing(false); + } catch (err) { + setProcessing(false); + alertApi.post({ message: err, severity: 'error' }); + } + }; + + const handleResolve = async (): Promise => { + try { + handleCloseMenu(); + setProcessing(true); + const newIncident = await ilertApi.resolveIncident(incident, userName); + alertApi.post({ message: 'Incident resolved.' }); + + callback(newIncident); + setProcessing(false); + } catch (err) { + setProcessing(false); + alertApi.post({ message: err, severity: 'error' }); + } + }; + + const handleAssign = () => { + handleCloseMenu(); + setIsAssignIncidentModalOpened(true); + }; + + const handleTriggerAction = (action: IncidentAction) => async () => { + try { + handleCloseMenu(); + setProcessing(true); + await ilertApi.triggerIncidentAction(incident, action); + alertApi.post({ message: 'Incident action triggered.' }); + setProcessing(false); + } catch (err) { + setProcessing(false); + alertApi.post({ message: err, severity: 'error' }); + } + }; + + const actions: React.ReactNode[] = incidentActions.map(a => { + const successTrigger = a.history + ? a.history.find(h => h.success) + : undefined; + const triggeredBy = + successTrigger && successTrigger.actor + ? `${successTrigger.actor.firstName} ${successTrigger.actor.lastName}` + : ''; + return ( + + + {triggeredBy ? `${a.name} (by ${triggeredBy})` : a.name} + + + ); + }); + + return ( + <> + + + + + {incident.status === 'PENDING' ? ( + + + Accept + + + ) : null} + + {incident.status !== 'RESOLVED' ? ( + + + Resolve + + + ) : null} + + {incident.status !== 'RESOLVED' ? ( + + + Assign + + + ) : null} + + {isLoading ? ( + + + + ) : ( + actions + )} + + + + + View in iLert + + + + + + + ); +}; diff --git a/plugins/ilert/src/components/Incident/IncidentAssignModal.tsx b/plugins/ilert/src/components/Incident/IncidentAssignModal.tsx new file mode 100644 index 0000000000..edac5e4e0e --- /dev/null +++ b/plugins/ilert/src/components/Incident/IncidentAssignModal.tsx @@ -0,0 +1,183 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { alertApiRef, useApi } from '@backstage/core'; +import { makeStyles } from '@material-ui/core/styles'; +import Alert from '@material-ui/lab/Alert'; +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import { useAssignIncident } from '../../hooks/useAssignIncident'; +import { Typography } from '@material-ui/core'; +import { ilertApiRef } from '../../api'; +import { Incident } from '../../types'; + +const useStyles = makeStyles(() => ({ + container: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + minWidth: 120, + width: '100%', + }, + option: { + fontSize: 15, + '& > span': { + marginRight: 10, + fontSize: 18, + }, + }, + optionWrapper: { + display: 'flex', + width: '100%', + }, + sourceImage: { + height: 22, + paddingRight: 4, + }, +})); + +export const IncidentAssignModal = ({ + incident, + isModalOpened, + setIsModalOpened, + onIncidentChanged, +}: { + incident: Incident | null; + isModalOpened: boolean; + setIsModalOpened: (open: boolean) => void; + onIncidentChanged?: (incident: Incident) => void; +}) => { + const [ + { incidentRespondersList, incidentResponder, isLoading }, + { setIsLoading, setIncidentResponder, setIncidentRespondersList }, + ] = useAssignIncident(incident, isModalOpened); + const callback = onIncidentChanged || ((_: Incident): void => {}); + const ilertApi = useApi(ilertApiRef); + const alertApi = useApi(alertApiRef); + const classes = useStyles(); + + const handleClose = () => { + setIncidentRespondersList([]); + setIsModalOpened(false); + }; + + const handleAssign = () => { + if (!incident || !incidentResponder) { + return; + } + setIsLoading(true); + setIncidentRespondersList([]); + setTimeout(async () => { + try { + const newIncident = await ilertApi.assignIncident( + incident, + incidentResponder, + ); + callback(newIncident); + alertApi.post({ message: 'Incident assigned.' }); + } catch (err) { + alertApi.post({ message: err, severity: 'error' }); + } + setIsLoading(false); + setIsModalOpened(false); + }, 250); + }; + + const canAssign = !!incidentResponder; + + return ( + + + Select responder to assign + + + + + This action will assign the incident to the selected responder. + + + { + setIncidentResponder(newValue); + }} + autoHighlight + groupBy={option => { + switch (option.group) { + case 'SUGGESTED': + return 'Suggested responders'; + case 'USER': + return 'Users'; + case 'ESCALATION_POLICY': + return 'Escalation policies'; + case 'ON_CALL_SCHEDULE': + return 'Schedules'; + default: + return ''; + } + }} + getOptionLabel={a => a.name} + renderOption={a => ( +
+ {a.name} +
+ )} + renderInput={params => ( + + )} + /> +
+ + + + +
+ ); +}; diff --git a/plugins/ilert/src/components/Incident/IncidentLink.tsx b/plugins/ilert/src/components/Incident/IncidentLink.tsx new file mode 100644 index 0000000000..7bfe08b7a9 --- /dev/null +++ b/plugins/ilert/src/components/Incident/IncidentLink.tsx @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { useApi, Link } from '@backstage/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { Incident } from '../../types'; +import { ilertApiRef } from '../../api'; + +const useStyles = makeStyles({ + link: { + lineHeight: '22px', + }, +}); + +export const IncidentLink = ({ incident }: { incident: Incident | null }) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + if (!incident) { + return null; + } + + return ( + + #{incident.id} + + ); +}; diff --git a/plugins/ilert/src/components/Incident/IncidentNewModal.tsx b/plugins/ilert/src/components/Incident/IncidentNewModal.tsx new file mode 100644 index 0000000000..8ff33e6319 --- /dev/null +++ b/plugins/ilert/src/components/Incident/IncidentNewModal.tsx @@ -0,0 +1,228 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { alertApiRef, identityApiRef, useApi } from '@backstage/core'; +import { makeStyles } from '@material-ui/core/styles'; +import Alert from '@material-ui/lab/Alert'; +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import { useNewIncident } from '../../hooks/useNewIncident'; +import { Typography } from '@material-ui/core'; +import useMediaQuery from '@material-ui/core/useMediaQuery'; +import { ilertApiRef } from '../../api'; +import { AlertSource } from '../../types'; + +const useStyles = makeStyles(() => ({ + container: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + minWidth: 120, + width: '100%', + }, + option: { + fontSize: 15, + '& > span': { + marginRight: 10, + fontSize: 18, + }, + }, + optionWrapper: { + display: 'flex', + width: '100%', + }, + sourceImage: { + height: 22, + paddingRight: 4, + }, +})); + +export const IncidentNewModal = ({ + isModalOpened, + setIsModalOpened, + refetchIncidents, + initialAlertSource, + entityName, +}: { + isModalOpened: boolean; + setIsModalOpened: (open: boolean) => void; + refetchIncidents: () => void; + initialAlertSource?: AlertSource | null; + entityName?: string; +}) => { + const [ + { alertSources, alertSource, summary, details, isLoading }, + { setAlertSource, setSummary, setDetails, setIsLoading }, + ] = useNewIncident(isModalOpened, initialAlertSource); + const ilertApi = useApi(ilertApiRef); + const alertApi = useApi(alertApiRef); + const identityApi = useApi(identityApiRef); + const userName = identityApi.getUserId(); + const source = window.location.toString(); + const classes = useStyles(); + const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)'); + + const handleClose = () => { + setIsModalOpened(false); + }; + + let integrationKey = ''; + if (initialAlertSource && initialAlertSource.integrationKey) { + integrationKey = initialAlertSource.integrationKey; + } else if (alertSource && alertSource.integrationKey) { + integrationKey = alertSource.integrationKey; + } + const handleCreate = () => { + if (!integrationKey) { + return; + } + setIsLoading(true); + setTimeout(async () => { + try { + await ilertApi.createIncident({ + integrationKey, + summary, + details, + userName, + source, + }); + alertApi.post({ message: 'Incident created.' }); + refetchIncidents(); + } catch (err) { + alertApi.post({ message: err, severity: 'error' }); + } + setIsModalOpened(false); + }, 250); + }; + + const canCreate = !!integrationKey && !!summary; + + return ( + + + {entityName ? ( +
+ This action will trigger an incident for{' '} + "{entityName}". +
+ ) : ( + 'New incident' + )} +
+ + + + Please describe the problem you want to report. Be as descriptive as + possible. Your signed in user and a reference to the current page + will automatically be amended to the alarm so that the receiver can + reach out to you if necessary. + + + {!initialAlertSource ? ( + { + setAlertSource(newValue); + }} + autoHighlight + getOptionLabel={a => a.name} + renderOption={a => ( +
+ {a.name} + {a.name} +
+ )} + renderInput={params => ( + + )} + /> + ) : null} + { + setSummary(event.target.value); + }} + /> + { + setDetails(event.target.value); + }} + /> +
+ + + + +
+ ); +}; diff --git a/plugins/ilert/src/components/Incident/IncidentStatus.tsx b/plugins/ilert/src/components/Incident/IncidentStatus.tsx new file mode 100644 index 0000000000..931a472ec6 --- /dev/null +++ b/plugins/ilert/src/components/Incident/IncidentStatus.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 } from '@backstage/core'; +import { makeStyles } from '@material-ui/core/styles'; +import Tooltip from '@material-ui/core/Tooltip'; +import { ACCEPTED, Incident, PENDING, RESOLVED } from '../../types'; + +const useStyles = makeStyles({ + denseListIcon: { + marginRight: 0, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + }, +}); + +export const incidentStatusLabels = { + [RESOLVED]: 'Resolved', + [ACCEPTED]: 'Accepted', + [PENDING]: 'Pending', +} as Record; + +export const IncidentStatus = ({ incident }: { incident: Incident }) => { + const classes = useStyles(); + + return ( + +
+ {incident.status === 'PENDING' ? : } +
+
+ ); +}; diff --git a/plugins/ilert/src/components/Incident/index.ts b/plugins/ilert/src/components/Incident/index.ts new file mode 100644 index 0000000000..df8bab5d95 --- /dev/null +++ b/plugins/ilert/src/components/Incident/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './IncidentActionsMenu'; +export * from './IncidentStatus'; diff --git a/plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx b/plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx new file mode 100644 index 0000000000..782848645e --- /dev/null +++ b/plugins/ilert/src/components/IncidentsPage/IncidentsPage.tsx @@ -0,0 +1,101 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { + Content, + ContentHeader, + SupportButton, + ResponseErrorPanel, +} from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import Button from '@material-ui/core/Button'; +import AddIcon from '@material-ui/icons/Add'; +import { IncidentsTable } from './IncidentsTable'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { useIncidents } from '../../hooks/useIncidents'; +import { IncidentNewModal } from '../Incident/IncidentNewModal'; + +export const IncidentsPage = () => { + const [ + { tableState, states, incidents, incidentsCount, isLoading, error }, + { + onIncidentStatesChange, + onChangePage, + onChangeRowsPerPage, + onIncidentChanged, + refetchIncidents, + setIsLoading, + }, + ] = useIncidents(true); + + const [isModalOpened, setIsModalOpened] = React.useState(false); + + const handleCreateNewIncidentClick = () => { + setIsModalOpened(true); + }; + + if (error) { + if (error instanceof AuthenticationError) { + return ( + + + + ); + } + + return ( + + + + ); + } + + return ( + + + + + + This helps you to bring iLert into your developer portal. + + + + + ); +}; diff --git a/plugins/ilert/src/components/IncidentsPage/IncidentsTable.tsx b/plugins/ilert/src/components/IncidentsPage/IncidentsTable.tsx new file mode 100644 index 0000000000..087b2bfd40 --- /dev/null +++ b/plugins/ilert/src/components/IncidentsPage/IncidentsTable.tsx @@ -0,0 +1,262 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Table, TableColumn, useApi } from '@backstage/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { ilertApiRef, TableState } from '../../api'; +import { Incident, IncidentStatus } from '../../types'; +import { StatusChip } from './StatusChip'; +import { AlertSourceLink } from '../AlertSource/AlertSourceLink'; +import { TableTitle } from './TableTitle'; +import Typography from '@material-ui/core/Typography'; +import { DateTime as dt, Interval } from 'luxon'; +import humanizeDuration from 'humanize-duration'; +import { IncidentActionsMenu } from '../Incident/IncidentActionsMenu'; +import { IncidentLink } from '../Incident/IncidentLink'; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const IncidentsTable = ({ + incidents, + incidentsCount, + tableState, + states, + isLoading, + onIncidentChanged, + setIsLoading, + onIncidentStatesChange, + onChangePage, + onChangeRowsPerPage, + compact, +}: { + incidents: Incident[]; + incidentsCount: number; + tableState: TableState; + states: IncidentStatus[]; + isLoading: boolean; + onIncidentChanged: (incident: Incident) => void; + setIsLoading: (isLoading: boolean) => void; + onIncidentStatesChange: (states: IncidentStatus[]) => void; + onChangePage: (page: number) => void; + onChangeRowsPerPage: (pageSize: number) => void; + compact?: boolean; +}) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + const xsColumnStyle = { + width: '5%', + maxWidth: '5%', + }; + const smColumnStyle = { + width: '10%', + maxWidth: '10%', + }; + const mdColumnStyle = { + width: '15%', + maxWidth: '15%', + }; + const lgColumnStyle = { + width: '20%', + maxWidth: '20%', + }; + const xlColumnStyle = { + width: '30%', + maxWidth: '30%', + }; + + const idColumn: TableColumn = { + title: 'ID', + field: 'id', + highlight: true, + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => , + }; + const summaryColumn: TableColumn = { + title: 'Summary', + field: 'summary', + cellStyle: !compact ? xlColumnStyle : undefined, + headerStyle: !compact ? xlColumnStyle : undefined, + render: rowData => {(rowData as Incident).summary}, + }; + const sourceColumn: TableColumn = { + title: 'Source', + field: 'source', + cellStyle: mdColumnStyle, + headerStyle: mdColumnStyle, + render: rowData => ( + + ), + }; + const durationColumn: TableColumn = { + title: 'Duration', + field: 'reportTime', + type: 'datetime', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => ( + + {(rowData as Incident).status !== 'RESOLVED' + ? humanizeDuration( + Interval.fromDateTimes( + dt.fromISO((rowData as Incident).reportTime), + dt.now(), + ) + .toDuration() + .valueOf(), + { units: ['h', 'm', 's'], largest: 2, round: true }, + ) + : humanizeDuration( + Interval.fromDateTimes( + dt.fromISO((rowData as Incident).reportTime), + dt.fromISO((rowData as Incident).resolvedOn), + ) + .toDuration() + .valueOf(), + { units: ['h', 'm', 's'], largest: 2, round: true }, + )} + + ), + }; + const assignedToColumn: TableColumn = { + title: 'Assigned to', + field: 'assignedTo', + cellStyle: !compact ? mdColumnStyle : lgColumnStyle, + headerStyle: !compact ? mdColumnStyle : lgColumnStyle, + render: rowData => ( + + {ilertApi.getUserInitials((rowData as Incident).assignedTo)} + + ), + }; + const priorityColumn: TableColumn = { + title: 'Priority', + field: 'priority', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => ( + + {(rowData as Incident).priority === 'HIGH' ? 'High' : 'Low'} + + ), + }; + const statusColumn: TableColumn = { + title: 'Status', + field: 'status', + cellStyle: xsColumnStyle, + headerStyle: xsColumnStyle, + render: rowData => , + }; + const actionsColumn: TableColumn = { + title: '', + field: '', + cellStyle: xsColumnStyle, + headerStyle: xsColumnStyle, + render: rowData => ( + + ), + }; + + const columns: TableColumn[] = compact + ? [ + summaryColumn, + durationColumn, + assignedToColumn, + statusColumn, + actionsColumn, + ] + : [ + idColumn, + summaryColumn, + sourceColumn, + durationColumn, + assignedToColumn, + priorityColumn, + statusColumn, + actionsColumn, + ]; + let tableStyle: React.CSSProperties = {}; + if (compact) { + tableStyle = { + width: '100%', + maxWidth: '100%', + minWidth: '0', + height: 'calc(100% - 10px)', + boxShadow: 'none !important', + borderRadius: 'none !important', + }; + } else { + tableStyle = { + width: '100%', + maxWidth: '100%', + }; + } + + return ( + + No incidents right now + + } + title={ + !compact ? ( + + ) : ( + + INCIDENTS + + ) + } + page={tableState.page} + totalCount={incidentsCount} + onChangePage={onChangePage} + onChangeRowsPerPage={onChangeRowsPerPage} + // localization={{ header: { actions: undefined } }} + columns={columns} + data={incidents} + isLoading={isLoading} + /> + ); +}; diff --git a/plugins/ilert/src/components/IncidentsPage/StatusChip.tsx b/plugins/ilert/src/components/IncidentsPage/StatusChip.tsx new file mode 100644 index 0000000000..9713b16a55 --- /dev/null +++ b/plugins/ilert/src/components/IncidentsPage/StatusChip.tsx @@ -0,0 +1,57 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { Chip, withStyles } from '@material-ui/core'; +import { Incident, PENDING, ACCEPTED, RESOLVED } from '../../types'; +import { incidentStatusLabels } from '../Incident/IncidentStatus'; + +const ResolvedChip = withStyles({ + root: { + backgroundColor: '#4caf50', + color: 'white', + margin: 0, + }, +})(Chip); + +const AcceptedChip = withStyles({ + root: { + backgroundColor: '#ffb74d', + color: 'white', + margin: 0, + }, +})(Chip); +const PendingChip = withStyles({ + root: { + backgroundColor: '#d32f2f', + color: 'white', + margin: 0, + }, +})(Chip); + +export const StatusChip = ({ incident }: { incident: Incident }) => { + const label = `${incidentStatusLabels[incident.status]}`; + + switch (incident.status) { + case RESOLVED: + return ; + case ACCEPTED: + return ; + case PENDING: + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/IncidentsPage/TableTitle.tsx b/plugins/ilert/src/components/IncidentsPage/TableTitle.tsx new file mode 100644 index 0000000000..f35981dec2 --- /dev/null +++ b/plugins/ilert/src/components/IncidentsPage/TableTitle.tsx @@ -0,0 +1,97 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { PENDING, ACCEPTED, RESOLVED, IncidentStatus } from '../../types'; +import { incidentStatusLabels } from '../Incident/IncidentStatus'; +import FormControl from '@material-ui/core/FormControl'; +import ListItemText from '@material-ui/core/ListItemText'; +import Select from '@material-ui/core/Select'; +import Typography from '@material-ui/core/Typography'; +import MenuItem from '@material-ui/core/MenuItem'; +import Checkbox from '@material-ui/core/Checkbox'; +import { makeStyles } from '@material-ui/core/styles'; + +const ITEM_HEIGHT = 48; +const ITEM_PADDING_TOP = 8; +const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + width: 250, + }, + }, +}; + +const useStyles = makeStyles({ + root: { + display: 'flex', + }, + label: { + marginTop: 8, + marginRight: 4, + }, + formControl: { + minWidth: 120, + maxWidth: 300, + }, + grow: { + flexGrow: 1, + }, +}); + +export const TableTitle = ({ + incidentStates, + onIncidentStatesChange, +}: { + incidentStates: IncidentStatus[]; + onIncidentStatesChange: (states: IncidentStatus[]) => void; +}) => { + const classes = useStyles(); + const handleIncidentStatusSelectChange = (event: any) => { + onIncidentStatesChange(event.target.value); + }; + + return ( +
+ + Status: + + + + +
+ ); +}; diff --git a/plugins/ilert/src/components/IncidentsPage/index.ts b/plugins/ilert/src/components/IncidentsPage/index.ts new file mode 100644 index 0000000000..7159247c68 --- /dev/null +++ b/plugins/ilert/src/components/IncidentsPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './IncidentsPage'; +export * from './IncidentsTable'; diff --git a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallSchedulesGrid.tsx b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallSchedulesGrid.tsx new file mode 100644 index 0000000000..31da9c1097 --- /dev/null +++ b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallSchedulesGrid.tsx @@ -0,0 +1,171 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { useApi, ItemCardGrid, Progress, Link } from '@backstage/core'; +import { makeStyles } from '@material-ui/core/styles'; +import Card from '@material-ui/core/Card'; +import CardContent from '@material-ui/core/CardContent'; +import CardHeader from '@material-ui/core/CardHeader'; +import Typography from '@material-ui/core/Typography'; +import { Schedule } from '../../types'; +import { ilertApiRef } from '../../api'; +import { OnCallShiftItem } from './OnCallShiftItem'; + +const useStyles = makeStyles(() => ({ + card: { + margin: 16, + width: 'calc(100% - 32px)', + }, + + cardHeader: { + maxWidth: '100%', + }, + + cardContent: { + marginLeft: 80, + borderLeft: '1px #808289 solid', + position: 'relative', + }, + + indicatorNext: { + position: 'absolute', + top: 'calc(40% - 10px)', + left: -6, + width: 12, + height: 12, + background: '#92949c !important', + borderRadius: '50%', + }, + + indicatorCurrent: { + position: 'absolute', + top: 'calc(40% - 10px)', + left: -6, + width: 12, + height: 12, + background: '#ffb74d !important', + color: '#ffb74d !important', + borderRadius: '50%', + '&::after': { + position: 'absolute', + top: 0, + left: 0, + width: '100%', + height: '100%', + borderRadius: '50%', + animation: '$ripple 1.2s infinite ease-in-out', + border: '1px solid currentColor', + content: '""', + }, + }, + '@keyframes ripple': { + '0%': { + transform: 'scale(.8)', + opacity: 1, + }, + '100%': { + transform: 'scale(2.4)', + opacity: 0, + }, + }, + + beforeText: { + position: 'absolute', + top: 'calc(31% - 10px)', + left: -78, + width: 65, + height: 20, + textAlign: 'center', + color: '#808289', + }, + + marginBottom: { + marginBottom: 16, + }, + + link: { + fontSize: '1.5rem', + fontWeight: 700, + overflow: 'hidden', + whiteSpace: 'nowrap', + textOverflow: 'ellipsis', + display: 'block', + }, +})); + +export const OnCallSchedulesGrid = ({ + onCallSchedules, + isLoading, + refetchOnCallSchedules, +}: { + onCallSchedules: Schedule[]; + isLoading: boolean; + refetchOnCallSchedules: () => void; +}) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + if (isLoading) { + return ; + } + return ( + + {!onCallSchedules?.length + ? null + : onCallSchedules.map((schedule, index) => ( + + + {schedule.name} + + } + /> + + +
+ + + On call now + + + + +
+ + + Next on call + + + + ))} + + ); +}; diff --git a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallSchedulesPage.tsx b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallSchedulesPage.tsx new file mode 100644 index 0000000000..bb0cd0218e --- /dev/null +++ b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallSchedulesPage.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { + Content, + ContentHeader, + SupportButton, + ResponseErrorPanel, +} from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { OnCallSchedulesGrid } from './OnCallSchedulesGrid'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { useOnCallSchedules } from '../../hooks/useOnCallSchedules'; + +export const OnCallSchedulesPage = () => { + const [ + { onCallSchedules, isLoading, error }, + { refetchOnCallSchedules }, + ] = useOnCallSchedules(); + + if (error) { + if (error instanceof AuthenticationError) { + return ( + + + + ); + } + + return ( + + + + ); + } + + return ( + + + + This helps you to bring iLert into your developer portal. + + + + + ); +}; diff --git a/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx new file mode 100644 index 0000000000..46f6963445 --- /dev/null +++ b/plugins/ilert/src/components/OnCallSchedulesPage/OnCallShiftItem.tsx @@ -0,0 +1,105 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 Button from '@material-ui/core/Button'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import RepeatIcon from '@material-ui/icons/Repeat'; +import { Shift } from '../../types'; +import { DateTime as dt } from 'luxon'; +import { makeStyles } from '@material-ui/core/styles'; +import { ShiftOverrideModal } from '../Shift/ShiftOverrideModal'; + +const useStyles = makeStyles({ + button: { + marginTop: 4, + padding: 0, + lineHeight: 1.8, + '& span': { + lineHeight: 1.8, + fontSize: '0.65rem', + }, + '& svg': { + fontSize: '0.85rem !important', + }, + }, +}); + +export const OnCallShiftItem = ({ + scheduleId, + shift, + refetchOnCallSchedules, +}: { + scheduleId: number; + shift: Shift; + refetchOnCallSchedules: () => void; +}) => { + const classes = useStyles(); + const [isModalOpened, setIsModalOpened] = React.useState(false); + + const handleOverride = () => { + setIsModalOpened(true); + }; + + if (!shift || !shift.start) { + return ( + + + + Nobody + + + + ); + } + + return ( + + {shift && shift.user ? ( + + + {`${shift.user.firstName} ${shift.user.lastName} (${shift.user.username})`} + + + ) : null} + + + {`${dt.fromISO(shift.start).toFormat('D MMM, HH:mm')} - ${dt + .fromISO(shift.end) + .toFormat('D MMM, HH:mm')}`} + + + + + + + + ); +}; diff --git a/plugins/ilert/src/components/OnCallSchedulesPage/index.ts b/plugins/ilert/src/components/OnCallSchedulesPage/index.ts new file mode 100644 index 0000000000..a52accc2bb --- /dev/null +++ b/plugins/ilert/src/components/OnCallSchedulesPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './OnCallSchedulesPage'; +export * from './OnCallSchedulesGrid'; diff --git a/plugins/ilert/src/components/Shift/ShiftOverrideModal.tsx b/plugins/ilert/src/components/Shift/ShiftOverrideModal.tsx new file mode 100644 index 0000000000..47e13f9b7e --- /dev/null +++ b/plugins/ilert/src/components/Shift/ShiftOverrideModal.tsx @@ -0,0 +1,196 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { alertApiRef, useApi } from '@backstage/core'; +import { makeStyles } from '@material-ui/core/styles'; +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import Dialog from '@material-ui/core/Dialog'; +import DialogActions from '@material-ui/core/DialogActions'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import Autocomplete from '@material-ui/lab/Autocomplete'; +import { Typography } from '@material-ui/core'; +import { ilertApiRef } from '../../api'; +import { useShiftOverride } from '../../hooks/useShiftOverride'; +import { Shift } from '../../types'; +import { DateTimePicker, MuiPickersUtilsProvider } from '@material-ui/pickers'; +import LuxonUtils from '@date-io/luxon'; + +const useStyles = makeStyles(() => ({ + container: { + display: 'flex', + flexWrap: 'wrap', + }, + formControl: { + minWidth: 120, + width: '100%', + }, + option: { + fontSize: 15, + '& > span': { + marginRight: 10, + fontSize: 18, + }, + }, + optionWrapper: { + display: 'flex', + width: '100%', + }, + sourceImage: { + height: 22, + paddingRight: 4, + }, + grow: { + flexGrow: 1, + }, +})); + +export const ShiftOverrideModal = ({ + scheduleId, + shift, + refetchOnCallSchedules, + isModalOpened, + setIsModalOpened, +}: { + scheduleId: number; + shift: Shift; + refetchOnCallSchedules: () => void; + isModalOpened: boolean; + setIsModalOpened: (isModalOpened: boolean) => void; +}) => { + const [ + { isLoading, users, user, start, end }, + { setUser, setStart, setEnd, setIsLoading }, + ] = useShiftOverride(shift, isModalOpened); + const ilertApi = useApi(ilertApiRef); + const alertApi = useApi(alertApiRef); + const classes = useStyles(); + + const handleClose = () => { + setIsModalOpened(false); + }; + + const handleOverride = () => { + if (!shift || !shift.user) { + return; + } + setIsLoading(true); + setTimeout(async () => { + try { + const success = await ilertApi.overrideShift( + scheduleId, + user.id, + start, + end, + ); + if (success) { + alertApi.post({ message: 'Shift overridden.' }); + refetchOnCallSchedules(); + } + } catch (err) { + alertApi.post({ message: err, severity: 'error' }); + } + setIsModalOpened(false); + }, 250); + }; + + if (!shift) { + return null; + } + + return ( + + Shift override + + + { + setUser(newValue); + }} + autoHighlight + getOptionLabel={a => ilertApi.getUserInitials(a)} + renderOption={a => ( +
+ {ilertApi.getUserInitials(a)} +
+ )} + renderInput={params => ( + + )} + /> + { + setStart(date ? date.toISO() : ''); + }} + /> + { + setEnd(date ? date.toISO() : ''); + }} + /> +
+
+ + + + +
+ ); +}; diff --git a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx b/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx new file mode 100644 index 0000000000..36671bff90 --- /dev/null +++ b/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorActionsMenu.tsx @@ -0,0 +1,135 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { alertApiRef, useApi, Link } from '@backstage/core'; +import { IconButton, Menu, MenuItem, Typography } from '@material-ui/core'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; + +import { ilertApiRef } from '../../api'; +import { UptimeMonitor } from '../../types'; + +export const UptimeMonitorActionsMenu = ({ + uptimeMonitor, + onUptimeMonitorChanged, +}: { + uptimeMonitor: UptimeMonitor; + onUptimeMonitorChanged?: (uptimeMonitor: UptimeMonitor) => void; +}) => { + const ilertApi = useApi(ilertApiRef); + const alertApi = useApi(alertApiRef); + const [anchorEl, setAnchorEl] = React.useState(null); + const callback = onUptimeMonitorChanged || ((_: UptimeMonitor): void => {}); + + const handleClick = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleCloseMenu = () => { + setAnchorEl(null); + }; + + const handlePause = async (): Promise => { + try { + const newUptimeMonitor = await ilertApi.pauseUptimeMonitor(uptimeMonitor); + handleCloseMenu(); + alertApi.post({ message: 'Uptime monitor paused.' }); + + callback(newUptimeMonitor); + } catch (err) { + alertApi.post({ message: err, severity: 'error' }); + } + }; + + const handleResume = async (): Promise => { + try { + const newUptimeMonitor = await ilertApi.resumeUptimeMonitor( + uptimeMonitor, + ); + handleCloseMenu(); + alertApi.post({ message: 'Uptime monitor resumed.' }); + + callback(newUptimeMonitor); + } catch (err) { + alertApi.post({ message: err, severity: 'error' }); + } + }; + + const handleOpenReport = async (): Promise => { + try { + const um = await ilertApi.fetchUptimeMonitor(uptimeMonitor.id); + handleCloseMenu(); + window.open(um.shareUrl, '_blank'); + } catch (err) { + alertApi.post({ message: err, severity: 'error' }); + } + }; + + return ( + <> + + + + + {uptimeMonitor.paused ? ( + + + Resume + + + ) : null} + + {!uptimeMonitor.paused ? ( + + + Pause + + + ) : null} + + + + + View Report + + + + + + + + View in iLert + + + + + + ); +}; diff --git a/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx b/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx new file mode 100644 index 0000000000..507e0208e2 --- /dev/null +++ b/plugins/ilert/src/components/UptimeMonitor/UptimeMonitorLink.tsx @@ -0,0 +1,48 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { useApi, Link } from '@backstage/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { UptimeMonitor } from '../../types'; +import { ilertApiRef } from '../../api'; + +const useStyles = makeStyles({ + link: { + lineHeight: '22px', + }, +}); + +export const UptimeMonitorLink = ({ + uptimeMonitor, +}: { + uptimeMonitor: UptimeMonitor | null; +}) => { + const ilertApi = useApi(ilertApiRef); + const classes = useStyles(); + + if (!uptimeMonitor) { + return null; + } + + return ( + + #{uptimeMonitor.id} + + ); +}; diff --git a/plugins/ilert/src/components/UptimeMonitor/index.ts b/plugins/ilert/src/components/UptimeMonitor/index.ts new file mode 100644 index 0000000000..f30d1ef15a --- /dev/null +++ b/plugins/ilert/src/components/UptimeMonitor/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './UptimeMonitorActionsMenu'; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx new file mode 100644 index 0000000000..dd17b1758b --- /dev/null +++ b/plugins/ilert/src/components/UptimeMonitorsPage/StatusChip.tsx @@ -0,0 +1,73 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import Chip from '@material-ui/core/Chip'; +import { withStyles } from '@material-ui/core/styles'; +import { UptimeMonitor } from '../../types'; + +const UpChip = withStyles({ + root: { + backgroundColor: '#4caf50', + color: 'white', + margin: 0, + }, +})(Chip); + +const DownChip = withStyles({ + root: { + backgroundColor: '#d32f2f', + color: 'white', + margin: 0, + }, +})(Chip); + +const UnknownChip = withStyles({ + root: { + backgroundColor: '#92949c', + color: 'white', + margin: 0, + }, +})(Chip); + +export const uptimeMonitorStatusLabels = { + ['up']: 'Up', + ['down']: 'Down', + ['unknown']: 'Unknown', +} as Record; + +export const StatusChip = ({ + uptimeMonitor, +}: { + uptimeMonitor: UptimeMonitor; +}) => { + let label = `${uptimeMonitorStatusLabels[uptimeMonitor.status]}`; + + if (uptimeMonitor.paused) { + label = 'Paused'; + return ; + } + + switch (uptimeMonitor.status) { + case 'up': + return ; + case 'down': + return ; + case 'unknown': + return ; + default: + return ; + } +}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx new file mode 100644 index 0000000000..3fdecd65e9 --- /dev/null +++ b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorCheckType.tsx @@ -0,0 +1,39 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { UptimeMonitor } from '../../types'; +import Typography from '@material-ui/core/Typography'; + +export const UptimeMonitorCheckType = ({ + uptimeMonitor, +}: { + uptimeMonitor: UptimeMonitor; +}) => { + switch (uptimeMonitor.region) { + case 'EU': + return ( + {`${uptimeMonitor.checkType.toUpperCase()} 🇩🇪`} + ); + default: + return ( + {`${uptimeMonitor.checkType.toUpperCase()} 🇺🇸`} + ); + } +}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx new file mode 100644 index 0000000000..9769fa408d --- /dev/null +++ b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsPage.tsx @@ -0,0 +1,67 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { + Content, + ContentHeader, + SupportButton, + ResponseErrorPanel, +} from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { UptimeMonitorsTable } from './UptimeMonitorsTable'; +import { MissingAuthorizationHeaderError } from '../Errors'; +import { useUptimeMonitors } from '../../hooks/useUptimeMonitors'; + +export const UptimeMonitorsPage = () => { + const [ + { tableState, uptimeMonitors, isLoading, error }, + { onChangePage, onChangeRowsPerPage, onUptimeMonitorChanged }, + ] = useUptimeMonitors(); + + if (error) { + if (error instanceof AuthenticationError) { + return ( + + + + ); + } + + return ( + + + + ); + } + + return ( + + + + This helps you to bring iLert into your developer portal. + + + + + ); +}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx new file mode 100644 index 0000000000..2173f8e1b7 --- /dev/null +++ b/plugins/ilert/src/components/UptimeMonitorsPage/UptimeMonitorsTable.tsx @@ -0,0 +1,176 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { Table, TableColumn } from '@backstage/core'; +import { TableState } from '../../api'; +import { UptimeMonitor } from '../../types'; +import { StatusChip } from './StatusChip'; +import Typography from '@material-ui/core/Typography'; +import { DateTime as dt, Interval } from 'luxon'; +import humanizeDuration from 'humanize-duration'; +import { EscalationPolicyLink } from '../EscalationPolicy/EscalationPolicyLink'; +import { UptimeMonitorCheckType } from './UptimeMonitorCheckType'; +import { UptimeMonitorActionsMenu } from '../UptimeMonitor/UptimeMonitorActionsMenu'; +import { UptimeMonitorLink } from '../UptimeMonitor/UptimeMonitorLink'; + +const useStyles = makeStyles(theme => ({ + empty: { + padding: theme.spacing(2), + display: 'flex', + justifyContent: 'center', + }, +})); + +export const UptimeMonitorsTable = ({ + uptimeMonitors, + tableState, + isLoading, + onChangePage, + onChangeRowsPerPage, + onUptimeMonitorChanged, +}: { + uptimeMonitors: UptimeMonitor[]; + tableState: TableState; + isLoading: boolean; + onChangePage: (page: number) => void; + onChangeRowsPerPage: (pageSize: number) => void; + onUptimeMonitorChanged: (uptimeMonitor: UptimeMonitor) => void; +}) => { + const classes = useStyles(); + + const smColumnStyle = { + width: '5%', + maxWidth: '5%', + }; + const mdColumnStyle = { + width: '10%', + maxWidth: '10%', + }; + const lgColumnStyle = { + width: '15%', + maxWidth: '15%', + }; + + const columns: TableColumn[] = [ + { + title: 'ID', + field: 'id', + highlight: true, + cellStyle: mdColumnStyle, + headerStyle: mdColumnStyle, + render: rowData => ( + + ), + }, + { + title: 'Name', + field: 'name', + render: rowData => ( + {(rowData as UptimeMonitor).name} + ), + }, + { + title: 'Check Type', + field: 'checkType', + cellStyle: lgColumnStyle, + headerStyle: lgColumnStyle, + render: rowData => ( + + ), + }, + { + title: 'Last state change', + field: 'lastStatusChange', + type: 'datetime', + cellStyle: mdColumnStyle, + headerStyle: mdColumnStyle, + render: rowData => ( + + {humanizeDuration( + Interval.fromDateTimes( + dt.fromISO((rowData as UptimeMonitor).lastStatusChange), + dt.now(), + ) + .toDuration() + .valueOf(), + { units: ['h', 'm', 's'], largest: 2, round: true }, + )} + + ), + }, + { + title: 'Escalation policy', + field: 'assignedTo', + cellStyle: lgColumnStyle, + headerStyle: lgColumnStyle, + render: rowData => ( + + ), + }, + { + title: 'Status', + field: 'status', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => ( + + ), + }, + { + title: '', + field: '', + cellStyle: smColumnStyle, + headerStyle: smColumnStyle, + render: rowData => ( + + ), + }, + ]; + + return ( +
+ No uptime monitor + + } + page={tableState.page} + onChangePage={onChangePage} + onChangeRowsPerPage={onChangeRowsPerPage} + localization={{ header: { actions: undefined } }} + isLoading={isLoading} + columns={columns} + data={uptimeMonitors} + /> + ); +}; diff --git a/plugins/ilert/src/components/UptimeMonitorsPage/index.ts b/plugins/ilert/src/components/UptimeMonitorsPage/index.ts new file mode 100644 index 0000000000..d1a8809a4e --- /dev/null +++ b/plugins/ilert/src/components/UptimeMonitorsPage/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export * from './UptimeMonitorsPage'; +export * from './UptimeMonitorsTable'; diff --git a/plugins/ilert/src/components/index.ts b/plugins/ilert/src/components/index.ts new file mode 100644 index 0000000000..81f7e02e23 --- /dev/null +++ b/plugins/ilert/src/components/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './ILertPage'; +export * from './ILertCard'; diff --git a/plugins/ilert/src/constants.ts b/plugins/ilert/src/constants.ts new file mode 100644 index 0000000000..bba644785f --- /dev/null +++ b/plugins/ilert/src/constants.ts @@ -0,0 +1,16 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const ILERT_INTEGRATION_KEY_ANNOTATION = 'ilert.com/integration-key'; diff --git a/plugins/ilert/src/hooks/index.ts b/plugins/ilert/src/hooks/index.ts new file mode 100644 index 0000000000..6e7e207112 --- /dev/null +++ b/plugins/ilert/src/hooks/index.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { useEntity } from '@backstage/plugin-catalog-react'; + +import { ILERT_INTEGRATION_KEY_ANNOTATION } from '../constants'; + +export function useILertEntity() { + const { entity } = useEntity(); + const integrationKey = + entity.metadata.annotations?.[ILERT_INTEGRATION_KEY_ANNOTATION] || ''; + const name = entity.metadata.name; + const identifier = `${entity.kind}:${ + entity.metadata.namespace || 'default' + }/${entity.metadata.name}`; + + return { integrationKey, name, identifier }; +} diff --git a/plugins/ilert/src/hooks/useAlertSource.ts b/plugins/ilert/src/hooks/useAlertSource.ts new file mode 100644 index 0000000000..15cc213b48 --- /dev/null +++ b/plugins/ilert/src/hooks/useAlertSource.ts @@ -0,0 +1,104 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../api'; +import { useApi, errorApiRef } from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { useAsyncRetry } from 'react-use'; +import { AlertSource, UptimeMonitor } from '../types'; + +export const useAlertSource = (integrationKey: string) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [alertSource, setAlertSource] = React.useState( + null, + ); + const [isAlertSourceLoading, setIsAlertSourceLoading] = React.useState(false); + const [ + uptimeMonitor, + setUptimeMonitor, + ] = React.useState(null); + const [isUptimeMonitorLoading, setIsUptimeMonitorLoading] = React.useState( + false, + ); + + const fetchAlertSourceCall = async () => { + try { + if (!integrationKey) { + return; + } + setIsAlertSourceLoading(true); + const data = await ilertApi.fetchAlertSource(integrationKey); + setAlertSource(data || null); + setIsAlertSourceLoading(false); + } catch (e) { + setIsAlertSourceLoading(false); + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }; + + const { + error: alertSourceError, + retry: alertSourceRetry, + } = useAsyncRetry(fetchAlertSourceCall, [integrationKey]); + + const fetchUptimeMonitorCall = async () => { + try { + if (!alertSource || alertSource.integrationType !== 'MONITOR') { + return; + } + setIsUptimeMonitorLoading(true); + const data = await ilertApi.fetchUptimeMonitor(alertSource.id); + setUptimeMonitor(data || null); + setIsUptimeMonitorLoading(false); + } catch (e) { + setIsUptimeMonitorLoading(false); + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }; + + const { + error: uptimeMonitorError, + retry: uptimeMonitorRetry, + } = useAsyncRetry(fetchUptimeMonitorCall, [alertSource]); + + const retry = () => { + alertSourceRetry(); + uptimeMonitorRetry(); + }; + + return [ + { + alertSource, + uptimeMonitor, + error: alertSourceError || uptimeMonitorError, + isLoading: isAlertSourceLoading || isUptimeMonitorLoading, + }, + { + retry, + setIsLoading: setIsAlertSourceLoading, + refetchAlertSource: fetchAlertSourceCall, + setAlertSource, + }, + ] as const; +}; diff --git a/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts b/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts new file mode 100644 index 0000000000..0fbb190a02 --- /dev/null +++ b/plugins/ilert/src/hooks/useAlertSourceOnCalls.ts @@ -0,0 +1,64 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../api'; +import { useApi, errorApiRef } from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { useAsyncRetry } from 'react-use'; +import { AlertSource, OnCall } from '../types'; + +export const useAlertSourceOnCalls = (alertSource?: AlertSource | null) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [onCallsList, setOnCallsList] = React.useState([]); + const [isLoading, setIsLoading] = React.useState(false); + + const fetchAlertSourceOnCallsCall = async () => { + try { + if (!alertSource) { + return; + } + setIsLoading(true); + const data = await ilertApi.fetchAlertSourceOnCalls(alertSource); + setOnCallsList(data || []); + setIsLoading(false); + } catch (e) { + setIsLoading(false); + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }; + + const { error, retry } = useAsyncRetry(fetchAlertSourceOnCallsCall, [ + alertSource, + ]); + + return [ + { + onCalls: onCallsList, + error, + isLoading, + }, + { + retry, + setIsLoading, + refetchAlertSourceOnCalls: fetchAlertSourceOnCallsCall, + }, + ] as const; +}; diff --git a/plugins/ilert/src/hooks/useAssignIncident.ts b/plugins/ilert/src/hooks/useAssignIncident.ts new file mode 100644 index 0000000000..8f0f663d2b --- /dev/null +++ b/plugins/ilert/src/hooks/useAssignIncident.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../api'; +import { useApi, errorApiRef } from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { useAsyncRetry } from 'react-use'; +import { Incident, IncidentResponder } from '../types'; + +export const useAssignIncident = (incident: Incident | null, open: boolean) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [incidentRespondersList, setIncidentRespondersList] = React.useState< + IncidentResponder[] + >([]); + const [ + incidentResponder, + setIncidentResponder, + ] = React.useState(null); + const [isLoading, setIsLoading] = React.useState(false); + + const { error, retry } = useAsyncRetry(async () => { + try { + if (!incident || !open) { + return; + } + const data = await ilertApi.fetchIncidentResponders(incident); + if (data && Array.isArray(data)) { + const groups = [ + 'SUGGESTED', + 'USER', + 'ESCALATION_POLICY', + 'ON_CALL_SCHEDULE', + ]; + data.sort((a, b) => groups.indexOf(a.group) - groups.indexOf(b.group)); + setIncidentRespondersList(data); + } + } catch (e) { + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }, [incident, open]); + + return [ + { + incidentRespondersList, + incidentResponder, + error, + isLoading, + }, + { + setIncidentRespondersList, + setIncidentResponder, + setIsLoading, + retry, + }, + ] as const; +}; diff --git a/plugins/ilert/src/hooks/useIncidentActions.ts b/plugins/ilert/src/hooks/useIncidentActions.ts new file mode 100644 index 0000000000..e23341ed9e --- /dev/null +++ b/plugins/ilert/src/hooks/useIncidentActions.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../api'; +import { useApi, errorApiRef } from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { useAsyncRetry } from 'react-use'; +import { Incident, IncidentAction } from '../types'; + +export const useIncidentActions = ( + incident: Incident | null, + open: boolean, +) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [incidentActionsList, setIncidentActionsList] = React.useState< + IncidentAction[] + >([]); + const [isLoading, setIsLoading] = React.useState(false); + + const { error, retry } = useAsyncRetry(async () => { + try { + if (!incident || !open) { + return; + } + const data = await ilertApi.fetchIncidentActions(incident); + setIncidentActionsList(data); + } catch (e) { + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }, [incident, open]); + + return [ + { + incidentActions: incidentActionsList, + error, + isLoading, + }, + { + setIncidentActionsList, + setIsLoading, + retry, + }, + ] as const; +}; diff --git a/plugins/ilert/src/hooks/useIncidents.ts b/plugins/ilert/src/hooks/useIncidents.ts new file mode 100644 index 0000000000..5f130fb20c --- /dev/null +++ b/plugins/ilert/src/hooks/useIncidents.ts @@ -0,0 +1,159 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { GetIncidentsOpts, ilertApiRef, TableState } from '../api'; +import { useApi, errorApiRef } from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { useAsyncRetry } from 'react-use'; +import { + ACCEPTED, + PENDING, + Incident, + IncidentStatus, + AlertSource, +} from '../types'; + +export const useIncidents = ( + paging: boolean, + singleSource?: boolean, + alertSource?: AlertSource | null, +) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [tableState, setTableState] = React.useState({ + page: 0, + pageSize: 10, + }); + const [states, setStates] = React.useState([ + ACCEPTED, + PENDING, + ]); + const [incidentsList, setIncidentsList] = React.useState([]); + const [incidentsCount, setIncidentsCount] = React.useState(0); + const [isLoading, setIsLoading] = React.useState(false); + + const fetchIncidentsCall = async () => { + try { + if (singleSource && !alertSource) { + return; + } + setIsLoading(true); + const opts: GetIncidentsOpts = { + states, + alertSources: alertSource ? [alertSource.id] : [], + }; + if (paging) { + opts.maxResults = tableState.pageSize; + opts.startIndex = tableState.page * tableState.pageSize; + } + const data = await ilertApi.fetchIncidents(opts); + setIncidentsList(data || []); + setIsLoading(false); + } catch (e) { + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + setIsLoading(false); + throw e; + } + }; + + const fetchIncidentsCountCall = async () => { + try { + const count = await ilertApi.fetchIncidentsCount({ states }); + setIncidentsCount(count || 0); + } catch (e) { + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }; + const fetchIncidents = useAsyncRetry(fetchIncidentsCall, [ + tableState, + states, + singleSource, + alertSource, + ]); + + const refetchIncidents = () => { + setTableState({ ...tableState, page: 0 }); + Promise.all([fetchIncidentsCall(), fetchIncidentsCountCall()]); + }; + + const fetchIncidentsCount = useAsyncRetry(fetchIncidentsCountCall, [states]); + + const error = fetchIncidents.error || fetchIncidentsCount.error; + const retry = () => { + fetchIncidents.retry(); + fetchIncidentsCount.retry(); + }; + + const onIncidentChanged = (newIncident: Incident) => { + let shouldRefetchIncidents = false; + setIncidentsList( + incidentsList.reduce((acc: Incident[], incident: Incident) => { + if (newIncident.id === incident.id) { + if (states.includes(newIncident.status)) { + acc.push(newIncident); + } else { + shouldRefetchIncidents = true; + } + return acc; + } + acc.push(incident); + return acc; + }, []), + ); + if (shouldRefetchIncidents) { + refetchIncidents(); + } + }; + + const onChangePage = (page: number) => { + setTableState({ ...tableState, page }); + }; + const onChangeRowsPerPage = (p: number) => { + setTableState({ ...tableState, pageSize: p }); + }; + const onIncidentStatesChange = (s: IncidentStatus[]) => { + setStates(s); + }; + + return [ + { + tableState, + states, + incidents: incidentsList, + incidentsCount, + error, + isLoading, + }, + { + setTableState, + setStates, + setIncidentsList, + setIsLoading, + retry, + onIncidentChanged, + refetchIncidents, + onChangePage, + onChangeRowsPerPage, + onIncidentStatesChange, + }, + ] as const; +}; diff --git a/plugins/ilert/src/hooks/useNewIncident.ts b/plugins/ilert/src/hooks/useNewIncident.ts new file mode 100644 index 0000000000..6fbcfbb5a5 --- /dev/null +++ b/plugins/ilert/src/hooks/useNewIncident.ts @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../api'; +import { useApi, errorApiRef } from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { useAsyncRetry } from 'react-use'; +import { AlertSource } from '../types'; + +export const useNewIncident = ( + open: boolean, + initialAlertSource?: AlertSource | null, +) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [alertSourcesList, setAlertSourcesList] = React.useState( + [], + ); + const [alertSource, setAlertSource] = React.useState( + null, + ); + const [summary, setSummary] = React.useState(''); + const [details, setDetails] = React.useState(''); + const [isLoading, setIsLoading] = React.useState(false); + + const fetchAlertSources = useAsyncRetry(async () => { + try { + if (!open || initialAlertSource) { + return; + } + const count = await ilertApi.fetchAlertSources(); + setAlertSourcesList(count || 0); + } catch (e) { + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }, [open]); + + const error = fetchAlertSources.error; + const retry = () => { + fetchAlertSources.retry(); + }; + + return [ + { + alertSources: alertSourcesList, + alertSource: initialAlertSource ? initialAlertSource : alertSource, + summary, + details, + error, + isLoading, + }, + { + setAlertSourcesList, + setAlertSource, + setSummary, + setDetails, + setIsLoading, + retry, + }, + ] as const; +}; diff --git a/plugins/ilert/src/hooks/useOnCallSchedules.ts b/plugins/ilert/src/hooks/useOnCallSchedules.ts new file mode 100644 index 0000000000..ce76a0770c --- /dev/null +++ b/plugins/ilert/src/hooks/useOnCallSchedules.ts @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../api'; +import { useApi, errorApiRef } from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { useAsyncRetry } from 'react-use'; +import { Schedule } from '../types'; + +export const useOnCallSchedules = () => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [onCallSchedulesList, setOnCallSchedulesList] = React.useState< + Schedule[] + >([]); + const [isLoading, setIsLoading] = React.useState(false); + + const fetchOnCallSchedulesCall = async () => { + try { + setIsLoading(true); + const data = await ilertApi.fetchOnCallSchedules(); + setOnCallSchedulesList(data || []); + setIsLoading(false); + } catch (e) { + setIsLoading(false); + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }; + + const { error, retry } = useAsyncRetry(fetchOnCallSchedulesCall, []); + + return [ + { + onCallSchedules: onCallSchedulesList, + error, + isLoading, + }, + { + retry, + setIsLoading, + refetchOnCallSchedules: fetchOnCallSchedulesCall, + }, + ] as const; +}; diff --git a/plugins/ilert/src/hooks/useShiftOverride.ts b/plugins/ilert/src/hooks/useShiftOverride.ts new file mode 100644 index 0000000000..136d20d286 --- /dev/null +++ b/plugins/ilert/src/hooks/useShiftOverride.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef } from '../api'; +import { useApi, errorApiRef } from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { useAsyncRetry } from 'react-use'; +import { User, Shift } from '../types'; + +export const useShiftOverride = (s: Shift, isModalOpened: boolean) => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [shift, setShift] = React.useState(s); + const [usersList, setUsersList] = React.useState([]); + const [isLoading, setIsLoading] = React.useState(false); + + const { error, retry } = useAsyncRetry(async () => { + try { + if (!isModalOpened) { + return; + } + setIsLoading(true); + const data = await ilertApi.fetchUsers(); + setUsersList(data || []); + setIsLoading(false); + } catch (e) { + setIsLoading(false); + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }, [isModalOpened]); + + const setUser = (user: User) => { + setShift({ ...shift, user }); + }; + + const setStart = (start: string) => { + setShift({ ...shift, start }); + }; + + const setEnd = (end: string) => { + setShift({ ...shift, end }); + }; + + return [ + { + shift, + users: usersList, + user: shift.user, + start: shift.start, + end: shift.end, + error, + isLoading, + }, + { + retry, + setIsLoading, + setUser, + setStart, + setEnd, + }, + ] as const; +}; diff --git a/plugins/ilert/src/hooks/useUptimeMonitors.ts b/plugins/ilert/src/hooks/useUptimeMonitors.ts new file mode 100644 index 0000000000..fa1576642a --- /dev/null +++ b/plugins/ilert/src/hooks/useUptimeMonitors.ts @@ -0,0 +1,89 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { ilertApiRef, TableState } from '../api'; +import { useApi, errorApiRef } from '@backstage/core'; +import { AuthenticationError } from '@backstage/errors'; +import { useAsyncRetry } from 'react-use'; +import { UptimeMonitor } from '../types'; + +export const useUptimeMonitors = () => { + const ilertApi = useApi(ilertApiRef); + const errorApi = useApi(errorApiRef); + + const [tableState, setTableState] = React.useState({ + page: 0, + pageSize: 10, + }); + const [uptimeMonitorsList, setUptimeMonitorsList] = React.useState< + UptimeMonitor[] + >([]); + const [isLoading, setIsLoading] = React.useState(false); + + const { error, retry } = useAsyncRetry(async () => { + try { + setIsLoading(true); + const data = await ilertApi.fetchUptimeMonitors(); + setUptimeMonitorsList(data || []); + setIsLoading(false); + } catch (e) { + setIsLoading(false); + if (!(e instanceof AuthenticationError)) { + errorApi.post(e); + } + throw e; + } + }, [tableState]); + + const onUptimeMonitorChanged = (newUptimeMonitor: UptimeMonitor) => { + setUptimeMonitorsList( + uptimeMonitorsList.map( + (uptimeMonitor: UptimeMonitor): UptimeMonitor => { + if (newUptimeMonitor.id === uptimeMonitor.id) { + return newUptimeMonitor; + } + + return uptimeMonitor; + }, + ), + ); + }; + + const onChangePage = (page: number) => { + setTableState({ ...tableState, page }); + }; + const onChangeRowsPerPage = (pageSize: number) => { + setTableState({ ...tableState, pageSize }); + }; + + return [ + { + tableState, + uptimeMonitors: uptimeMonitorsList, + error, + isLoading, + }, + { + setTableState, + setUptimeMonitorsList, + retry, + onUptimeMonitorChanged, + onChangePage, + onChangeRowsPerPage, + setIsLoading, + }, + ] as const; +}; diff --git a/plugins/ilert/src/index.ts b/plugins/ilert/src/index.ts new file mode 100644 index 0000000000..2e5301f151 --- /dev/null +++ b/plugins/ilert/src/index.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { IconComponent } from '@backstage/core'; +import ILertIconComponent from './assets/ilert.icon.svg'; + +export { + ilertPlugin, + ilertPlugin as plugin, + ILertPage, + EntityILertCard, +} from './plugin'; +export { + ILertPage as Router, + isPluginApplicableToEntity, + isPluginApplicableToEntity as isILertAvailable, + ILertCard, +} from './components'; +export * from './api'; +export * from './route-refs'; +export const ILertIcon: IconComponent = ILertIconComponent; diff --git a/plugins/ilert/src/plugin.test.ts b/plugins/ilert/src/plugin.test.ts new file mode 100644 index 0000000000..89a0cb231f --- /dev/null +++ b/plugins/ilert/src/plugin.test.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { ilertPlugin } from './plugin'; + +describe('plugin-ilert', () => { + it('should export plugin', () => { + expect(ilertPlugin).toBeDefined(); + }); +}); diff --git a/plugins/ilert/src/plugin.ts b/plugins/ilert/src/plugin.ts new file mode 100644 index 0000000000..546b77911d --- /dev/null +++ b/plugins/ilert/src/plugin.ts @@ -0,0 +1,60 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { + configApiRef, + createApiFactory, + createPlugin, + discoveryApiRef, + identityApiRef, + createRoutableExtension, + createComponentExtension, +} from '@backstage/core'; +import { ILertClient, ilertApiRef } from './api'; +import { iLertRouteRef } from './route-refs'; + +export const ilertPlugin = createPlugin({ + id: 'ilert', + apis: [ + createApiFactory({ + api: ilertApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + configApi: configApiRef, + }, + factory: ({ discoveryApi, configApi }) => + ILertClient.fromConfig(configApi, discoveryApi), + }), + ], + routes: { + root: iLertRouteRef, + }, +}); + +export const ILertPage = ilertPlugin.provide( + createRoutableExtension({ + component: () => import('./components').then(m => m.ILertPage), + mountPoint: iLertRouteRef, + }), +); + +export const EntityILertCard = ilertPlugin.provide( + createComponentExtension({ + component: { + lazy: () => import('./components/ILertCard').then(m => m.ILertCard), + }, + }), +); diff --git a/plugins/ilert/src/route-refs.tsx b/plugins/ilert/src/route-refs.tsx new file mode 100644 index 0000000000..b63ba15315 --- /dev/null +++ b/plugins/ilert/src/route-refs.tsx @@ -0,0 +1,24 @@ +/* + * Copyright 2021 Spotify AB + * + * 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'; +import ILertIcon from './assets/ilert.icon.svg'; + +export const iLertRouteRef = createRouteRef({ + icon: ILertIcon, + path: '/ilert', + title: 'iLert', +}); diff --git a/plugins/ilert/src/setupTests.ts b/plugins/ilert/src/setupTests.ts new file mode 100644 index 0000000000..0cec5b395d --- /dev/null +++ b/plugins/ilert/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2021 Spotify AB + * + * 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/ilert/src/types.ts b/plugins/ilert/src/types.ts new file mode 100644 index 0000000000..d7746c9d9b --- /dev/null +++ b/plugins/ilert/src/types.ts @@ -0,0 +1,337 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Incident { + id: number; + summary: string; + details: string; + reportTime: string; + resolvedOn: string; + status: IncidentStatus; + priority: IncidentPriority; + incidentKey: string; + alertSource: AlertSource | null; + assignedTo: User | null; + logEntries: LogEntry[]; + links: Link[]; + images: Image[]; + subscribers: Subscriber[]; + commentText: string; + commentPublishToSubscribers: boolean; +} + +export const PENDING = 'PENDING'; +export const ACCEPTED = 'ACCEPTED'; +export const RESOLVED = 'RESOLVED'; +export type IncidentStatus = typeof PENDING | typeof ACCEPTED | typeof RESOLVED; +export type IncidentPriority = 'HIGH' | 'LOW'; + +export interface Link { + href: string; + text: string; +} + +export interface Image { + src: string; + href: string; + alt: string; +} + +export type SubscriberType = 'TEAM' | 'USER'; + +export interface Subscriber { + id: number; + name: string; + type: SubscriberType; +} + +export interface LogEntry { + id: number; + timestamp: string; + logEntryType: string; + text: string; + incidentId?: number; + iconName?: string; + iconClass?: string; + filterTypes?: string[]; +} + +export interface User { + id: number; + username: string; + firstName: string; + lastName: string; + email: string; + mobile: Phone; + landline: Phone; + timezone?: string; + language?: Language; + role?: UserRole; + notificationPreferences?: any[]; + position: string; + department: string; +} + +export type UserRole = + | 'USER' + | 'ADMIN' + | 'STAKEHOLDER' + | 'ACCOUNT_OWNER' + | 'RESPONDER'; +export type Language = 'de' | 'en'; +export interface Phone { + regionCode: string; + number: string; +} + +export interface AlertSource { + id: number; + name: string; + status: AlertSourceStatus; + escalationPolicy: EscalationPolicy; + integrationType: AlertSourceIntegrationType; + integrationKey?: string; + iconUrl?: string; + lightIconUrl?: string; + darkIconUrl?: string; + incidentCreation?: AlertSourceIncidentCreation; + incidentPriorityRule?: AlertSourceIncidentPriorityRule; + emailFiltered?: boolean; + emailResolveFiltered?: boolean; + active?: boolean; + emailPredicates?: AlertSourceEmailPredicate[]; + emailResolvePredicates?: AlertSourceEmailPredicate[]; + filterOperator?: AlertSourceFilterOperator; + resolveFilterOperator?: AlertSourceFilterOperator; + supportHours?: AlertSourceSupportHours; + heartbeat?: AlertSourceHeartbeat; + autotaskMetadata?: AlertSourceAutotaskMetadata; + autoResolutionTimeout?: string; + teams: TeamShort[]; +} + +export interface TeamShort { + id: number; + name: string; +} + +export interface TeamMember { + user: User; + role: 'STAKEHOLDER' | 'RESPONDER' | 'USER' | 'ADMIN'; +} + +export type AlertSourceStatus = + | 'PENDING' + | 'ALL_ACCEPTED' + | 'ALL_RESOLVED' + | 'IN_MAINTENANCE' + | 'DISABLED'; +export type AlertSourceIntegrationType = + | 'NAGIOS' + | 'ICINGA' + | 'EMAIL' + | 'SMS' + | 'API' + | 'CRN' + | 'HEARTBEAT' + | 'PRTG' + | 'PINGDOM' + | 'CLOUDWATCH' + | 'AWSPHD' + | 'STACKDRIVER' + | 'INSTANA' + | 'ZABBIX' + | 'SOLARWINDS' + | 'PROMETHEUS' + | 'NEWRELIC' + | 'GRAFANA' + | 'GITHUB' + | 'DATADOG' + | 'UPTIMEROBOT' + | 'APPDYNAMICS' + | 'DYNATRACE' + | 'TOPDESK' + | 'STATUSCAKE' + | 'MONITOR' + | 'TOOL' + | 'CHECKMK' + | 'AUTOTASK' + | 'AWSBUDGET' + | 'KENTIXAM' + | 'CONSUL' + | 'ZAMMAD' + | 'SIGNALFX' + | 'SPLUNK' + | 'KUBERNETES' + | 'SEMATEXT' + | 'SENTRY' + | 'SUMOLOGIC' + | 'RAYGUN' + | 'MXTOOLBOX' + | 'ESWATCHER' + | 'AMAZONSNS' + | 'KAPACITOR' + | 'CORTEXXSOAR' + | string; +export type AlertSourceIncidentCreation = + | 'ONE_INCIDENT_PER_EMAIL' + | 'ONE_INCIDENT_PER_EMAIL_SUBJECT' + | 'ONE_PENDING_INCIDENT_ALLOWED' + | 'ONE_OPEN_INCIDENT_ALLOWED' + | 'OPEN_RESOLVE_ON_EXTRACTION'; +export type AlertSourceFilterOperator = 'AND' | 'OR'; +export type AlertSourceIncidentPriorityRule = + | 'HIGH' + | 'LOW' + | 'HIGH_DURING_SUPPORT_HOURS' + | 'LOW_DURING_SUPPORT_HOURS'; +export interface AlertSourceEmailPredicate { + field: 'EMAIL_FROM' | 'EMAIL_SUBJECT' | 'EMAIL_BODY'; + criteria: + | 'CONTAINS_ANY_WORDS' + | 'CONTAINS_NOT_WORDS' + | 'CONTAINS_STRING' + | 'CONTAINS_NOT_STRING' + | 'IS_STRING' + | 'IS_NOT_STRING' + | 'MATCHES_REGEX' + | 'MATCHES_NOT_REGEX'; + value: string; +} +export type AlertSourceTimeZone = + | 'Europe/Berlin' + | 'America/New_York' + | 'America/Los_Angeles' + | 'Asia/Istanbul'; +export interface AlertSourceSupportDay { + start: string; + end: string; +} +export interface AlertSourceSupportHours { + timezone: AlertSourceTimeZone; + autoRaiseIncidents: boolean; + supportDays: { + MONDAY: AlertSourceSupportDay; + TUESDAY: AlertSourceSupportDay; + WEDNESDAY: AlertSourceSupportDay; + THURSDAY: AlertSourceSupportDay; + FRIDAY: AlertSourceSupportDay; + SATURDAY: AlertSourceSupportDay; + SUNDAY: AlertSourceSupportDay; + }; +} +export interface AlertSourceHeartbeat { + summary: string; + intervalSec: number; + status: 'OVERDUE' | 'ON_TIME' | 'NEVER_RECEIVED'; +} + +export interface AlertSourceAutotaskMetadata { + userName: string; + secret: string; + apiIntegrationCode: string; + webServer: string; +} + +export interface EscalationPolicy { + id: number; + name: string; + escalationRules: EscalationRule[]; + newEscalationRule: EscalationRule; + repeating?: boolean; + frequency?: number; + teams: TeamShort[]; +} + +export interface EscalationRule { + user: User | null; + schedule: Schedule | null; + escalationTimeout: number; +} + +export interface Schedule { + id: number; + name: string; + timezone: string; + startsOn: string; + currentShift: Shift; + nextShift: Shift; + shifts: Shift[]; + overrides: Shift[]; + teams: TeamShort[]; +} + +export interface Shift { + user: User; + start: string; + end: string; +} + +export interface UptimeMonitor { + id: number; + name: string; + region: 'EU' | 'US'; + checkType: 'http' | 'tcp' | 'udp' | 'ping'; + checkParams: UptimeMonitorCheckParams; + intervalSec: number; + timeoutMs: number; + createIncidentAfterFailedChecks: number; + paused: boolean; + embedUrl: string; + shareUrl: string; + status: string; + lastStatusChange: string; + escalationPolicy: EscalationPolicy; + teams: TeamShort[]; +} + +export interface UptimeMonitorCheckParams { + host?: string; + port?: number; + url?: string; +} + +export interface IncidentResponder { + group: 'SUGGESTED' | 'USER' | 'ESCALATION_POLICY' | 'ON_CALL_SCHEDULE'; + id: number; + name: string; + disabled: boolean; +} + +export interface IncidentAction { + name: string; + type: string; + webhookId: string; + extensionId?: string; + history?: IncidentActionHistory[]; +} + +export interface IncidentActionHistory { + id: string; + webhookId: string; + incidentId: number; + actor: User; + success: boolean; +} + +export interface OnCall { + user: User; + escalationPolicy: EscalationPolicy; + schedule?: Schedule; + start: string; + end: string; + escalationLevel: number; +} diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index ac734e9a95..79e204b350 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-jenkins +## 0.4.3 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.4.2 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index c4ba600d7e..d14a18ffe7 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.4.2", + "version": "0.4.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.3", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,12 +47,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/testing-library__jest-dom": "^5.9.1", diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index ef006a360f..b71ac92e5a 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-kafka-backend +## 0.2.5 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.2.4 ### Patch Changes diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 3e3f22654a..14bbfef486 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kafka-backend", - "version": "0.2.4", + "version": "0.2.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", - "@backstage/catalog-model": "^0.7.8", + "@backstage/backend-common": "^0.8.0", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -42,7 +42,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/jest-when": "^2.7.2", "@types/lodash": "^4.14.151", "jest-when": "^3.1.0", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index f1c4c215ed..67c70c321d 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-kafka +## 0.2.7 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.2.6 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index e405460262..b6c600a90c 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kafka", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.4", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.1", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -33,13 +33,13 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index a9893925e2..265b7eb414 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-kubernetes-backend +## 0.3.7 + +### Patch Changes + +- f9f9d633d: Add possibility to configure TLS verification for `gke` type clusters +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.3.6 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index f73166d5fc..9e438ce788 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.6", + "version": "0.3.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,8 +31,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", - "@backstage/catalog-model": "^0.7.8", + "@backstage/backend-common": "^0.8.0", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/plugin-kubernetes-common": "^0.1.0", "@google-cloud/container": "^2.2.0", @@ -53,7 +53,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/aws4": "^1.5.1", "supertest": "^6.1.3" }, diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index d78842f351..cca63f6389 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-kubernetes +## 0.4.4 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- ea21d46f0: Export types +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.4.3 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 19865a6fcc..5036e08852 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.4.3", + "version": "0.4.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.5", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/plugin-kubernetes-common": "^0.1.0", "@backstage/theme": "^0.2.7", "@kubernetes/client-node": "^0.14.0", @@ -49,13 +49,13 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/kubernetes/src/index.ts b/plugins/kubernetes/src/index.ts index eca6b068f1..aabf36f1dc 100644 --- a/plugins/kubernetes/src/index.ts +++ b/plugins/kubernetes/src/index.ts @@ -19,3 +19,4 @@ export { EntityKubernetesContent, } from './plugin'; export { Router } from './Router'; +export * from './kubernetes-auth-provider'; diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/index.ts b/plugins/kubernetes/src/kubernetes-auth-provider/index.ts new file mode 100644 index 0000000000..8f54913903 --- /dev/null +++ b/plugins/kubernetes/src/kubernetes-auth-provider/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 Spotify AB + * + * 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 { kubernetesAuthProvidersApiRef } from './types'; +export { KubernetesAuthProviders } from './KubernetesAuthProviders'; diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index cb2cb5b9d1..d9b9b541d0 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-lighthouse +## 0.2.16 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.2.15 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index f70e29f69b..68f4722157 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.15", + "version": "0.2.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.4", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.2", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,12 +46,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/react": "^16.9", diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index f2745796d8..2091ef6a1d 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-newrelic +## 0.2.7 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + ## 0.2.6 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 20ee5521f4..7938f19100 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,12 +41,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index c62c618ff4..33ef38a7c2 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-org +## 0.3.13 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- f59a945b7: Paginate group members to only display 50 members maximum. +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/core-api@0.2.18 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.3.12 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 012514f1c4..4f8e37fa99 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.3.12", + "version": "0.3.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.6", - "@backstage/core": "^0.7.8", - "@backstage/core-api": "^0.2.16", - "@backstage/plugin-catalog-react": "^0.1.4", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/core-api": "^0.2.18", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,12 +35,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index b7fc7fec40..21e79445b0 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-pagerduty +## 0.3.4 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.3.3 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 53f0ddbb23..b3fbc37569 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-pagerduty", - "version": "0.3.3", + "version": "0.3.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.1", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,12 +46,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 06a96576e6..2890039b96 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-proxy-backend +## 0.2.8 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [f9fb4a205] + - @backstage/backend-common@0.8.0 + ## 0.2.7 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 2c6fc9e08d..d2b0d64678 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.2.7", + "version": "0.2.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,7 +28,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/config": "^0.1.5", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -42,7 +42,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/register-component/CHANGELOG.md b/plugins/register-component/CHANGELOG.md index 3634972e9a..ae0ae6562b 100644 --- a/plugins/register-component/CHANGELOG.md +++ b/plugins/register-component/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-register-component +## 0.2.15 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.2.14 ### Patch Changes diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 3efb61b982..d10bdcfc38 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.2.14", + "version": "0.2.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.5", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.4", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -45,12 +45,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index 9aa0d77e69..0318d3a111 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-rollbar-backend +## 0.1.11 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [f9fb4a205] + - @backstage/backend-common@0.8.0 + ## 0.1.10 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index c7eb819dc1..03be93dbfa 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar-backend", - "version": "0.1.10", + "version": "0.1.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/config": "^0.1.5", "@types/express": "^4.17.6", "axios": "^0.21.1", @@ -47,7 +47,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index 1d8c2c399e..26b9913df2 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-rollbar +## 0.3.5 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.3.4 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 8b559e0bf1..37ada55f5f 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.3.4", + "version": "0.3.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.1", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,13 +47,13 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/react": "^16.9", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index b4aa856606..cdb8fd734b 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend +## 0.11.1 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 82ca1ac22: The apiBaseUrl setting for Bitbucket Server integrations will now be used when it is set. Otherwise, it will default back to the host setting. +- fd39d4662: Move `jest-when` to the dev dependencies +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.11.0 ### Minor Changes @@ -16,7 +30,6 @@ + SingleHostDiscovery, + } from '@backstage/backend-common'; - export default async function createPlugin({ logger, config, diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 7452d94c63..2565a9d05a 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.11.0", + "version": "0.11.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/catalog-client": "^0.3.11", - "@backstage/catalog-model": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.2", @@ -64,8 +64,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/test-utils": "^0.1.11", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/supertest": "^2.0.8", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index b67740ecc1..7e55dabc55 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-scaffolder +## 0.9.4 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 81ef1d57b: Show error on task page if task does not exist. +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.9.3 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 1992028157..a29251f705 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.9.3", + "version": "0.9.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,13 +31,13 @@ }, "dependencies": { "@backstage/catalog-client": "^0.3.11", - "@backstage/catalog-model": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/integration": "^0.5.2", - "@backstage/integration-react": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.1.5", + "@backstage/integration-react": "^0.1.2", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -60,12 +60,12 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/humanize-duration": "^3.18.1", "@testing-library/react-hooks": "^3.3.0", "@types/jest": "^26.0.7", diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 99077b9b62..23234aca2f 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -25,8 +25,8 @@ "@types/lunr": "^2.3.3" }, "devDependencies": { - "@backstage/backend-common": "^0.7.0", - "@backstage/cli": "^0.6.10" + "@backstage/backend-common": "^0.8.0", + "@backstage/cli": "^0.6.11" }, "files": [ "dist" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index fd429aae62..9d34d92575 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-backend +## 0.1.5 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [f9fb4a205] + - @backstage/backend-common@0.8.0 + ## 0.1.4 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 4de8ada9bc..c06f54a266 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend", - "version": "0.1.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -19,7 +19,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/search-common": "^0.1.1", "@backstage/plugin-search-backend-node": "^0.1.3", "@types/express": "^4.17.6", @@ -29,7 +29,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index b4bcd1f855..9c0ab2fa8c 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search +## 0.3.6 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.3.5 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index f503b9a78e..7593a2901d 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.3.5", + "version": "0.3.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.8", - "@backstage/catalog-model": "^0.7.3", - "@backstage/plugin-catalog-react": "^0.1.2", + "@backstage/core": "^0.7.9", + "@backstage/catalog-model": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/search-common": "^0.1.1", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", @@ -44,12 +44,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 261972384a..b22b5539ef 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-sentry +## 0.3.10 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.3.9 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 6784debcf9..7da547dd94 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.3.9", + "version": "0.3.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,9 +31,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.1", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,12 +46,12 @@ "timeago.js": "^4.0.2" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/react": "^16.9", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md new file mode 100644 index 0000000000..bdc4b3adba --- /dev/null +++ b/plugins/shortcuts/CHANGELOG.md @@ -0,0 +1,12 @@ +# @backstage/plugin-shortcuts + +## 0.1.2 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index f87fe0e683..aea2f8461e 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-shortcuts", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,12 +35,12 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index 48b9292791..e457d2bdf9 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-sonarqube +## 0.1.18 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.1.17 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index df5304d746..9e3d6bf03b 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube", - "version": "0.1.17", + "version": "0.1.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,9 +32,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.7", - "@backstage/plugin-catalog-react": "^0.1.3", - "@backstage/core": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,12 +47,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index b3151c3e04..4f34ea3bcc 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-splunk-on-call +## 0.2.1 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.2.0 ### Minor Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 020fbc37cd..ebb6fdb42e 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.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.4", - "@backstage/core": "^0.7.8", - "@backstage/plugin-catalog-react": "^0.1.1", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -45,12 +45,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/luxon": "^1.25.0", "@types/node": "^14.14.32", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 9f0a5b87c3..64f255adb1 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-tech-radar +## 0.3.11 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + ## 0.3.10 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 5e33024f87..e65e3d27a7 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-radar", - "version": "0.3.10", + "version": "0.3.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,12 +43,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/color": "^3.0.1", "@types/d3-force": "^2.1.1", "@types/jest": "^26.0.7", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 59a116f653..4b348d49a4 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-techdocs-backend +## 0.8.1 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] +- Updated dependencies [e04f1ccfb] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + - @backstage/techdocs-common@0.6.1 + ## 0.8.0 ### Minor Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 1424381f30..a702f63622 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.8.0", + "version": "0.8.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", - "@backstage/catalog-model": "^0.7.8", + "@backstage/backend-common": "^0.8.0", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/techdocs-common": "^0.6.0", + "@backstage/techdocs-common": "^0.6.1", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", "dockerode": "^3.2.1", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/dockerode": "^3.2.1", "supertest": "^6.1.3" }, diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 69e988a433..8382ef9419 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/plugin-techdocs +## 0.9.2 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/integration-react@0.1.2 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 + ## 0.9.1 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 20fca364da..ce748f836d 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.9.1", + "version": "0.9.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,11 +32,11 @@ }, "dependencies": { "@backstage/config": "^0.1.5", - "@backstage/catalog-model": "^0.7.8", - "@backstage/core": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", "@backstage/integration": "^0.5.2", - "@backstage/integration-react": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.1.5", + "@backstage/integration-react": "^0.1.2", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@backstage/errors": "^0.1.1", "@material-ui/core": "^4.11.0", @@ -51,12 +51,12 @@ "sanitize-html": "^2.3.2" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/react": "^16.9", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx index a67443e40f..7bcf182d56 100644 --- a/plugins/techdocs/src/reader/components/Reader.tsx +++ b/plugins/techdocs/src/reader/components/Reader.tsx @@ -89,12 +89,17 @@ export const Reader = ({ entityId, onReady }: Props) => { useEffect(() => { const updateSidebarPosition = () => { if (!!shadowDomRef.current && !!sidebars) { + const mdTabs = shadowDomRef.current!.querySelector( + '.md-container > .md-tabs', + ); sidebars!.forEach(sidebar => { const newTop = Math.max( shadowDomRef.current!.getBoundingClientRect().top, 0, ); - sidebar.style.top = `${newTop}px`; + sidebar.style.top = mdTabs + ? `${newTop + mdTabs.getBoundingClientRect().height}px` + : `${newTop}px`; }); } }; @@ -318,8 +323,11 @@ export const Reader = ({ entityId, onReady }: Props) => { // set sidebar height so they don't initially render in wrong position const docTopPosition = (dom as HTMLElement).getBoundingClientRect() .top; + const mdTabs = dom.querySelector('.md-container > .md-tabs'); sideDivs!.forEach(sidebar => { - sidebar.style.top = `${docTopPosition}px`; + sidebar.style.top = mdTabs + ? `${docTopPosition + mdTabs.getBoundingClientRect().height}px` + : `${docTopPosition}px`; }); }, }), diff --git a/plugins/techdocs/src/reader/transformers/rewriteDocLinks.test.ts b/plugins/techdocs/src/reader/transformers/rewriteDocLinks.test.ts index 06da8a447c..ca85c64fbe 100644 --- a/plugins/techdocs/src/reader/transformers/rewriteDocLinks.test.ts +++ b/plugins/techdocs/src/reader/transformers/rewriteDocLinks.test.ts @@ -16,6 +16,7 @@ import { createTestShadowDom, getSample } from '../../test-utils'; import { rewriteDocLinks } from '../transformers'; +import { normalizeUrl } from './rewriteDocLinks'; describe('rewriteDocLinks', () => { it('should not do anything', () => { @@ -56,3 +57,17 @@ describe('rewriteDocLinks', () => { ]); }); }); + +describe('normalizeUrl', () => { + it.each([ + ['http://example.org', 'http://example.org/'], + ['http://example.org/', 'http://example.org/'], + ['http://example.org/folder', 'http://example.org/folder/'], + ['http://example.org/folder/', 'http://example.org/folder/'], + ['http://example.org/folder#intro', 'http://example.org/folder/#intro'], + ['http://example.org/folder/#intro', 'http://example.org/folder/#intro'], + ['http://example.org/folder#', 'http://example.org/folder/#'], + ])('should handle %s', (url, expected) => { + expect(normalizeUrl(url)).toEqual(expected); + }); +}); diff --git a/plugins/techdocs/src/reader/transformers/rewriteDocLinks.ts b/plugins/techdocs/src/reader/transformers/rewriteDocLinks.ts index 7b0c23c3e3..54e5537347 100644 --- a/plugins/techdocs/src/reader/transformers/rewriteDocLinks.ts +++ b/plugins/techdocs/src/reader/transformers/rewriteDocLinks.ts @@ -31,9 +31,7 @@ export const rewriteDocLinks = (): Transformer => { if (elemAttribute.match(/^https?:\/\//i)) { elem.setAttribute('target', '_blank'); } - const normalizedWindowLocation = window.location.href.endsWith('/') - ? window.location.href - : `${window.location.href}/`; + const normalizedWindowLocation = normalizeUrl(window.location.href); elem.setAttribute( attributeName, @@ -48,3 +46,14 @@ export const rewriteDocLinks = (): Transformer => { return dom; }; }; + +/** Make sure that the input url always ends with a '/' */ +export function normalizeUrl(input: string): string { + const url = new URL(input); + + if (!url.pathname.endsWith('/')) { + url.pathname += '/'; + } + + return url.toString(); +} diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index b42f631a98..ebe3c2003c 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-todo-backend +## 0.1.5 + +### Patch Changes + +- Updated dependencies [22fd8ce2a] +- Updated dependencies [10c008a3a] +- Updated dependencies [f9fb4a205] +- Updated dependencies [16be1d093] + - @backstage/backend-common@0.8.0 + - @backstage/catalog-model@0.7.9 + ## 0.1.4 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 1af9f9b40c..5225adb648 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.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,9 +24,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.7.0", + "@backstage/backend-common": "^0.8.0", "@backstage/catalog-client": "^0.3.11", - "@backstage/catalog-model": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.2", @@ -39,7 +39,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.6.10", + "@backstage/cli": "^0.6.11", "@types/supertest": "^2.0.8", "msw": "^0.21.2", "supertest": "^6.1.3" diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md new file mode 100644 index 0000000000..f3732c923e --- /dev/null +++ b/plugins/todo/CHANGELOG.md @@ -0,0 +1,17 @@ +# @backstage/plugin-todo + +## 0.1.1 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [10c008a3a] +- Updated dependencies [889d89b6e] +- Updated dependencies [16be1d093] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + - @backstage/plugin-catalog-react@0.1.6 + - @backstage/catalog-model@0.7.9 diff --git a/plugins/todo/package.json b/plugins/todo/package.json index b7564f7080..c9a9f4bb51 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-todo", - "version": "0.1.0", + "version": "0.1.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,10 +26,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.7.3", - "@backstage/core": "^0.7.8", + "@backstage/catalog-model": "^0.7.9", + "@backstage/core": "^0.7.9", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.1.1", + "@backstage/plugin-catalog-react": "^0.1.6", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -39,12 +39,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "msw": "^0.21.2", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index f6f3046bef..5b1706e0c7 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-user-settings +## 0.2.10 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + ## 0.2.9 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 32e232cad4..f47f5b50af 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.2.9", + "version": "0.2.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,12 +41,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/plugins/welcome/CHANGELOG.md b/plugins/welcome/CHANGELOG.md index cf907b5b4f..53ffd59264 100644 --- a/plugins/welcome/CHANGELOG.md +++ b/plugins/welcome/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-welcome +## 0.2.8 + +### Patch Changes + +- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 +- 675a569a9: chore: bump `react-use` dependency in all packages +- Updated dependencies [062bbf90f] +- Updated dependencies [889d89b6e] +- Updated dependencies [3f988cb63] +- Updated dependencies [675a569a9] + - @backstage/core@0.7.9 + ## 0.2.7 ### Patch Changes diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 72ec24bf71..df16ec7f4d 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-welcome", - "version": "0.2.7", + "version": "0.2.8", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -30,7 +30,7 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/core": "^0.7.8", + "@backstage/core": "^0.7.9", "@backstage/theme": "^0.2.7", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -41,12 +41,12 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.6.10", - "@backstage/dev-utils": "^0.1.13", - "@backstage/test-utils": "^0.1.10", + "@backstage/cli": "^0.6.11", + "@backstage/dev-utils": "^0.1.14", + "@backstage/test-utils": "^0.1.11", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^12.0.7", + "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", diff --git a/yarn.lock b/yarn.lock index 437a9be678..9d9400fbbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1951,6 +1951,13 @@ dependencies: "@date-io/core" "^1.3.13" +"@date-io/luxon@1.x": + version "1.3.13" + resolved "https://registry.npmjs.org/@date-io/luxon/-/luxon-1.3.13.tgz#68f0134bb38ef486b2ed6df01981f814c633e28a" + integrity sha512-9wUrJCNSMZJeYAiH+dbb45oGpnHeFP7TOH/Lt26If47gjFCkjvyINzWx+K5AGsnlP0Qosxc7hkF1yLi6ecutxw== + dependencies: + "@date-io/core" "^1.3.13" + "@emotion/cache@^10.0.27": version "10.0.29" resolved "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" @@ -3729,14 +3736,26 @@ react-transition-group "^4.0.0" rifm "^0.7.0" +"@material-ui/pickers@^3.3.10": + version "3.3.10" + resolved "https://registry.npmjs.org/@material-ui/pickers/-/pickers-3.3.10.tgz#f1b0f963348cc191645ef0bdeff7a67c6aa25485" + integrity sha512-hS4pxwn1ZGXVkmgD4tpFpaumUaAg2ZzbTrxltfC5yPw4BJV+mGkfnQOB4VpWEYZw2jv65Z0wLwDE/piQiPPZ3w== + dependencies: + "@babel/runtime" "^7.6.0" + "@date-io/core" "1.x" + "@types/styled-jsx" "^2.2.8" + clsx "^1.0.2" + react-transition-group "^4.0.0" + rifm "^0.7.0" + "@material-ui/styles@^4.10.0", "@material-ui/styles@^4.11.0", "@material-ui/styles@^4.11.3", "@material-ui/styles@^4.9.6": - version "4.11.3" - resolved "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.3.tgz#1b8d97775a4a643b53478c895e3f2a464e8916f2" - integrity sha512-HzVzCG+PpgUGMUYEJ2rTEmQYeonGh41BYfILNFb/1ueqma+p1meSdu4RX6NjxYBMhf7k+jgfHFTTz+L1SXL/Zg== + version "4.11.4" + resolved "https://registry.npmjs.org/@material-ui/styles/-/styles-4.11.4.tgz#eb9dfccfcc2d208243d986457dff025497afa00d" + integrity sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew== dependencies: "@babel/runtime" "^7.4.4" "@emotion/hash" "^0.8.0" - "@material-ui/types" "^5.1.0" + "@material-ui/types" "5.1.0" "@material-ui/utils" "^4.11.2" clsx "^1.0.4" csstype "^2.5.2" @@ -3761,7 +3780,7 @@ csstype "^2.5.2" prop-types "^15.7.2" -"@material-ui/types@^5.1.0": +"@material-ui/types@5.1.0", "@material-ui/types@^5.1.0": version "5.1.0" resolved "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2" integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A== @@ -5606,10 +5625,10 @@ "@babel/runtime" "^7.12.5" "@testing-library/dom" "^7.28.1" -"@testing-library/user-event@^12.0.7": - version "12.8.3" - resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-12.8.3.tgz#1aa3ed4b9f79340a1e1836bc7f57c501e838704a" - integrity sha512-IR0iWbFkgd56Bu5ZI/ej8yQwrkCv8Qydx6RzwbKz9faXazR/+5tvYKsZQgyXJiwgpcva127YO6JcWy7YlCfofQ== +"@testing-library/user-event@^13.1.8": + version "13.1.8" + resolved "https://registry.npmjs.org/@testing-library/user-event/-/user-event-13.1.8.tgz#9cbf342b88d837ee188f9f9f4df6d1beaaf179c2" + integrity sha512-M04HgOlJvxILf5xyrkJaEQfFOtcvhy3usLldQIEg9zgFIYQofSmFGVfFlS7BWowqlBGLrItwGMlPXCoBgoHSiw== dependencies: "@babel/runtime" "^7.12.5" @@ -7440,7 +7459,7 @@ agentkeepalive@^4.1.3: depd "^1.1.2" humanize-ms "^1.2.1" -aggregate-error@3.0.1, aggregate-error@^3.0.0: +aggregate-error@3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== @@ -7448,6 +7467,14 @@ aggregate-error@3.0.1, aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +aggregate-error@^3.0.0, aggregate-error@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + airbnb-js-shims@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/airbnb-js-shims/-/airbnb-js-shims-2.2.1.tgz#db481102d682b98ed1daa4c5baa697a05ce5c040" @@ -8667,7 +8694,7 @@ base64-js@^1.0.2, base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== -base64url@3.x.x, base64url@^3.0.1: +base64url@3.x.x: version "3.0.1" resolved "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== @@ -10098,11 +10125,6 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" -compare-versions@^3.6.0: - version "3.6.0" - resolved "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62" - integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA== - component-emitter@^1.2.0, component-emitter@^1.2.1, component-emitter@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" @@ -10138,21 +10160,21 @@ compression@^1.7.4: safe-buffer "5.1.2" vary "~1.1.2" -compute-gcd@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.0.tgz#fc1ede5b65001e950226502f46543863e4fea10e" - integrity sha1-/B7eW2UAHpUCJlAvRlQ4Y+T+oQ4= +compute-gcd@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz#34d639f3825625e1357ce81f0e456a6249d8c77f" + integrity sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg== dependencies: validate.io-array "^1.0.3" validate.io-function "^1.0.2" validate.io-integer-array "^1.0.0" -compute-lcm@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.0.tgz#abd96d040b41b0a166f89944b5c8b7c511e21ad5" - integrity sha1-q9ltBAtBsKFm+JlEtci3xRHiGtU= +compute-lcm@^1.1.0, compute-lcm@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz#9107c66b9dca28cefb22b4ab4545caac4034af23" + integrity sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ== dependencies: - compute-gcd "^1.2.0" + compute-gcd "^1.2.1" validate.io-array "^1.0.3" validate.io-function "^1.0.2" validate.io-integer-array "^1.0.0" @@ -10611,13 +10633,20 @@ cross-env@^7.0.0: dependencies: cross-spawn "^7.0.1" -cross-fetch@3.0.6, cross-fetch@^3.0.4, cross-fetch@^3.0.5, cross-fetch@^3.0.6: +cross-fetch@3.0.6: version "3.0.6" resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.6.tgz#3a4040bc8941e653e0e9cf17f29ebcd177d3365c" integrity sha512-KBPUbqgFjzWlVcURG+Svp9TlhA5uliYtiNx/0r8nv0pdypeQCRJ9IaSIc3q/x3q8t3F75cHuwxVql1HFGHCNJQ== dependencies: node-fetch "2.6.1" +cross-fetch@^3.0.4, cross-fetch@^3.0.5, cross-fetch@^3.0.6: + version "3.1.4" + resolved "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" + integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ== + dependencies: + node-fetch "2.6.1" + cross-spawn@7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.1.tgz#0ab56286e0f7c24e153d04cc2aa027e43a9a5d14" @@ -11495,6 +11524,11 @@ delegates@^1.0.0: resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +denque@^1.4.1: + version "1.5.0" + resolved "https://registry.npmjs.org/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de" + integrity sha512-CYiCSgIF1p6EUByQPlGkKnP1M9g0ZV3qMIrqMqZqdwazygIA/YP2vrbcyl1h/WppKJTdl1F85cXIle+394iDAQ== + depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -13419,13 +13453,6 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -find-versions@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/find-versions/-/find-versions-4.0.0.tgz#3c57e573bf97769b8cb8df16934b627915da4965" - integrity sha512-wgpWy002tA+wgmO27buH/9KzyEOQnKsG/R0yrcjPT9BOFm0zRBVQbZ95nRGXWMywS8YR5knRbpohio0bcJABxQ== - dependencies: - semver-regex "^3.1.2" - find-yarn-workspace-root2@1.2.16: version "1.2.16" resolved "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz#60287009dd2f324f59646bdb4b7610a6b301c2a9" @@ -13490,7 +13517,7 @@ for-own@^0.1.3: foreach@^2.0.4: version "2.0.5" - resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= forever-agent@~0.6.1: @@ -13822,6 +13849,13 @@ gcs-resumable-upload@^3.1.4: pumpify "^2.0.0" stream-events "^1.0.4" +generate-function@^2.3.1: + version "2.3.1" + resolved "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + generic-names@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/generic-names/-/generic-names-2.0.1.tgz#f8a378ead2ccaa7a34f0317b05554832ae41b872" @@ -14483,9 +14517,11 @@ graphql-subscriptions@^1.0.0: iterall "^1.2.1" graphql-tag@^2.11.0: - version "2.11.0" - resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd" - integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA== + version "2.12.4" + resolved "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.4.tgz#d34066688a4f09e72d6f4663c74211e9b4b7c4bf" + integrity sha512-VV1U4O+9x99EkNpNmCUV5RZwq6MnK4+pGbRYWG+lA/m3uo7TSqJF81OkcOP148gFP6fzdl7JWYBrwWVTS9jXww== + dependencies: + tslib "^2.1.0" graphql-tools@5.0.0: version "5.0.0" @@ -14802,9 +14838,9 @@ hoopy@^0.1.4: integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hosted-git-info@^3.0.6: version "3.0.8" @@ -15098,6 +15134,11 @@ humanize-duration@^3.25.1: resolved "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.25.1.tgz#50e12bf4b3f515ec91106107ee981e8cfe955d6f" integrity sha512-P+dRo48gpLgc2R9tMRgiDRNULPKCmqFYgguwqOO2C0fjO35TgdURDQDANSR1Nt92iHlbHGMxOTnsB8H8xnMa2Q== +humanize-duration@^3.26.0: + version "3.26.0" + resolved "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.26.0.tgz#4d77f6b3d2fe0ca1ff14623ccc2b2f8b48ab1aaf" + integrity sha512-SddekX3p5ApvPY6bbAYppGKe874jP6iFZXYtrQToDV4R0j2UpTYPqwTFM2QpXpuw9DhS/eXTUnKYTF9TbXAJ6A== + humanize-ms@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" @@ -15105,21 +15146,10 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@^4.2.3: - version "4.3.8" - resolved "https://registry.npmjs.org/husky/-/husky-4.3.8.tgz#31144060be963fd6850e5cc8f019a1dfe194296d" - integrity sha512-LCqqsB0PzJQ/AlCgfrfzRe3e3+NvmefAdKQhRYpxS4u6clblBoDdzzvHi8fmxKRzvMxPY/1WZWzomPZww0Anow== - dependencies: - chalk "^4.0.0" - ci-info "^2.0.0" - compare-versions "^3.6.0" - cosmiconfig "^7.0.0" - find-versions "^4.0.0" - opencollective-postinstall "^2.0.2" - pkg-dir "^5.0.0" - please-upgrade-node "^3.2.0" - slash "^3.0.0" - which-pm-runs "^1.0.0" +husky@^6.0.0: + version "6.0.0" + resolved "https://registry.npmjs.org/husky/-/husky-6.0.0.tgz#810f11869adf51604c32ea577edbc377d7f9319e" + integrity sha512-SQS2gDTB7tBN486QSoKPKQItZw97BMOd+Kdb6ghfpBc0yXyzrddI0oDV5MkDAbuB4X2mO3/nj60TRMcYxwzZeQ== hyphenate-style-name@^1.0.2, hyphenate-style-name@^1.0.3: version "1.0.3" @@ -15920,6 +15950,11 @@ is-promise@^2.1.0, is-promise@^2.2.2: resolved "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== +is-property@^1.0.2: + version "1.0.2" + resolved "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + integrity sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ= + is-reference@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" @@ -16707,10 +16742,10 @@ jose@^1.27.1: dependencies: "@panva/asn1.js" "^1.0.0" -jose@^2.0.2: - version "2.0.3" - resolved "https://registry.npmjs.org/jose/-/jose-2.0.3.tgz#9c931ab3e13e2d16a5b9e6183e60b2fc40a8e1b8" - integrity sha512-L+RlDgjO0Tk+Ki6/5IXCSEnmJCV8iMFZoBuEgu2vPQJJ4zfG/k3CAqZUMKDYNRHIDyy0QidJpOvX0NgpsAqFlw== +jose@^2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz#29746a18d9fff7dcf9d5d2a6f62cb0c7cd27abd3" + integrity sha512-BAiDNeDKTMgk4tvD0BbxJ8xHEHBZgpeRZ1zGPPsitSyMgjoMWiLGYAE7H7NpP5h0lPppQajQs871E8NHUrzVPA== dependencies: "@panva/asn1.js" "^1.0.0" @@ -16870,7 +16905,7 @@ json-buffer@3.0.0: resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898" integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg= -json-buffer@3.0.1: +json-buffer@3.0.1, json-buffer@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== @@ -16886,9 +16921,9 @@ json-parse-even-better-errors@^2.3.0: integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json-pointer@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.0.tgz#8e500550a6aac5464a473377da57aa6cc22828d7" - integrity sha1-jlAFUKaqxUZKRzN32leqbMIoKNc= + version "0.6.1" + resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.1.tgz#3c6caa6ac139e2599f5a1659d39852154015054d" + integrity sha512-3OvjqKdCBvH41DLpV4iSt6v2XhZXV1bPB4OROuknvUXI7ZQNofieCPkmE26stEJ9zdQuvIxDHCuYhfgxFAAs+Q== dependencies: foreach "^2.0.4" @@ -16908,14 +16943,14 @@ json-schema-merge-allof@^0.6.0: json-schema-compare "^0.2.2" lodash "^4.17.4" -json-schema-merge-allof@^0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.7.0.tgz#84d3e8c3e03d3060014286958eb8834fa9d76304" - integrity sha512-kvsuSVnl1n5xnNEu5ed4o8r8ujSA4/IgRtHmpgfMfa7FOMIRAzN4F9qbuklouTn5J8bi83y6MQ11n+ERMMTXZg== +json-schema-merge-allof@^0.8.1: + version "0.8.1" + resolved "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz#ed2828cdd958616ff74f932830a26291789eaaf2" + integrity sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w== dependencies: - compute-lcm "^1.1.0" + compute-lcm "^1.1.2" json-schema-compare "^0.2.2" - lodash "^4.17.4" + lodash "^4.17.20" json-schema-traverse@^0.4.1: version "0.4.1" @@ -17222,6 +17257,14 @@ keytar@^5.4.0: nan "2.14.1" prebuild-install "5.3.3" +keyv-memcache@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/keyv-memcache/-/keyv-memcache-1.2.5.tgz#9097af5c617dc740e7300ebfd4a2efb8917429e3" + integrity sha512-iG+GHlhXyV83gmlCtMFIqNYVvv0i0towAy42NvziUR7D+k9jp81fAq0lu66H4gooOnW+ojkdigRvRpL40j1f7w== + dependencies: + json-buffer "^3.0.1" + memjs "^1.3.0" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -17236,6 +17279,13 @@ keyv@^4.0.0: dependencies: json-buffer "3.0.1" +keyv@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" + integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA== + dependencies: + json-buffer "3.0.1" + killable@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -17996,7 +18046,7 @@ lru-cache@2: resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952" integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI= -lru-cache@^4.0.1: +lru-cache@^4.0.1, lru-cache@^4.1.3: version "4.1.5" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -18309,6 +18359,11 @@ media-typer@0.3.0: resolved "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= +memjs@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/memjs/-/memjs-1.3.0.tgz#b7959b4ff3770e4c785463fd147f1e4fafd47a24" + integrity sha512-y/V9a0auepA9Lgyr4QieK6K2FczjHucEdTpSS+hHVNmVEkYxruXhkHu8n6DSRQ4HXHEE3cc6Sf9f88WCJXGXsQ== + memoize-one@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" @@ -18426,9 +18481,9 @@ merge2@^1.2.3, merge2@^1.3.0: integrity sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw== merge@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/merge/-/merge-2.1.0.tgz#91fff62458ba2eca378dd395fa85f1690bf87f60" - integrity sha512-TcuhVDV+e6X457MQAm7xIb19rWhZuEDEho7RrwxMpQ/3GhD5sDlnP188gjQQuweXHy9igdke5oUtVOXX1X8Sxg== + version "2.1.1" + resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" + integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== meros@^1.1.2: version "1.1.4" @@ -18668,7 +18723,6 @@ minipass-fetch@^1.3.0, minipass-fetch@^1.3.2: resolved "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.3.3.tgz#34c7cea038c817a8658461bf35174551dce17a0a" integrity sha512-akCrLDWfbdAWkMLBxJEeWTdNsjML+dt5YgOI4gJ53vuO0vrmYQkUPxa6j6V65s9CcePIr2SSWqjT2EcrNseryQ== dependencies: - encoding "^0.1.12" minipass "^3.1.0" minipass-sized "^1.0.3" minizlib "^2.0.0" @@ -18993,6 +19047,20 @@ mv@~2: ncp "~2.0.0" rimraf "~2.4.0" +mysql2@^2.2.5: + version "2.2.5" + resolved "https://registry.npmjs.org/mysql2/-/mysql2-2.2.5.tgz#72624ffb4816f80f96b9c97fedd8c00935f9f340" + integrity sha512-XRqPNxcZTpmFdXbJqb+/CtYVLCx14x1RTeNMD4954L331APu75IC74GDqnZMEt1kwaXy6TySo55rF2F3YJS78g== + dependencies: + denque "^1.4.1" + generate-function "^2.3.1" + iconv-lite "^0.6.2" + long "^4.0.0" + lru-cache "^6.0.0" + named-placeholders "^1.1.2" + seq-queue "^0.0.5" + sqlstring "^2.3.2" + mz@^2.7.0: version "2.7.0" resolved "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" @@ -19002,6 +19070,13 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" +named-placeholders@^1.1.2: + version "1.1.2" + resolved "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.2.tgz#ceb1fbff50b6b33492b5cf214ccf5e39cef3d0e8" + integrity sha512-wiFWqxoLL3PGVReSZpjLVxyJ1bRqe+KKJVbr4hGs1KWfTZTQyezHFBbuKj9hsizHyGV2ne7EMjHdxEGAybD5SA== + dependencies: + lru-cache "^4.1.3" + nan@2.14.1, nan@^2.12.1, nan@^2.14.0: version "2.14.1" resolved "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz#d7be34dfa3105b91494c3147089315eff8874b01" @@ -19746,10 +19821,10 @@ octokit-plugin-create-pull-request@^3.9.3: dependencies: "@octokit/types" "^6.8.2" -oidc-token-hash@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.0.tgz#acdfb1f4310f58e64d5d74a4e8671a426986e888" - integrity sha512-8Yr4CZSv+Tn8ZkN3iN2i2w2G92mUKClp4z7EGUfdsERiYSbj7P4i/NHm72ft+aUdsiFx9UdIPSTwbyzQ6C4URg== +oidc-token-hash@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.1.tgz#ae6beec3ec20f0fd885e5400d175191d6e2f10c6" + integrity sha512-EvoOtz6FIEBzE+9q253HsLCVRiK/0doEJ2HCvvqMQb3dHZrP3WlJKYtJ55CRTw4jmYomzH4wkPuCj/I3ZvpKxQ== on-finished@~2.3.0: version "2.3.0" @@ -19811,24 +19886,18 @@ openapi-sampler@^1.0.0-beta.15: dependencies: json-pointer "^0.6.0" -opencollective-postinstall@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz#5657f1bede69b6e33a45939b061eb53d3c6c3a89" - integrity sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw== - openid-client@^4.1.1, openid-client@^4.2.1: - version "4.2.1" - resolved "https://registry.npmjs.org/openid-client/-/openid-client-4.2.1.tgz#8200c0ab6a3b8e954727dfa790847dc5cb8999c2" - integrity sha512-07eOcJeMH3ZHNvx5DVMZQmy3vZSTQqKSSunbtM1pXb+k5LBPi5hMum1vJCFReXlo4wuLEqZ/OgbsZvXPhbGRtA== + version "4.7.3" + resolved "https://registry.npmjs.org/openid-client/-/openid-client-4.7.3.tgz#ea4f6f9ff6203dfbbe3d9bb4415d5dce751b0a70" + integrity sha512-YLwZQLSjo3gdSVxw/G25ddoRp9oCpXkREZXssmenlejZQPsnTq+yQtFUcBmC7u3VVkx+gwqXZF7X0CtAAJrRRg== dependencies: - base64url "^3.0.1" + aggregate-error "^3.1.0" got "^11.8.0" - jose "^2.0.2" + jose "^2.0.5" lru-cache "^6.0.0" make-error "^1.3.6" object-hash "^2.0.1" - oidc-token-hash "^5.0.0" - p-any "^3.0.0" + oidc-token-hash "^5.0.1" opn@^5.5.0: version "5.5.0" @@ -19943,14 +20012,6 @@ p-all@^2.1.0: dependencies: p-map "^2.0.0" -p-any@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/p-any/-/p-any-3.0.0.tgz#79847aeed70b5d3a10ea625296c0c3d2e90a87b9" - integrity sha512-5rqbqfsRWNb0sukt0awwgJMlaep+8jV45S15SKKB34z4UuzjcofIfnriCBhWjZP2jbVtjt9yRl7buB6RlKsu9w== - dependencies: - p-cancelable "^2.0.0" - p-some "^5.0.0" - p-cancelable@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" @@ -20095,14 +20156,6 @@ p-retry@^3.0.1: dependencies: retry "^0.12.0" -p-some@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/p-some/-/p-some-5.0.0.tgz#8b730c74b4fe5169d7264a240ad010b6ebc686a4" - integrity sha512-Js5XZxo6vHjB9NOYAzWDYAIyyiPvva0DWESAIWIK7uhSpGsyg5FwUPxipU/SOQx5x9EqhOh545d1jo6cVkitig== - dependencies: - aggregate-error "^3.0.0" - p-cancelable "^2.0.0" - p-timeout@^3.1.0, p-timeout@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" @@ -20758,13 +20811,6 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -pkg-dir@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" - integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== - dependencies: - find-up "^5.0.0" - pkg-up@3.1.0, pkg-up@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" @@ -23467,11 +23513,6 @@ semver-diff@^3.1.1: dependencies: semver "^6.3.0" -semver-regex@^3.1.2: - version "3.1.2" - resolved "https://registry.npmjs.org/semver-regex/-/semver-regex-3.1.2.tgz#34b4c0d361eef262e07199dbef316d0f2ab11807" - integrity sha512-bXWyL6EAKOJa81XG1OZ/Yyuq+oT0b2YLlxx7c+mrdYPaPbnj6WgVULXhinMIeZGufuUBu/eVRqXEhiv4imfwxA== - "semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: version "5.7.1" resolved "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -23532,6 +23573,11 @@ sentence-case@^3.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" +seq-queue@^0.0.5: + version "0.0.5" + resolved "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" + integrity sha1-1WgS4cAXpuTnw+Ojeh2m143TyT4= + serialize-error@^8.0.1, serialize-error@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz#3a069970c712f78634942ddd50fbbc0eaebe2f67" @@ -24137,6 +24183,11 @@ sqlite3@^5.0.0: optionalDependencies: node-gyp "3.x" +sqlstring@^2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.2.tgz#cdae7169389a1375b18e885f2e60b3e460809514" + integrity sha512-vF4ZbYdKS8OnoJAWBmMxCQDkiEBkGQYU7UZPtL8flbDRSNkhaXvRJ279ZtI6M+zDaQovVU4tuRgzK5fVhvFAhg== + ssh2-streams@~0.4.10: version "0.4.10" resolved "https://registry.npmjs.org/ssh2-streams/-/ssh2-streams-0.4.10.tgz#48ef7e8a0e39d8f2921c30521d56dacb31d23a34"