diff --git a/.changeset/beige-garlics-doubt.md b/.changeset/beige-garlics-doubt.md
deleted file mode 100644
index 436abce45e..0000000000
--- a/.changeset/beige-garlics-doubt.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/test-utils': patch
----
-
-Fix a bug in `MockStorageApi` where it unhelpfully returned new empty buckets every single time
diff --git a/.changeset/brave-lemons-hope.md b/.changeset/brave-lemons-hope.md
deleted file mode 100644
index 64c0d59538..0000000000
--- a/.changeset/brave-lemons-hope.md
+++ /dev/null
@@ -1,32 +0,0 @@
----
-'@backstage/plugin-scaffolder': patch
----
-
-Scaffolder Field Extensions are here! This means you'll now the ability to create custom field extensions and have the Scaffolder use the components when collecting information from the user in the wizard. By default we supply the `RepoUrlPicker` and the `OwnerPicker`, but if you want to provide some more extensions or override the built on ones you will have to change how the `ScaffolderPage` is wired up in your `app/src/App.tsx` to pass in the custom fields to the Scaffolder.
-
-You'll need to move this:
-
-```tsx
-} />
-```
-
-To this:
-
-```tsx
-import {
- ScaffolderFieldExtensions,
- RepoUrlPickerFieldExtension,
- OwnerPickerFieldExtension,
-} from '@backstage/plugin-scaffolder';
-
-}>
-
-
-
-
- {/*Any other extensions you want to provide*/}
-
-;
-```
-
-More documentation on how to write your own `FieldExtensions` to follow.
diff --git a/.changeset/brown-lobsters-enjoy.md b/.changeset/brown-lobsters-enjoy.md
deleted file mode 100644
index bc6a1771d8..0000000000
--- a/.changeset/brown-lobsters-enjoy.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Adjust the Webpack `devtool` module filename template to correctly resolve via the source maps to the source files.
diff --git a/.changeset/catalog-search-item.md b/.changeset/catalog-search-item.md
new file mode 100644
index 0000000000..c1a12ec161
--- /dev/null
+++ b/.changeset/catalog-search-item.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+A `` component is now available for use in custom Search Experiences.
diff --git a/.changeset/cyan-suns-chew.md b/.changeset/cyan-suns-chew.md
new file mode 100644
index 0000000000..95d51c23fe
--- /dev/null
+++ b/.changeset/cyan-suns-chew.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-cost-insights': patch
+---
+
+Move `canvas` package to `devDependencies`.
diff --git a/.changeset/cyan-weeks-lie.md b/.changeset/cyan-weeks-lie.md
deleted file mode 100644
index 6b313d6912..0000000000
--- a/.changeset/cyan-weeks-lie.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder': patch
----
-
-Remove the trailing space from a the aria-label of the Template "CHOOSE" button.
diff --git a/.changeset/early-colts-stare.md b/.changeset/early-colts-stare.md
deleted file mode 100644
index 7eb279d67e..0000000000
--- a/.changeset/early-colts-stare.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-Exclude core packages from package dependency diff.
diff --git a/.changeset/eighty-rabbits-fail.md b/.changeset/eighty-rabbits-fail.md
deleted file mode 100644
index 5f15d79ef4..0000000000
--- a/.changeset/eighty-rabbits-fail.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder': patch
----
-
-Don't merge with previous from state on form changes.
diff --git a/.changeset/empty-berries-melt.md b/.changeset/empty-berries-melt.md
new file mode 100644
index 0000000000..ab1125262a
--- /dev/null
+++ b/.changeset/empty-berries-melt.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Restructure the next catalog types and files a bit
diff --git a/.changeset/little-bobcats-explode.md b/.changeset/little-bobcats-explode.md
deleted file mode 100644
index c031fde505..0000000000
--- a/.changeset/little-bobcats-explode.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog': patch
----
-
-invert logic for when to show type column
diff --git a/.changeset/long-jokes-end.md b/.changeset/long-jokes-end.md
deleted file mode 100644
index 4c88d232fa..0000000000
--- a/.changeset/long-jokes-end.md
+++ /dev/null
@@ -1,11 +0,0 @@
----
-'@backstage/cli': patch
-'@backstage/core-app-api': patch
-'@backstage/core-components': patch
-'@backstage/core-plugin-api': patch
-'@backstage/dev-utils': patch
-'@backstage/test-utils': patch
-'@backstage/theme': patch
----
-
-Update installation instructions in README.
diff --git a/.changeset/mean-boats-speak.md b/.changeset/mean-boats-speak.md
deleted file mode 100644
index 94b873039f..0000000000
--- a/.changeset/mean-boats-speak.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/catalog-model': patch
----
-
-Remove the explicit connection from `EntityEnvelope` and `Entity`.
diff --git a/.changeset/mean-tigers-brake.md b/.changeset/mean-tigers-brake.md
deleted file mode 100644
index 07871a590b..0000000000
--- a/.changeset/mean-tigers-brake.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/integration': patch
----
-
-GitHub App ID can be a string too for environment variables otherwise it will fail validation
diff --git a/.changeset/old-crabs-jump.md b/.changeset/old-crabs-jump.md
new file mode 100644
index 0000000000..9441f3aeb7
--- /dev/null
+++ b/.changeset/old-crabs-jump.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Updated dependencies
diff --git a/.changeset/poor-buttons-sparkle.md b/.changeset/poor-buttons-sparkle.md
deleted file mode 100644
index c73becd930..0000000000
--- a/.changeset/poor-buttons-sparkle.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/core': patch
----
-
-Export `CheckboxTree` as we have a storybook for it
diff --git a/.changeset/real-hats-fail.md b/.changeset/real-hats-fail.md
deleted file mode 100644
index f0f3c9aaa6..0000000000
--- a/.changeset/real-hats-fail.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog-react': patch
----
-
-Sort `EntityTagPicker` entries.
diff --git a/.changeset/red-ducks-yawn.md b/.changeset/red-ducks-yawn.md
deleted file mode 100644
index 81d18a6eab..0000000000
--- a/.changeset/red-ducks-yawn.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder': patch
----
-
-Add a `` field to the scaffolder to pick arbitrary entity kinds, like systems.
diff --git a/.changeset/search-cross-the-goal.md b/.changeset/search-cross-the-goal.md
new file mode 100644
index 0000000000..580b28ac23
--- /dev/null
+++ b/.changeset/search-cross-the-goal.md
@@ -0,0 +1,9 @@
+---
+'@backstage/search-common': patch
+'@backstage/plugin-search-backend-node': patch
+'@backstage/plugin-search': patch
+---
+
+The ` set of components exported by the Search Plugin are now updated to use the Search Backend API. These will be made available as the default non-"next" versions in a follow-up release.
+
+The interfaces for decorators and collators in the Search Backend have also seen minor, breaking revisions ahead of a general release. If you happen to be building on top of these interfaces, check and update your implementations accordingly. The APIs will be considered more stable in a follow-up release.
diff --git a/.changeset/seven-badgers-marry.md b/.changeset/seven-badgers-marry.md
deleted file mode 100644
index de9431c41b..0000000000
--- a/.changeset/seven-badgers-marry.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-scaffolder-backend': patch
----
-
-This adds a configuration option to the scaffolder plugin router, so we can allow for multiple `TaskWorkers`. Currently with only one `TaskWorker` you are limited to scaffolding one thing at a time. Set the `taskWorkers?: number` option in your scaffolder router to get more than 1 `TaskWorker`
diff --git a/.changeset/seven-ligers-watch.md b/.changeset/seven-ligers-watch.md
deleted file mode 100644
index 3742dca690..0000000000
--- a/.changeset/seven-ligers-watch.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/create-app': patch
----
-
-Add CLI output and README how to start app after create-app CLI
diff --git a/.changeset/slimy-kids-attack.md b/.changeset/slimy-kids-attack.md
deleted file mode 100644
index ddb7735571..0000000000
--- a/.changeset/slimy-kids-attack.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-catalog-react': patch
----
-
-Tweak the `EntityListProvider` to do single-cycle updates
diff --git a/.changeset/smart-bugs-argue.md b/.changeset/smart-bugs-argue.md
deleted file mode 100644
index 373b419de7..0000000000
--- a/.changeset/smart-bugs-argue.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/cli': patch
----
-
-chore(deps): bump `@spotify/eslint-config-react` from 9.0.0 to 10.0.0
diff --git a/.changeset/techdocs-nice-forks-flow.md b/.changeset/techdocs-nice-forks-flow.md
deleted file mode 100644
index 3afc628a7d..0000000000
--- a/.changeset/techdocs-nice-forks-flow.md
+++ /dev/null
@@ -1,6 +0,0 @@
----
-'@backstage/techdocs-common': patch
----
-
-Support parsing `mkdocs.yml` files that are using custom yaml tags like
-`!!python/name:materialx.emoji.twemoji`.
diff --git a/.changeset/thin-cougars-cheer.md b/.changeset/thin-cougars-cheer.md
deleted file mode 100644
index 50c8a1ae05..0000000000
--- a/.changeset/thin-cougars-cheer.md
+++ /dev/null
@@ -1,7 +0,0 @@
----
-'@backstage/plugin-catalog': patch
-'@backstage/plugin-catalog-backend': patch
----
-
-Move dependency to `@microsoft/microsoft-graph-types` from `@backstage/plugin-catalog`
-to `@backstage/plugin-catalog-backend`.
diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt
index b8f028a2ae..d91b840292 100644
--- a/.github/styles/vocab.txt
+++ b/.github/styles/vocab.txt
@@ -210,6 +210,7 @@ rst
rsync
ruleset
sam
+sanitization
scaffolded
scaffolder
Scaffolder
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 0a6d0f8589..4aa6cd9f8d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -76,6 +76,9 @@ jobs:
- name: prettier
run: yarn prettier:check
+ - name: lock
+ run: yarn lock:check
+
- name: validate config
run: yarn backstage-cli config:check --lax
diff --git a/.github/workflows/microsite-build-check.yml b/.github/workflows/microsite-build-check.yml
index 881a7ca5e4..68a6c08cd9 100644
--- a/.github/workflows/microsite-build-check.yml
+++ b/.github/workflows/microsite-build-check.yml
@@ -40,6 +40,10 @@ jobs:
run: yarn prettier:check
working-directory: microsite
+ - name: lock
+ run: yarn lock:check
+ working-directory: microsite
+
- name: build microsite
run: yarn build
working-directory: microsite
diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md
index fefb20b095..2e568664d9 100644
--- a/docs/getting-started/create-an-app.md
+++ b/docs/getting-started/create-an-app.md
@@ -23,7 +23,8 @@ Backstage provides a utility for creating new apps. It guides you through the
initial setup of selecting the name of the app and a database for the backend.
The database options are either SQLite or PostgreSQL, where the latter requires
you to set up a separate database instance. If in doubt, choose SQLite, but
-don't worry about the choice, it's easy to change later!
+don't worry about the choice, it's easy to change later! Here is a
+[tutorial](../tutorials/switching-sqlite-postgres.md) for it.
The easiest way to run the create app package is with `npx`:
diff --git a/microsite/data/plugins/cortex.yaml b/microsite/data/plugins/cortex.yaml
new file mode 100644
index 0000000000..1f1c57adfd
--- /dev/null
+++ b/microsite/data/plugins/cortex.yaml
@@ -0,0 +1,13 @@
+---
+title: Cortex Service Quality Scorecards
+author: Cortex
+authorUrl: https://www.getcortexapp.com
+category: Monitoring
+description: Grade the quality of your Backstage services using Scorecards. Automate production readiness, migrations, security audits, and more with CQL (Cortex Query Language).
+documentation: https://github.com/cortexapps/backstage-plugin
+iconUrl: img/cortex.png
+npmPackageName: '@cortexapps/backstage-plugin'
+tags:
+ - web
+ - monitoring
+ - sre
diff --git a/microsite/package.json b/microsite/package.json
index 595debcc65..66b3fc9b80 100644
--- a/microsite/package.json
+++ b/microsite/package.json
@@ -12,13 +12,15 @@
"write-translations": "docusaurus-write-translations",
"version": "docusaurus-version",
"rename-version": "docusaurus-rename-version",
- "verify:sidebars": "node ./scripts/verify-sidebars"
+ "verify:sidebars": "node ./scripts/verify-sidebars",
+ "lock:check": "yarn-lock-check"
},
"devDependencies": {
"@spotify/prettier-config": "^10.0.0",
"docusaurus": "^2.0.0-alpha.70",
"js-yaml": "^4.1.0",
- "prettier": "^2.3.0"
+ "prettier": "^2.3.0",
+ "yarn-lock-check": "^1.0.4"
},
"prettier": "@spotify/prettier-config"
}
diff --git a/microsite/static/img/cortex.png b/microsite/static/img/cortex.png
new file mode 100644
index 0000000000..6a45d0ca06
Binary files /dev/null and b/microsite/static/img/cortex.png differ
diff --git a/microsite/yarn.lock b/microsite/yarn.lock
index 4033968a2e..a6a86df36f 100644
--- a/microsite/yarn.lock
+++ b/microsite/yarn.lock
@@ -9,6 +9,13 @@
dependencies:
"@babel/highlight" "^7.0.0"
+"@babel/code-frame@^7.0.0":
+ version "7.12.13"
+ resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz#dcfc826beef65e75c50e21d3837d7d95798dd658"
+ integrity sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==
+ dependencies:
+ "@babel/highlight" "^7.12.13"
+
"@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.11":
version "7.12.11"
resolved "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
@@ -220,6 +227,11 @@
resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz#c9a1f021917dcb5ccf0d4e453e399022981fc9ed"
integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
+"@babel/helper-validator-identifier@^7.14.0":
+ version "7.14.0"
+ resolved "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz#d26cad8a47c65286b15df1547319a5d0bcf27288"
+ integrity sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==
+
"@babel/helper-validator-option@^7.12.1", "@babel/helper-validator-option@^7.12.11":
version "7.12.11"
resolved "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz#d66cb8b7a3e7fe4c6962b32020a131ecf0847f4f"
@@ -253,6 +265,15 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
+"@babel/highlight@^7.12.13":
+ version "7.14.0"
+ resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf"
+ integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.14.0"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
"@babel/parser@^7.12.10", "@babel/parser@^7.12.11", "@babel/parser@^7.12.7":
version "7.12.11"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz#9ce3595bcd74bc5c466905e86c535b8b25011e79"
@@ -911,7 +932,7 @@
"@spotify/prettier-config@^10.0.0":
version "10.0.0"
- resolved "https://registry.yarnpkg.com/@spotify/prettier-config/-/prettier-config-10.0.0.tgz#fa076d98d2e7e6c53dd3d86a696307a7010bd056"
+ resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-10.0.0.tgz#fa076d98d2e7e6c53dd3d86a696307a7010bd056"
integrity sha512-VYOdo8P7lIScAkl02nB9KpUAuOYMManryBIBuKJkAw5D3aVtLobfmdIKvdV6MqEmGMEQPbn7w/UpnjJYhUH+IA==
"@types/cheerio@^0.22.8":
@@ -921,16 +942,39 @@
dependencies:
"@types/node" "*"
+"@types/glob@^7.1.3":
+ version "7.1.3"
+ resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
+ integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==
+ dependencies:
+ "@types/minimatch" "*"
+ "@types/node" "*"
+
+"@types/minimatch@*":
+ version "3.0.4"
+ resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz#f0ec25dbf2f0e4b18647313ac031134ca5b24b21"
+ integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==
+
"@types/node@*":
version "14.14.20"
resolved "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz#f7974863edd21d1f8a494a73e8e2b3658615c340"
integrity sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==
+"@types/node@^15.6.1":
+ version "15.9.0"
+ resolved "https://registry.npmjs.org/@types/node/-/node-15.9.0.tgz#0b7f6c33ca5618fe329a9d832b478b4964d325a8"
+ integrity sha512-AR1Vq1Ei1GaA5FjKL5PBqblTZsL5M+monvGSZwe6sSIdGiuu7Xr/pNwWJY+0ZQuN8AapD/XMB5IzBAyYRFbocA==
+
"@types/q@^1.5.1":
version "1.5.4"
resolved "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
+"@yarnpkg/lockfile@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
+ integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+
accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
@@ -1039,7 +1083,7 @@ argparse@^1.0.10, argparse@^1.0.7:
argparse@^2.0.1:
version "2.0.1"
- resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+ resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
arr-diff@^4.0.0:
@@ -1405,6 +1449,11 @@ buffer@^5.2.1:
base64-js "^1.3.1"
ieee754 "^1.1.13"
+builtin-modules@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+ integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
+
bytes@1:
version "1.0.0"
resolved "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8"
@@ -1518,7 +1567,7 @@ caw@^2.0.0, caw@^2.0.1:
tunnel-agent "^0.6.0"
url-to-options "^1.0.1"
-chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@2.4.2, chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -1709,7 +1758,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
-commander@^2.8.1:
+commander@^2.12.1, commander@^2.8.1:
version "2.20.3"
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -2181,6 +2230,11 @@ diacritics-map@^0.1.0:
resolved "https://registry.npmjs.org/diacritics-map/-/diacritics-map-0.1.0.tgz#6dfc0ff9d01000a2edf2865371cac316e94977af"
integrity sha1-bfwP+dAQAKLt8oZTccrDFulJd68=
+diff@^4.0.1:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
+ integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
dir-glob@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz#0b205d2b6aef98238ca286598a8204d29d0a0034"
@@ -3049,6 +3103,18 @@ glob@^7.0.0, glob@^7.0.5, glob@^7.1.2, glob@^7.1.3, glob@^7.1.6, glob@~7.1.1:
once "^1.3.0"
path-is-absolute "^1.0.0"
+glob@^7.1.1, glob@^7.1.7:
+ version "7.1.7"
+ resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
+ integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
global-modules@2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
@@ -3460,6 +3526,11 @@ ini@^1.3.4, ini@^1.3.5:
resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
+ini@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
+ integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
+
inquirer@6.5.0:
version "6.5.0"
resolved "https://registry.npmjs.org/inquirer/-/inquirer-6.5.0.tgz#2303317efc9a4ea7ec2e2df6f86569b734accf42"
@@ -3567,6 +3638,13 @@ is-core-module@^2.1.0:
dependencies:
has "^1.0.3"
+is-core-module@^2.2.0:
+ version "2.4.0"
+ resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1"
+ integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==
+ dependencies:
+ has "^1.0.3"
+
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
@@ -3864,7 +3942,7 @@ js-yaml@^3.13.1, js-yaml@^3.8.1:
js-yaml@^4.1.0:
version "4.1.0"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+ resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
@@ -4379,7 +4457,7 @@ mixin-deep@^1.1.3, mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
-mkdirp@^0.5.1, mkdirp@^0.5.5, mkdirp@~0.5.1:
+mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.5, mkdirp@~0.5.1:
version "0.5.5"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
@@ -5199,7 +5277,7 @@ prepend-http@^2.0.0:
prettier@^2.3.0:
version "2.3.0"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18"
+ resolved "https://registry.npmjs.org/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18"
integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w==
prismjs@^1.22.0:
@@ -5603,6 +5681,14 @@ resolve@^1.1.6, resolve@^1.10.0:
is-core-module "^2.1.0"
path-parse "^1.0.6"
+resolve@^1.3.2:
+ version "1.20.0"
+ resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
+ integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
+ dependencies:
+ is-core-module "^2.2.0"
+ path-parse "^1.0.6"
+
responselike@1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
@@ -6351,11 +6437,37 @@ truncate-html@^1.0.3:
"@types/cheerio" "^0.22.8"
cheerio "0.22.0"
-tslib@^1.9.0, tslib@^1.9.3:
+tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+tslint@^6.1.3:
+ version "6.1.3"
+ resolved "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904"
+ integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ builtin-modules "^1.1.1"
+ chalk "^2.3.0"
+ commander "^2.12.1"
+ diff "^4.0.1"
+ glob "^7.1.1"
+ js-yaml "^3.13.1"
+ minimatch "^3.0.4"
+ mkdirp "^0.5.3"
+ resolve "^1.3.2"
+ semver "^5.3.0"
+ tslib "^1.13.0"
+ tsutils "^2.29.0"
+
+tsutils@^2.29.0:
+ version "2.29.0"
+ resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
+ integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==
+ dependencies:
+ tslib "^1.8.1"
+
tunnel-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
@@ -6381,6 +6493,11 @@ typedarray@^0.0.6:
resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+typescript@^4.3.2:
+ version "4.3.2"
+ resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
+ integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==
+
unbzip2-stream@^1.0.9:
version "1.4.3"
resolved "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
@@ -6650,6 +6767,19 @@ yargs@^2.3.0:
dependencies:
wordwrap "0.0.2"
+yarn-lock-check@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.4.tgz#a0373de051be0c8442d8933070df7a45595263b4"
+ integrity sha512-Gj0wRN85c4OPZUlE7WsQ0a1COv38uyeWWR0YAvJr2Vxw1f32bwK19xySjUZlxt9o4QopJkd8g6x6CLv81OHwYg==
+ dependencies:
+ "@types/glob" "^7.1.3"
+ "@types/node" "^15.6.1"
+ "@yarnpkg/lockfile" "^1.1.0"
+ glob "^7.1.7"
+ ini "^2.0.0"
+ tslint "^6.1.3"
+ typescript "^4.3.2"
+
yauzl@^2.4.2:
version "2.10.0"
resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
diff --git a/package.json b/package.json
index 1ffeb490a3..6a663c877e 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,8 @@
"lerna": "lerna",
"storybook": "yarn workspace storybook start",
"build-storybook": "yarn workspace storybook build-storybook",
- "prepare": "husky install"
+ "prepare": "husky install",
+ "lock:check": "yarn-lock-check"
},
"workspaces": {
"packages": [
@@ -56,7 +57,7 @@
"devDependencies": {
"@changesets/cli": "^2.14.0",
"@octokit/openapi-types": "^2.2.0",
- "@spotify/prettier-config": "^9.0.0",
+ "@spotify/prettier-config": "^10.0.0",
"command-exists": "^1.2.9",
"concurrently": "^6.0.0",
"eslint-plugin-notice": "^0.9.10",
@@ -66,7 +67,8 @@
"lint-staged": "^10.1.0",
"prettier": "^2.2.1",
"recursive-readdir": "^2.2.2",
- "shx": "^0.3.2"
+ "shx": "^0.3.2",
+ "yarn-lock-check": "^1.0.4"
},
"prettier": "@spotify/prettier-config",
"lint-staged": {
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index b909295afc..7f9e9e554d 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -1,5 +1,38 @@
# example-app
+## 0.2.31
+
+### Patch Changes
+
+- Updated dependencies [497f4ce18]
+- Updated dependencies [ee4eb5b40]
+- Updated dependencies [84160313e]
+- Updated dependencies [3772de8ba]
+- Updated dependencies [7e7c71417]
+- Updated dependencies [f430b6c6f]
+- Updated dependencies [2a942cc9e]
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [1cf1d351f]
+- Updated dependencies [90a505a77]
+- Updated dependencies [76f99a1a0]
+- Updated dependencies [deaba2e13]
+- Updated dependencies [1157fa307]
+- Updated dependencies [8e919a6f8]
+- Updated dependencies [2305ab8fc]
+- Updated dependencies [054bcd029]
+- Updated dependencies [aad98c544]
+- Updated dependencies [f46a9e82d]
+ - @backstage/plugin-scaffolder@0.9.7
+ - @backstage/cli@0.6.14
+ - @backstage/plugin-catalog@0.6.1
+ - @backstage/theme@0.2.8
+ - @backstage/catalog-model@0.8.1
+ - @backstage/core@0.7.12
+ - @backstage/plugin-tech-radar@0.4.0
+ - @backstage/plugin-catalog-react@0.2.1
+ - @backstage/plugin-techdocs@0.9.5
+
## 0.2.30
### Patch Changes
diff --git a/packages/app/cypress/integration/components/search/SearchPage.js b/packages/app/cypress/integration/components/search/SearchPage.js
new file mode 100644
index 0000000000..4e13fc8a0f
--- /dev/null
+++ b/packages/app/cypress/integration/components/search/SearchPage.js
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+const API_ENDPOINT = 'http://localhost:7000/api/search/query';
+
+describe('SearchPage', () => {
+ describe('Given a search context with a term, results, and filter values', () => {
+ it('The results are rendered as expected', () => {
+ const results = [
+ {
+ type: 'software-catalog',
+ document: {
+ title: 'backstage',
+ text: 'Backstage system documentation',
+ location: '/result/location/path',
+ },
+ },
+ ];
+
+ cy.enterAsGuest();
+ cy.visit('/search-next', {
+ onBeforeLoad(win) {
+ cy.stub(win, 'fetch')
+ .withArgs(`${API_ENDPOINT}?term=&pageCursor=`)
+ .resolves({
+ ok: true,
+ json: () => ({ results }),
+ });
+ },
+ });
+ cy.contains('Search');
+
+ cy.contains(results[0].document.title);
+ cy.contains(results[0].document.text);
+ cy.get(`a[href="${results[0].document.location}"]`).should('be.visible');
+ });
+
+ it('The filters are rendered as expected', () => {
+ cy.enterAsGuest();
+ cy.visit(
+ '/search-next?filters%5Bkind%5D=Component&filters%5Blifecycle%5D%5B%5D=experimental',
+ {
+ onBeforeLoad(win) {
+ cy.stub(win, 'fetch')
+ .withArgs(
+ `${API_ENDPOINT}?term=&filters%5Bkind%5D=Component&filters%5Blifecycle%5D%5B0%5D=experimental&pageCursor=`,
+ )
+ .resolves({
+ ok: true,
+ json: () => ({ results: [] }),
+ });
+ },
+ },
+ );
+ cy.contains('Search');
+
+ // lifecycle
+ cy.contains('lifecycle');
+
+ cy.contains('experimental');
+ cy.get(
+ '[data-testid="search-checkboxfilter-next"] input[value="experimental"]',
+ ).should('have.attr', 'checked');
+
+ cy.contains('production');
+ cy.get(
+ '[data-testid="search-checkboxfilter-next"] input[value="production"]',
+ ).should('not.have.attr', 'checked');
+
+ // kind
+ cy.contains('kind');
+ cy.get(
+ '[data-testid="search-selectfilter-next"] [role="button"][aria-haspopup="listbox"]',
+ ).click();
+
+ cy.contains('All');
+ cy.contains('Template');
+ cy.contains('Component');
+
+ cy.get('[role="option"][data-value="Component"]').should(
+ 'have.attr',
+ 'aria-selected',
+ 'true',
+ );
+ });
+
+ it('The search bar is rendered as expected', () => {
+ cy.enterAsGuest();
+ cy.visit('/search-next?query=backstage', {
+ onBeforeLoad(win) {
+ cy.stub(win, 'fetch')
+ .withArgs(`${API_ENDPOINT}?term=backstage&pageCursor=`)
+ .resolves({
+ ok: true,
+ json: () => ({ results: [] }),
+ });
+ },
+ });
+ cy.contains('Search');
+
+ cy.get('[data-testid="search-bar-next"] input').should(
+ 'have.attr',
+ 'value',
+ 'backstage',
+ );
+ });
+ });
+});
diff --git a/packages/app/cypress/support/commands.js b/packages/app/cypress/support/commands.js
new file mode 100644
index 0000000000..dd2b26634d
--- /dev/null
+++ b/packages/app/cypress/support/commands.js
@@ -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.
+ */
+Cypress.Commands.add('enterAsGuest', () => {
+ cy.visit('/');
+ cy.get('button').contains('Enter').click();
+});
diff --git a/packages/app/cypress/support/index.js b/packages/app/cypress/support/index.js
index 8fc8ca91f1..c1f930027a 100644
--- a/packages/app/cypress/support/index.js
+++ b/packages/app/cypress/support/index.js
@@ -14,3 +14,4 @@
* limitations under the License.
*/
import '@testing-library/cypress/add-commands';
+import './commands';
diff --git a/packages/app/package.json b/packages/app/package.json
index be53a2b992..03d87dc243 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,18 +1,18 @@
{
"name": "example-app",
- "version": "0.2.30",
+ "version": "0.2.31",
"private": true,
"bundled": true,
"dependencies": {
- "@backstage/catalog-model": "^0.8.0",
- "@backstage/cli": "^0.6.13",
- "@backstage/core": "^0.7.11",
+ "@backstage/catalog-model": "^0.8.1",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/core": "^0.7.12",
"@backstage/integration-react": "^0.1.2",
"@backstage/plugin-api-docs": "^0.4.15",
"@backstage/plugin-badges": "^0.2.2",
- "@backstage/plugin-catalog": "^0.6.0",
+ "@backstage/plugin-catalog": "^0.6.1",
"@backstage/plugin-catalog-import": "^0.5.8",
- "@backstage/plugin-catalog-react": "^0.2.0",
+ "@backstage/plugin-catalog-react": "^0.2.1",
"@backstage/plugin-circleci": "^0.2.15",
"@backstage/plugin-cloudbuild": "^0.2.16",
"@backstage/plugin-code-coverage": "^0.1.4",
@@ -29,15 +29,15 @@
"@backstage/plugin-org": "^0.3.14",
"@backstage/plugin-pagerduty": "0.3.5",
"@backstage/plugin-rollbar": "^0.3.6",
- "@backstage/plugin-scaffolder": "^0.9.6",
+ "@backstage/plugin-scaffolder": "^0.9.7",
"@backstage/plugin-search": "^0.3.7",
"@backstage/plugin-sentry": "^0.3.11",
"@backstage/plugin-shortcuts": "^0.1.2",
- "@backstage/plugin-tech-radar": "^0.3.11",
- "@backstage/plugin-techdocs": "^0.9.4",
+ "@backstage/plugin-tech-radar": "^0.4.0",
+ "@backstage/plugin-techdocs": "^0.9.5",
"@backstage/plugin-todo": "^0.1.2",
"@backstage/plugin-user-settings": "^0.2.10",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.5.3",
@@ -56,7 +56,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/cypress": "^7.0.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index c5e5033973..5874465ef6 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -54,6 +54,7 @@ import { Navigate, Route } from 'react-router';
import { apis } from './apis';
import { Root } from './components/Root';
import { entityPage } from './components/catalog/EntityPage';
+import { searchPage } from './components/search/SearchPage';
import { providers } from './identityProviders';
import * as plugins from './plugins';
@@ -119,10 +120,9 @@ const routes = (
} />
} />
} />
- }
- />
+ }>
+ {searchPage}
+
} />
({
+ bar: {
+ padding: theme.spacing(1, 0),
+ },
+ filters: {
+ padding: theme.spacing(2),
+ },
+ filter: {
+ '& + &': {
+ marginTop: theme.spacing(2.5),
+ },
+ },
+}));
+
+const SearchPage = () => {
+ const classes = useStyles();
+
+ return (
+
+ } />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {({ results }) => (
+
+ {results.map(({ type, document }) => {
+ switch (type) {
+ case 'software-catalog':
+ return (
+
+ );
+ default:
+ return (
+
+ );
+ }
+ })}
+
+ )}
+
+
+
+
+
+ );
+};
+
+export const searchPage = ;
diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts
index 1691f53485..6688b9c158 100644
--- a/packages/backend/src/plugins/search.ts
+++ b/packages/backend/src/plugins/search.ts
@@ -30,7 +30,6 @@ export default async function createPlugin({
const indexBuilder = new IndexBuilder({ logger, searchEngine });
indexBuilder.addCollator({
- type: 'software-catalog',
defaultRefreshIntervalSeconds: 600,
collator: new DefaultCatalogCollator({ discovery }),
});
diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md
index 8598a9ec8c..94b4662a97 100644
--- a/packages/catalog-model/CHANGELOG.md
+++ b/packages/catalog-model/CHANGELOG.md
@@ -1,5 +1,11 @@
# @backstage/catalog-model
+## 0.8.1
+
+### Patch Changes
+
+- ebe802bc4: Remove the explicit connection from `EntityEnvelope` and `Entity`.
+
## 0.8.0
### Minor Changes
diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json
index d924d895a6..edcffaa691 100644
--- a/packages/catalog-model/package.json
+++ b/packages/catalog-model/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/catalog-model",
- "version": "0.8.0",
+ "version": "0.8.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -41,7 +41,7 @@
"yup": "^0.29.3"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
+ "@backstage/cli": "^0.6.14",
"@types/express": "^4.17.6",
"@types/jest": "^26.0.7",
"@types/lodash": "^4.14.151",
diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md
index 5e5842fc16..da06bfea17 100644
--- a/packages/cli/CHANGELOG.md
+++ b/packages/cli/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/cli
+## 0.6.14
+
+### Patch Changes
+
+- ee4eb5b40: Adjust the Webpack `devtool` module filename template to correctly resolve via the source maps to the source files.
+- 84160313e: Mark the `create-github-app` command as ready for use and reveal it in the command list.
+- 7e7c71417: Exclude core packages from package dependency diff.
+- e7c5e4b30: Update installation instructions in README.
+- 2305ab8fc: chore(deps): bump `@spotify/eslint-config-react` from 9.0.0 to 10.0.0
+- 054bcd029: Deprecated the `backend:build-image` command, pointing to the newer `backend:bundle` command.
+
## 0.6.13
### Patch Changes
diff --git a/packages/cli/package.json b/packages/cli/package.json
index e7126e5fe4..3f075824dc 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.13",
+ "version": "0.6.14",
"private": false,
"publishConfig": {
"access": "public"
@@ -52,7 +52,7 @@
"@types/start-server-webpack-plugin": "^2.2.0",
"@types/webpack-env": "^1.15.2",
"@types/webpack-node-externals": "^2.5.0",
- "@typescript-eslint/eslint-plugin": "^v4.15.2",
+ "@typescript-eslint/eslint-plugin": "^v4.26.0",
"@typescript-eslint/parser": "^v4.14.0",
"@yarnpkg/lockfile": "^1.1.0",
"babel-plugin-dynamic-import-node": "^2.3.3",
@@ -120,10 +120,10 @@
"devDependencies": {
"@backstage/backend-common": "^0.8.1",
"@backstage/config": "^0.1.5",
- "@backstage/core": "^0.7.11",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
- "@backstage/theme": "^0.2.7",
+ "@backstage/core": "^0.7.12",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
+ "@backstage/theme": "^0.2.8",
"@types/diff": "^5.0.0",
"@types/express": "^4.17.6",
"@types/fs-extra": "^9.0.1",
diff --git a/packages/cli/src/commands/backend/buildImage.ts b/packages/cli/src/commands/backend/buildImage.ts
index 654f51cd10..8434017cd9 100644
--- a/packages/cli/src/commands/backend/buildImage.ts
+++ b/packages/cli/src/commands/backend/buildImage.ts
@@ -15,6 +15,7 @@
*/
import { Command } from 'commander';
+import { yellow } from 'chalk';
import fs from 'fs-extra';
import { join as joinPath, relative as relativePath } from 'path';
import { createDistWorkspace } from '../../lib/packager';
@@ -31,6 +32,15 @@ export default async (cmd: Command) => {
return;
}
+ console.warn(
+ yellow(`
+The backend:build-image command is deprecated and will be removed in the future.
+Please use the backend:bundle command instead along with your own Docker setup.
+
+ https://backstage.io/docs/deployment/docker
+`),
+ );
+
const pkgPath = paths.resolveTarget(PKG_PATH);
const pkg = await fs.readJson(pkgPath);
const appConfigs = await findAppConfigs();
diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts
index ae39941106..431659a972 100644
--- a/packages/cli/src/commands/index.ts
+++ b/packages/cli/src/commands/index.ts
@@ -60,11 +60,7 @@ export function registerCommands(program: CommanderStatic) {
.helpOption(', --backstage-cli-help') // Let docker handle --help
.option('--build', 'Build packages before packing them into the image')
.description(
- // TODO: Add example use cases in Backstage documentation.
- // For example, if a $NPM_TOKEN needs to be exposed, run `backend:build-image --secret
- // id=NPM_TOKEN,src=/NPM_TOKEN.txt`.
- 'Bundles the package into a docker image. All extra args are forwarded to ' +
- '`docker image build`.',
+ 'Bundles the package into a docker image. This command is deprecated and will be removed.',
)
.action(lazy(() => import('./backend/buildImage').then(m => m.default)));
@@ -225,10 +221,8 @@ export function registerCommands(program: CommanderStatic) {
.action(lazy(() => import('./buildWorkspace').then(m => m.default)));
program
- .command('create-github-app ', { hidden: true })
- .description(
- 'Create new GitHub App in your organization. This command is experimental and may change in the future.',
- )
+ .command('create-github-app ')
+ .description('Create new GitHub App in your organization.')
.action(lazy(() => import('./create-github-app').then(m => m.default)));
}
diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md
index b6bc5060e4..c06e2b6789 100644
--- a/packages/core-api/CHANGELOG.md
+++ b/packages/core-api/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/core-api
+## 0.2.21
+
+### Patch Changes
+
+- 0160678b1: Made the `RouteRef*` types compatible with the ones exported from `@backstage/core-plugin-api`.
+- Updated dependencies [031ccd45f]
+- Updated dependencies [e7c5e4b30]
+ - @backstage/core-plugin-api@0.1.1
+ - @backstage/theme@0.2.8
+
## 0.2.20
### Patch Changes
diff --git a/packages/core-api/package.json b/packages/core-api/package.json
index 6ecb942c28..1c3e707a29 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.20",
+ "version": "0.2.21",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,7 +30,8 @@
},
"dependencies": {
"@backstage/config": "^0.1.4",
- "@backstage/theme": "^0.2.6",
+ "@backstage/core-plugin-api": "^0.1.1",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@types/react": "^16.9",
@@ -42,8 +43,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/test-utils": "^0.1.13",
"@backstage/test-utils-core": "^0.1.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
diff --git a/packages/core-api/src/routing/types.ts b/packages/core-api/src/routing/types.ts
index 0089e29e4b..e88b2f748c 100644
--- a/packages/core-api/src/routing/types.ts
+++ b/packages/core-api/src/routing/types.ts
@@ -16,6 +16,7 @@
import { IconComponent } from '../icons/types';
import { getOrCreateGlobalSingleton } from '../lib/globalObject';
+import { RouteRef as NewRouteRef } from '@backstage/core-plugin-api';
export type AnyParams = { [param in string]: string } | undefined;
export type ParamKeys = keyof Params extends never
@@ -34,7 +35,11 @@ export type RouteFunc = (
...[params]: Params extends undefined ? readonly [] : readonly [Params]
) => string;
-export const routeRefType: unique symbol = getOrCreateGlobalSingleton(
+type RouteRefType = Exclude<
+ keyof NewRouteRef,
+ 'params' | 'path' | 'title' | 'icon'
+>;
+export const routeRefType: RouteRefType = getOrCreateGlobalSingleton(
'route-ref-type',
() => Symbol('route-ref-type'),
);
diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md
new file mode 100644
index 0000000000..f98cf7eeb1
--- /dev/null
+++ b/packages/core-app-api/CHANGELOG.md
@@ -0,0 +1,12 @@
+# @backstage/core-app-api
+
+## 0.1.1
+
+### Patch Changes
+
+- e7c5e4b30: Update installation instructions in README.
+- Updated dependencies [031ccd45f]
+- Updated dependencies [e7c5e4b30]
+ - @backstage/core-plugin-api@0.1.1
+ - @backstage/core-components@0.1.1
+ - @backstage/theme@0.2.8
diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json
index 296a80ce94..ddb598ce9b 100644
--- a/packages/core-app-api/package.json
+++ b/packages/core-app-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-app-api",
"description": "Core app API used by Backstage apps",
- "version": "0.1.0",
+ "version": "0.1.1",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,10 +29,10 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core-components": "^0.1.0",
+ "@backstage/core-components": "^0.1.1",
"@backstage/config": "^0.1.3",
- "@backstage/core-plugin-api": "^0.1.0",
- "@backstage/theme": "^0.2.3",
+ "@backstage/core-plugin-api": "^0.1.1",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@types/react": "^16.9",
@@ -44,8 +44,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.3",
- "@backstage/test-utils": "^0.1.8",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/test-utils": "^0.1.13",
"@backstage/test-utils-core": "^0.1.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md
new file mode 100644
index 0000000000..93941494c8
--- /dev/null
+++ b/packages/core-components/CHANGELOG.md
@@ -0,0 +1,11 @@
+# @backstage/core-components
+
+## 0.1.1
+
+### Patch Changes
+
+- e7c5e4b30: Update installation instructions in README.
+- Updated dependencies [031ccd45f]
+- Updated dependencies [e7c5e4b30]
+ - @backstage/core-plugin-api@0.1.1
+ - @backstage/theme@0.2.8
diff --git a/packages/core-components/package.json b/packages/core-components/package.json
index 003b4bdac6..1ce4adf2b7 100644
--- a/packages/core-components/package.json
+++ b/packages/core-components/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-components",
"description": "Core components used by Backstage plugins and apps",
- "version": "0.1.0",
+ "version": "0.1.1",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,9 +30,9 @@
},
"dependencies": {
"@backstage/config": "^0.1.5",
- "@backstage/core-plugin-api": "^0.1.0",
+ "@backstage/core-plugin-api": "^0.1.1",
"@backstage/errors": "^0.1.1",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -70,9 +70,9 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/core-app-api": "^0.1.0",
- "@backstage/cli": "^0.6.11",
- "@backstage/test-utils": "^0.1.11",
+ "@backstage/core-app-api": "^0.1.1",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md
new file mode 100644
index 0000000000..fcb4b00a4b
--- /dev/null
+++ b/packages/core-plugin-api/CHANGELOG.md
@@ -0,0 +1,10 @@
+# @backstage/core-plugin-api
+
+## 0.1.1
+
+### Patch Changes
+
+- 031ccd45f: Made the deprecated `icon` fields compatible with the `IconComponent` type from `@backstage/core` in order to smooth out the migration.
+- e7c5e4b30: Update installation instructions in README.
+- Updated dependencies [e7c5e4b30]
+ - @backstage/theme@0.2.8
diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md
index 339873cd85..dcf53d8aed 100644
--- a/packages/core-plugin-api/api-report.md
+++ b/packages/core-plugin-api/api-report.md
@@ -9,6 +9,7 @@ import { ComponentType } from 'react';
import { Config } from '@backstage/config';
import { default as React_2 } from 'react';
import { ReactNode } from 'react';
+import { SvgIconProps } from '@material-ui/core';
// @public
export type AlertApi = {
@@ -206,7 +207,7 @@ export function createRouteRef>;
@@ -424,7 +425,7 @@ export type RouteRef = {
readonly [routeRefType]: 'absolute';
params: ParamKeys;
path: string;
- icon?: IconComponent;
+ icon?: OldIconComponent;
title?: string;
};
diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json
index bb6dfdc4bd..64c2154ce9 100644
--- a/packages/core-plugin-api/package.json
+++ b/packages/core-plugin-api/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-plugin-api",
"description": "Core API used by Backstage plugins",
- "version": "0.1.0",
+ "version": "0.1.1",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,7 +30,8 @@
},
"dependencies": {
"@backstage/config": "^0.1.3",
- "@backstage/theme": "^0.2.3",
+ "@backstage/theme": "^0.2.8",
+ "@material-ui/core": "^4.11.0",
"@types/react": "^16.9",
"history": "^5.0.0",
"prop-types": "^15.7.2",
@@ -40,8 +41,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.12",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/test-utils": "^0.1.13",
"@backstage/test-utils-core": "^0.1.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
diff --git a/packages/core-plugin-api/src/icons/index.ts b/packages/core-plugin-api/src/icons/index.ts
index 50e9534751..953c6ef1ed 100644
--- a/packages/core-plugin-api/src/icons/index.ts
+++ b/packages/core-plugin-api/src/icons/index.ts
@@ -14,4 +14,4 @@
* limitations under the License.
*/
-export * from './types';
+export type { IconComponent } from './types';
diff --git a/packages/core-plugin-api/src/icons/types.ts b/packages/core-plugin-api/src/icons/types.ts
index b0932bdb3a..53743edc59 100644
--- a/packages/core-plugin-api/src/icons/types.ts
+++ b/packages/core-plugin-api/src/icons/types.ts
@@ -15,6 +15,7 @@
*/
import { ComponentType } from 'react';
+import { SvgIconProps } from '@material-ui/core';
/**
* IconComponent is the common icon type used throughout Backstage when
@@ -31,3 +32,10 @@ import { ComponentType } from 'react';
export type IconComponent = ComponentType<{
fontSize?: 'default' | 'small' | 'large';
}>;
+
+/**
+ * This exists for backwards compatibility with the old core package.
+ * It's used in some parts of this package in order to smooth out the
+ * migration, but it is not exported.
+ */
+export type OldIconComponent = ComponentType;
diff --git a/packages/core-plugin-api/src/routing/RouteRef.ts b/packages/core-plugin-api/src/routing/RouteRef.ts
index f6499ccc9f..fa4c936bff 100644
--- a/packages/core-plugin-api/src/routing/RouteRef.ts
+++ b/packages/core-plugin-api/src/routing/RouteRef.ts
@@ -21,13 +21,13 @@ import {
ParamKeys,
OptionalParams,
} from './types';
-import { IconComponent } from '../icons/types';
+import { OldIconComponent } from '../icons/types';
// TODO(Rugvip): Remove this in the next breaking release, it's exported but unused
export type RouteRefConfig = {
params?: ParamKeys;
path?: string;
- icon?: IconComponent;
+ icon?: OldIconComponent;
title: string;
};
@@ -40,7 +40,7 @@ export class RouteRefImpl
readonly params: ParamKeys,
private readonly config: {
path?: string;
- icon?: IconComponent;
+ icon?: OldIconComponent;
title?: string;
},
) {}
@@ -79,7 +79,7 @@ export function createRouteRef<
/** @deprecated Route refs no longer decide their own path */
path?: string;
/** @deprecated Route refs no longer decide their own icon */
- icon?: IconComponent;
+ icon?: OldIconComponent;
/** @deprecated Route refs no longer decide their own title */
title?: string;
}): RouteRef> {
diff --git a/packages/core-plugin-api/src/routing/types.ts b/packages/core-plugin-api/src/routing/types.ts
index 0089e29e4b..0e1c817a39 100644
--- a/packages/core-plugin-api/src/routing/types.ts
+++ b/packages/core-plugin-api/src/routing/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { IconComponent } from '../icons/types';
+import { OldIconComponent } from '../icons/types';
import { getOrCreateGlobalSingleton } from '../lib/globalObject';
export type AnyParams = { [param in string]: string } | undefined;
@@ -48,7 +48,7 @@ export type RouteRef = {
/** @deprecated paths are no longer accessed directly from RouteRefs, use useRouteRef instead */
path: string;
/** @deprecated icons are no longer accessed via RouteRefs */
- icon?: IconComponent;
+ icon?: OldIconComponent;
/** @deprecated titles are no longer accessed via RouteRefs */
title?: string;
};
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index 4c7d1ff5a7..d06dbab3d5 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/core
+## 0.7.12
+
+### Patch Changes
+
+- 1cf1d351f: Export `CheckboxTree` as we have a storybook for it
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [0160678b1]
+ - @backstage/theme@0.2.8
+ - @backstage/core-api@0.2.21
+
## 0.7.11
### Patch Changes
diff --git a/packages/core/package.json b/packages/core/package.json
index d759110997..ee7e56741d 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.11",
+ "version": "0.7.12",
"private": false,
"publishConfig": {
"access": "public",
@@ -30,9 +30,9 @@
},
"dependencies": {
"@backstage/config": "^0.1.5",
- "@backstage/core-api": "^0.2.20",
+ "@backstage/core-api": "^0.2.21",
"@backstage/errors": "^0.1.1",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -71,8 +71,8 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md
index e2759e7583..5d8e1c3d71 100644
--- a/packages/create-app/CHANGELOG.md
+++ b/packages/create-app/CHANGELOG.md
@@ -1,5 +1,43 @@
# @backstage/create-app
+## 0.3.24
+
+### Patch Changes
+
+- 1ddf551f4: Add CLI output and README how to start app after create-app CLI
+- Updated dependencies [7af9cef07]
+- Updated dependencies [497f4ce18]
+- Updated dependencies [ee4eb5b40]
+- Updated dependencies [84160313e]
+- Updated dependencies [3772de8ba]
+- Updated dependencies [7e7c71417]
+- Updated dependencies [f430b6c6f]
+- Updated dependencies [2a942cc9e]
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [1cf1d351f]
+- Updated dependencies [90a505a77]
+- Updated dependencies [76f99a1a0]
+- Updated dependencies [1157fa307]
+- Updated dependencies [6fe1567a7]
+- Updated dependencies [e7a5a3474]
+- Updated dependencies [2305ab8fc]
+- Updated dependencies [054bcd029]
+- Updated dependencies [aad98c544]
+- Updated dependencies [63a432e9c]
+- Updated dependencies [f46a9e82d]
+ - @backstage/test-utils@0.1.13
+ - @backstage/plugin-scaffolder@0.9.7
+ - @backstage/cli@0.6.14
+ - @backstage/plugin-catalog@0.6.1
+ - @backstage/theme@0.2.8
+ - @backstage/catalog-model@0.8.1
+ - @backstage/core@0.7.12
+ - @backstage/plugin-tech-radar@0.4.0
+ - @backstage/plugin-scaffolder-backend@0.11.5
+ - @backstage/plugin-catalog-backend@0.10.1
+ - @backstage/plugin-techdocs@0.9.5
+
## 0.3.23
### Patch Changes
diff --git a/packages/create-app/package.json b/packages/create-app/package.json
index e0780ec2c1..a0fb9e6d4a 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.23",
+ "version": "0.3.24",
"private": false,
"publishConfig": {
"access": "public"
diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md
index 6390650897..6f8b164371 100644
--- a/packages/dev-utils/CHANGELOG.md
+++ b/packages/dev-utils/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/dev-utils
+## 0.1.17
+
+### Patch Changes
+
+- e7c5e4b30: Update installation instructions in README.
+- Updated dependencies [7af9cef07]
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [1cf1d351f]
+- Updated dependencies [deaba2e13]
+- Updated dependencies [8e919a6f8]
+ - @backstage/test-utils@0.1.13
+ - @backstage/theme@0.2.8
+ - @backstage/catalog-model@0.8.1
+ - @backstage/core@0.7.12
+ - @backstage/plugin-catalog-react@0.2.1
+
## 0.1.16
### Patch Changes
diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json
index 5812543a8b..91c34c8ef2 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.16",
+ "version": "0.1.17",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,12 +29,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core": "^0.7.11",
- "@backstage/catalog-model": "^0.8.0",
+ "@backstage/core": "^0.7.12",
+ "@backstage/catalog-model": "^0.8.1",
"@backstage/integration-react": "^0.1.2",
- "@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/test-utils": "^0.1.12",
- "@backstage/theme": "^0.2.3",
+ "@backstage/plugin-catalog-react": "^0.2.1",
+ "@backstage/test-utils": "^0.1.13",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@testing-library/jest-dom": "^5.10.1",
@@ -48,7 +48,7 @@
"react-router-dom": "6.0.0-beta.0"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
+ "@backstage/cli": "^0.6.14",
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32"
},
diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md
index 9c1f25ded3..14b1d75441 100644
--- a/packages/integration/CHANGELOG.md
+++ b/packages/integration/CHANGELOG.md
@@ -1,5 +1,11 @@
# @backstage/integration
+## 0.5.5
+
+### Patch Changes
+
+- 49d7ec169: GitHub App ID can be a string too for environment variables otherwise it will fail validation
+
## 0.5.4
### Patch Changes
diff --git a/packages/integration/package.json b/packages/integration/package.json
index ab4a2055dd..1220493eea 100644
--- a/packages/integration/package.json
+++ b/packages/integration/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/integration",
- "version": "0.5.4",
+ "version": "0.5.5",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -37,9 +37,9 @@
"luxon": "^1.25.0"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
+ "@backstage/cli": "^0.6.14",
"@backstage/config-loader": "^0.6.3",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/test-utils": "^0.1.13",
"@types/jest": "^26.0.7",
"@types/luxon": "^1.25.0",
"msw": "^0.21.2"
diff --git a/packages/search-common/api-report.md b/packages/search-common/api-report.md
index 76bd937a1c..a3de27b5a9 100644
--- a/packages/search-common/api-report.md
+++ b/packages/search-common/api-report.md
@@ -10,12 +10,14 @@ import { JsonObject } from '@backstage/config';
export interface DocumentCollator {
// (undocumented)
execute(): Promise;
+ readonly type: string;
}
// @public
export interface DocumentDecorator {
// (undocumented)
execute(documents: IndexableDocument[]): Promise;
+ readonly types?: string[];
}
// @public
@@ -41,6 +43,8 @@ export interface SearchQuery {
export interface SearchResult {
// (undocumented)
document: IndexableDocument;
+ // (undocumented)
+ type: string;
}
// @public (undocumented)
diff --git a/packages/search-common/src/types.ts b/packages/search-common/src/types.ts
index daa5823424..2e7ca97edc 100644
--- a/packages/search-common/src/types.ts
+++ b/packages/search-common/src/types.ts
@@ -23,6 +23,7 @@ export interface SearchQuery {
}
export interface SearchResult {
+ type: string;
document: IndexableDocument;
}
@@ -57,6 +58,11 @@ export interface IndexableDocument {
* search.
*/
export interface DocumentCollator {
+ /**
+ * The type or name of the document set returned by this collator. Used as an
+ * index name by Search Engines.
+ */
+ readonly type: string;
execute(): Promise;
}
@@ -65,5 +71,11 @@ export interface DocumentCollator {
* additional metadata.
*/
export interface DocumentDecorator {
+ /**
+ * An optional array of document/index types on which this decorator should
+ * be applied. If no types are provided, this decorator will be applied to
+ * all document/index types.
+ */
+ readonly types?: string[];
execute(documents: IndexableDocument[]): Promise;
}
diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md
index 0f45c7cebe..3d38612851 100644
--- a/packages/techdocs-common/CHANGELOG.md
+++ b/packages/techdocs-common/CHANGELOG.md
@@ -1,5 +1,17 @@
# @backstage/techdocs-common
+## 0.6.4
+
+### Patch Changes
+
+- aad98c544: Fixes multiple XSS and sanitization bypass vulnerabilities in TechDocs.
+- 090594755: Support parsing `mkdocs.yml` files that are using custom yaml tags like
+ `!!python/name:materialx.emoji.twemoji`.
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [49d7ec169]
+ - @backstage/catalog-model@0.8.1
+ - @backstage/integration@0.5.5
+
## 0.6.3
### Patch Changes
diff --git a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts
index 5e9890cd98..684c4023d7 100644
--- a/packages/techdocs-common/__mocks__/@google-cloud/storage.ts
+++ b/packages/techdocs-common/__mocks__/@google-cloud/storage.ts
@@ -63,6 +63,9 @@ class GCSFile {
process.nextTick(() => {
if (fs.existsSync(this.localFilePath)) {
+ if (readable.eventNames().includes('pipe')) {
+ readable.emit('pipe');
+ }
readable.emit('data', fs.readFileSync(this.localFilePath));
readable.emit('end');
} else {
diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json
index 0e7b2efd3d..d1ec21060b 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.3",
+ "version": "0.6.4",
"main": "src/index.ts",
"types": "src/index.ts",
"private": false,
@@ -39,10 +39,10 @@
"@azure/identity": "^1.2.2",
"@azure/storage-blob": "^12.4.0",
"@backstage/backend-common": "^0.8.1",
- "@backstage/catalog-model": "^0.8.0",
+ "@backstage/catalog-model": "^0.8.1",
"@backstage/config": "^0.1.5",
"@backstage/errors": "^0.1.1",
- "@backstage/integration": "^0.5.4",
+ "@backstage/integration": "^0.5.5",
"@google-cloud/storage": "^5.6.0",
"@types/express": "^4.17.6",
"aws-sdk": "^2.840.0",
@@ -60,7 +60,7 @@
"winston": "^3.2.1"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
+ "@backstage/cli": "^0.6.14",
"@types/fs-extra": "^9.0.5",
"@types/git-url-parse": "^9.0.0",
"@types/js-yaml": "^4.0.0",
diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts
index b2954f7b2e..d4fb0d2dc2 100644
--- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts
+++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts
@@ -286,8 +286,12 @@ describe('AwsS3Publish', () => {
mockFs.restore();
mockFs({
[entityRootDir]: {
+ html: {
+ 'unsafe.html': '',
+ },
img: {
'with spaces.png': 'found it',
+ 'unsafe.svg': '',
},
'some folder': {
'also with spaces.js': 'found it too',
@@ -318,5 +322,28 @@ describe('AwsS3Publish', () => {
);
expect(jsResponse.text).toEqual('found it too');
});
+
+ it('should pass text/plain content-type for html', async () => {
+ const {
+ kind,
+ metadata: { namespace, name },
+ } = entity;
+
+ const htmlResponse = await request(app).get(
+ `/${namespace}/${kind}/${name}/html/unsafe.html`,
+ );
+ expect(htmlResponse.text).toEqual('');
+ expect(htmlResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+
+ const svgResponse = await request(app).get(
+ `/${namespace}/${kind}/${name}/img/unsafe.svg`,
+ );
+ expect(svgResponse.text).toEqual('');
+ expect(svgResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+ });
});
});
diff --git a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts
index 568393662b..0bf720ae31 100644
--- a/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts
+++ b/packages/techdocs-common/src/stages/publish/azureBlobStorage.test.ts
@@ -343,8 +343,12 @@ describe('publishing with valid credentials', () => {
mockFs.restore();
mockFs({
[entityRootDir]: {
+ html: {
+ 'unsafe.html': '',
+ },
img: {
'with spaces.png': 'found it',
+ 'unsafe.svg': '',
},
'some folder': {
'also with spaces.js': 'found it too',
@@ -375,5 +379,28 @@ describe('publishing with valid credentials', () => {
);
expect(jsResponse.text).toEqual('found it too');
});
+
+ it('should pass text/plain content-type for html', async () => {
+ const {
+ kind,
+ metadata: { namespace, name },
+ } = entity;
+
+ const htmlResponse = await request(app).get(
+ `/${namespace}/${kind}/${name}/html/unsafe.html`,
+ );
+ expect(htmlResponse.text).toEqual('');
+ expect(htmlResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+
+ const svgResponse = await request(app).get(
+ `/${namespace}/${kind}/${name}/img/unsafe.svg`,
+ );
+ expect(svgResponse.text).toEqual('');
+ expect(svgResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+ });
});
});
diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts
index f29e1f10be..4db1d83c7e 100644
--- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts
+++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts
@@ -285,8 +285,12 @@ describe('GoogleGCSPublish', () => {
mockFs.restore();
mockFs({
[entityRootDir]: {
+ html: {
+ 'unsafe.html': '',
+ },
img: {
'with spaces.png': 'found it',
+ 'unsafe.svg': '',
},
'some folder': {
'also with spaces.js': 'found it too',
@@ -309,11 +313,36 @@ describe('GoogleGCSPublish', () => {
const pngResponse = await request(app).get(
`/${namespace}/${kind}/${name}/img/with%20spaces.png`,
);
- expect(pngResponse.text).toEqual('found it');
+ expect(Buffer.from(pngResponse.body).toString('utf8')).toEqual(
+ 'found it',
+ );
const jsResponse = await request(app).get(
`/${namespace}/${kind}/${name}/some%20folder/also%20with%20spaces.js`,
);
expect(jsResponse.text).toEqual('found it too');
});
+
+ it('should pass text/plain content-type for html', async () => {
+ const {
+ kind,
+ metadata: { namespace, name },
+ } = entity;
+
+ const htmlResponse = await request(app).get(
+ `/${namespace}/${kind}/${name}/html/unsafe.html`,
+ );
+ expect(htmlResponse.text).toEqual('');
+ expect(htmlResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+
+ const svgResponse = await request(app).get(
+ `/${namespace}/${kind}/${name}/img/unsafe.svg`,
+ );
+ expect(svgResponse.text).toEqual('');
+ expect(svgResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+ });
});
});
diff --git a/packages/techdocs-common/src/stages/publish/helpers.test.ts b/packages/techdocs-common/src/stages/publish/helpers.test.ts
index 1fae66f3d2..7eb591bdb2 100644
--- a/packages/techdocs-common/src/stages/publish/helpers.test.ts
+++ b/packages/techdocs-common/src/stages/publish/helpers.test.ts
@@ -20,12 +20,18 @@ import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers';
describe('getHeadersForFileExtension', () => {
const correctMapOfExtensions = [
- ['.html', 'text/html; charset=utf-8'],
+ ['.html', 'text/plain; charset=utf-8'],
+ ['.htm', 'text/plain; charset=utf-8'],
+ ['.HTML', 'text/plain; charset=utf-8'],
+ ['.dhtml', 'text/plain; charset=utf-8'],
+ ['.xhtml', 'text/plain; charset=utf-8'],
+ ['.xml', 'text/plain; charset=utf-8'],
['.css', 'text/css; charset=utf-8'],
['.png', 'image/png'],
['.jpg', 'image/jpeg'],
['.jpeg', 'image/jpeg'],
- ['.svg', 'image/svg+xml'],
+ ['.svg', 'text/plain; charset=utf-8'],
+ ['.SVG', 'text/plain; charset=utf-8'],
['.json', 'application/json; charset=utf-8'],
['.this-in-not-an-extension', 'text/plain; charset=utf-8'],
];
diff --git a/packages/techdocs-common/src/stages/publish/helpers.ts b/packages/techdocs-common/src/stages/publish/helpers.ts
index 138ec611e0..22da949aec 100644
--- a/packages/techdocs-common/src/stages/publish/helpers.ts
+++ b/packages/techdocs-common/src/stages/publish/helpers.ts
@@ -16,6 +16,22 @@
import mime from 'mime-types';
import recursiveReadDir from 'recursive-readdir';
+/**
+ * Helper to get the expected content-type for a given file extension. Also
+ * takes XSS mitigation into account.
+ */
+const getContentTypeForExtension = (ext: string): string => {
+ const defaultContentType = 'text/plain; charset=utf-8';
+
+ // Prevent sanitization bypass by preventing browsers from directly rendering
+ // the contents of untrusted files.
+ if (ext.match(/htm|xml|svg/i)) {
+ return defaultContentType;
+ }
+
+ return mime.contentType(ext) || defaultContentType;
+};
+
export type responseHeadersType = {
'Content-Type': string;
};
@@ -29,9 +45,8 @@ export const getHeadersForFileExtension = (
fileExtension: string,
): responseHeadersType => {
return {
- 'Content-Type':
- mime.contentType(fileExtension) || 'text/plain; charset=utf-8',
- } as responseHeadersType;
+ 'Content-Type': getContentTypeForExtension(fileExtension),
+ };
};
/**
diff --git a/packages/techdocs-common/src/stages/publish/local.test.ts b/packages/techdocs-common/src/stages/publish/local.test.ts
index cd1a17ab5f..114401f1db 100644
--- a/packages/techdocs-common/src/stages/publish/local.test.ts
+++ b/packages/techdocs-common/src/stages/publish/local.test.ts
@@ -16,8 +16,11 @@
import {
getVoidLogger,
PluginEndpointDiscovery,
+ resolvePackagePath,
} from '@backstage/backend-common';
import { ConfigReader } from '@backstage/config';
+import express from 'express';
+import request from 'supertest';
import mockFs from 'mock-fs';
import * as os from 'os';
import { LocalPublish } from './local';
@@ -35,11 +38,21 @@ const createMockEntity = (annotations = {}) => {
};
};
+const testDiscovery: jest.Mocked = {
+ getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7000/api/techdocs'),
+ getExternalBaseUrl: jest.fn(),
+};
+
const logger = getVoidLogger();
const tmpDir =
os.platform() === 'win32' ? 'C:\\tmp\\generatedDir' : '/tmp/generatedDir';
+const resolvedDir = resolvePackagePath(
+ '@backstage/plugin-techdocs-backend',
+ 'static/docs',
+);
+
describe('local publisher', () => {
it('should publish generated documentation dir', async () => {
mockFs({
@@ -48,13 +61,6 @@ describe('local publisher', () => {
},
});
- const testDiscovery: jest.Mocked = {
- getBaseUrl: jest
- .fn()
- .mockResolvedValue('http://localhost:7000/api/techdocs'),
- getExternalBaseUrl: jest.fn(),
- };
-
const mockConfig = new ConfigReader({});
const publisher = new LocalPublish(mockConfig, logger, testDiscovery);
@@ -66,4 +72,40 @@ describe('local publisher', () => {
mockFs.restore();
});
+
+ describe('docsRouter', () => {
+ const mockConfig = new ConfigReader({});
+ const publisher = new LocalPublish(mockConfig, logger, testDiscovery);
+ let app: express.Express;
+
+ beforeEach(() => {
+ app = express().use(publisher.docsRouter());
+
+ mockFs.restore();
+ mockFs({
+ [resolvedDir]: {
+ 'unsafe.html': '',
+ 'unsafe.svg': '',
+ },
+ });
+ });
+
+ afterEach(() => {
+ mockFs.restore();
+ });
+
+ it('should pass text/plain content-type for unsafe types', async () => {
+ const htmlResponse = await request(app).get(`/unsafe.html`);
+ expect(htmlResponse.text).toEqual('');
+ expect(htmlResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+
+ const svgResponse = await request(app).get(`/unsafe.svg`);
+ expect(svgResponse.text).toEqual('');
+ expect(svgResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+ });
+ });
});
diff --git a/packages/techdocs-common/src/stages/publish/local.ts b/packages/techdocs-common/src/stages/publish/local.ts
index e09473cf60..bf49c5b926 100644
--- a/packages/techdocs-common/src/stages/publish/local.ts
+++ b/packages/techdocs-common/src/stages/publish/local.ts
@@ -31,6 +31,7 @@ import {
ReadinessResponse,
TechDocsMetadata,
} from './types';
+import { getHeadersForFileExtension } from './helpers';
// TODO: Use a more persistent storage than node_modules or /tmp directory.
// Make it configurable with techdocs.publisher.local.publishDirectory
@@ -132,7 +133,16 @@ export class LocalPublish implements PublisherBase {
}
docsRouter(): express.Handler {
- return express.static(staticDocsDir);
+ return express.static(staticDocsDir, {
+ // Handle content-type header the same as all other publishers.
+ setHeaders: (res, filePath) => {
+ const fileExtension = path.extname(filePath);
+ const headers = getHeadersForFileExtension(fileExtension);
+ for (const [header, value] of Object.entries(headers)) {
+ res.setHeader(header, value);
+ }
+ },
+ });
}
async hasDocsBeenGenerated(entity: Entity): Promise {
diff --git a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts
index aeaceeaaa6..dfac7fb0f7 100644
--- a/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts
+++ b/packages/techdocs-common/src/stages/publish/openStackSwift.test.ts
@@ -291,7 +291,11 @@ describe('OpenStackSwiftPublish', () => {
mockFs.restore();
mockFs({
[entityRootDir]: {
+ html: {
+ 'unsafe.html': '',
+ },
img: {
+ 'unsafe.svg': '',
'with spaces.png': 'found it',
},
'some folder': {
@@ -323,5 +327,28 @@ describe('OpenStackSwiftPublish', () => {
);
expect(jsResponse.text).toEqual('found it too');
});
+
+ it('should pass text/plain content-type for unsafe types', async () => {
+ const {
+ kind,
+ metadata: { namespace, name },
+ } = entity;
+
+ const htmlResponse = await request(app).get(
+ `/${namespace}/${kind}/${name}/html/unsafe.html`,
+ );
+ expect(htmlResponse.text).toEqual('');
+ expect(htmlResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+
+ const svgResponse = await request(app).get(
+ `/${namespace}/${kind}/${name}/img/unsafe.svg`,
+ );
+ expect(svgResponse.text).toEqual('');
+ expect(svgResponse.header).toMatchObject({
+ 'content-type': 'text/plain; charset=utf-8',
+ });
+ });
});
});
diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md
index 7fe7efe490..6ff92e79ae 100644
--- a/packages/test-utils/CHANGELOG.md
+++ b/packages/test-utils/CHANGELOG.md
@@ -1,5 +1,16 @@
# @backstage/test-utils
+## 0.1.13
+
+### Patch Changes
+
+- 7af9cef07: Fix a bug in `MockStorageApi` where it unhelpfully returned new empty buckets every single time
+- e7c5e4b30: Update installation instructions in README.
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [0160678b1]
+ - @backstage/theme@0.2.8
+ - @backstage/core-api@0.2.21
+
## 0.1.12
### Patch Changes
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index 429d616572..8f906965b4 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.12",
+ "version": "0.1.13",
"private": false,
"publishConfig": {
"access": "public",
@@ -29,9 +29,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core-api": "^0.2.19",
+ "@backstage/core-api": "^0.2.21",
"@backstage/test-utils-core": "^0.1.1",
- "@backstage/theme": "^0.2.3",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
@@ -45,7 +45,7 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.12",
+ "@backstage/cli": "^0.6.14",
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32"
},
diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md
index e70364016d..e19e044977 100644
--- a/packages/theme/CHANGELOG.md
+++ b/packages/theme/CHANGELOG.md
@@ -1,5 +1,11 @@
# @backstage/theme
+## 0.2.8
+
+### Patch Changes
+
+- e7c5e4b30: Update installation instructions in README.
+
## 0.2.7
### Patch Changes
diff --git a/packages/theme/package.json b/packages/theme/package.json
index f93c793ff8..2575af6c97 100644
--- a/packages/theme/package.json
+++ b/packages/theme/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/theme",
"description": "material-ui theme for use with Backstage.",
- "version": "0.2.7",
+ "version": "0.2.8",
"private": false,
"publishConfig": {
"access": "public",
@@ -31,7 +31,7 @@
"@material-ui/core": "^4.11.0"
},
"devDependencies": {
- "@backstage/cli": "^0.6.10"
+ "@backstage/cli": "^0.6.14"
},
"files": [
"dist"
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index a64553b4fc..54588303e1 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -33,7 +33,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-icons/font": "^1.0.2",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -49,9 +49,9 @@
"swagger-ui-react": "^3.37.2"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/badges/package.json b/plugins/badges/package.json
index 56896fb379..23c914c9ab 100644
--- a/plugins/badges/package.json
+++ b/plugins/badges/package.json
@@ -24,7 +24,7 @@
"@backstage/core": "^0.7.11",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -34,9 +34,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json
index 5181bd106b..8f3fa016c6 100644
--- a/plugins/bitrise/package.json
+++ b/plugins/bitrise/package.json
@@ -23,7 +23,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -37,9 +37,9 @@
"recharts": "^1.8.5"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md
index 1fef6c7143..fe0a72a902 100644
--- a/plugins/catalog-backend/CHANGELOG.md
+++ b/plugins/catalog-backend/CHANGELOG.md
@@ -1,5 +1,18 @@
# @backstage/plugin-catalog-backend
+## 0.10.1
+
+### Patch Changes
+
+- e7a5a3474: Only validate the envelope for emitted entities, and defer full validation to when they get processed later on.
+- 63a432e9c: Skip deletion of bootstrap location when running the new catalog.
+- f46a9e82d: Move dependency to `@microsoft/microsoft-graph-types` from `@backstage/plugin-catalog`
+ to `@backstage/plugin-catalog-backend`.
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [49d7ec169]
+ - @backstage/catalog-model@0.8.1
+ - @backstage/integration@0.5.5
+
## 0.10.0
### Minor Changes
diff --git a/plugins/catalog-backend/migrationsv2/20210302150147_refresh_state.js b/plugins/catalog-backend/migrationsv2/20210302150147_refresh_state.js
index 2258017965..4e6cf4f282 100644
--- a/plugins/catalog-backend/migrationsv2/20210302150147_refresh_state.js
+++ b/plugins/catalog-backend/migrationsv2/20210302150147_refresh_state.js
@@ -191,11 +191,6 @@ exports.up = async function up(knex) {
table.index(['key'], 'search_key_idx');
table.index(['value'], 'search_value_idx');
});
-
- // Delete bootstrap location which is no longer required.
- await knex('locations')
- .where({ type: 'bootstrap', target: 'bootstrap' })
- .delete();
};
/**
diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json
index cacb8839c2..ca9c95409c 100644
--- a/plugins/catalog-backend/package.json
+++ b/plugins/catalog-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-backend",
- "version": "0.10.0",
+ "version": "0.10.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,10 +32,10 @@
"@azure/msal-node": "^1.0.0-beta.3",
"@backstage/backend-common": "^0.8.1",
"@backstage/catalog-client": "^0.3.12",
- "@backstage/catalog-model": "^0.8.0",
+ "@backstage/catalog-model": "^0.8.1",
"@backstage/config": "^0.1.5",
"@backstage/errors": "^0.1.1",
- "@backstage/integration": "^0.5.4",
+ "@backstage/integration": "^0.5.5",
"@backstage/plugin-search-backend-node": "^0.1.4",
"@backstage/search-common": "^0.1.1",
"@microsoft/microsoft-graph-types": "^1.25.0",
@@ -65,8 +65,8 @@
"yup": "^0.29.3"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/test-utils": "^0.1.13",
"@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/next/DefaultCatalogProcessingEngine.test.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts
index 376af6e748..0532342c5d 100644
--- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts
+++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.test.ts
@@ -15,12 +15,11 @@
*/
import { getVoidLogger } from '@backstage/backend-common';
-import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase';
-
-import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine';
-import { Stitcher } from './Stitcher';
-import { CatalogProcessingOrchestrator } from './types';
import waitForExpect from 'wait-for-expect';
+import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase';
+import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine';
+import { CatalogProcessingOrchestrator } from './processing/types';
+import { Stitcher } from './stitching/Stitcher';
describe('DefaultCatalogProcessingEngine', () => {
const db = ({
diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts
index 4dda881963..5de012e936 100644
--- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts
+++ b/plugins/catalog-backend/src/next/DefaultCatalogProcessingEngine.ts
@@ -14,20 +14,26 @@
* limitations under the License.
*/
-import { stringifyEntityRef } from '@backstage/catalog-model';
+import {
+ Entity,
+ entityEnvelopeSchemaValidator,
+ stringifyEntityRef,
+} from '@backstage/catalog-model';
import { serializeError } from '@backstage/errors';
import { Logger } from 'winston';
import { ProcessingDatabase } from './database/types';
-import { Stitcher } from './Stitcher';
+import { CatalogProcessingOrchestrator } from './processing/types';
+import { Stitcher } from './stitching/Stitcher';
import {
CatalogProcessingEngine,
- CatalogProcessingOrchestrator,
EntityProvider,
EntityProviderConnection,
EntityProviderMutation,
} from './types';
class Connection implements EntityProviderConnection {
+ readonly validateEntityEnvelope = entityEnvelopeSchemaValidator();
+
constructor(
private readonly config: {
processingDatabase: ProcessingDatabase;
@@ -37,7 +43,9 @@ class Connection implements EntityProviderConnection {
async applyMutation(mutation: EntityProviderMutation): Promise {
const db = this.config.processingDatabase;
+
if (mutation.type === 'full') {
+ this.check(mutation.entities);
await db.transaction(async tx => {
await db.replaceUnprocessedEntities(tx, {
sourceKey: this.config.id,
@@ -47,6 +55,9 @@ class Connection implements EntityProviderConnection {
});
return;
}
+
+ this.check(mutation.added);
+ this.check(mutation.removed);
await db.transaction(async tx => {
await db.replaceUnprocessedEntities(tx, {
sourceKey: this.config.id,
@@ -56,6 +67,16 @@ class Connection implements EntityProviderConnection {
});
});
}
+
+ private check(entities: Entity[]) {
+ for (const entity of entities) {
+ try {
+ this.validateEntityEnvelope(entity);
+ } catch (e) {
+ throw new TypeError(`Malformed entity envelope, ${e}`);
+ }
+ }
+ }
}
export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
@@ -113,14 +134,18 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine {
// TODO: replace Promise.all with something more sophisticated for parallel processing.
await Promise.all(
items.map(async item => {
- const { id, state, unprocessedEntity } = item;
+ const { id, state, unprocessedEntity, entityRef } = item;
const result = await this.orchestrator.process({
entity: unprocessedEntity,
state,
});
for (const error of result.errors) {
- this.logger.warn(error.message);
+ // TODO(freben): Try to extract the location out of the unprocessed
+ // entity and add as meta to the log lines
+ this.logger.warn(error.message, {
+ entity: entityRef,
+ });
}
const errorsString = JSON.stringify(
result.errors.map(e => serializeError(e)),
diff --git a/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts
deleted file mode 100644
index c2c001943c..0000000000
--- a/plugins/catalog-backend/src/next/DefaultCatalogProcessingOrchestrator.ts
+++ /dev/null
@@ -1,318 +0,0 @@
-/*
- * 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,
- EntityRelationSpec,
- stringifyEntityRef,
- LOCATION_ANNOTATION,
- LocationSpec,
- LocationEntity,
- EntityPolicy,
- ORIGIN_LOCATION_ANNOTATION,
- stringifyLocationReference,
- parseLocationReference,
-} from '@backstage/catalog-model';
-import {
- CatalogProcessor,
- CatalogProcessorParser,
- CatalogProcessorResult,
-} from '../ingestion/processors';
-import {
- CatalogProcessingOrchestrator,
- EntityProcessingRequest,
- EntityProcessingResult,
-} from './types';
-import { Logger } from 'winston';
-import { InputError } from '@backstage/errors';
-import { locationSpecToLocationEntity } from './util';
-import path from 'path';
-import * as results from '../ingestion/processors/results';
-import { ScmIntegrationRegistry } from '@backstage/integration';
-
-function isLocationEntity(entity: Entity): entity is LocationEntity {
- return entity.kind === 'Location';
-}
-
-function getEntityOriginLocationRef(entity: Entity): string {
- const ref = entity.metadata.annotations?.[ORIGIN_LOCATION_ANNOTATION];
- if (!ref) {
- const entityRef = stringifyEntityRef(entity);
- throw new InputError(
- `Entity '${entityRef}' does not have an origin location`,
- );
- }
- return ref;
-}
-
-function toAbsoluteUrl(
- integrations: ScmIntegrationRegistry,
- base: LocationSpec,
- type: string,
- target: string,
-): string {
- if (base.type !== type) {
- return target;
- }
- try {
- if (type === 'file') {
- if (target.startsWith('.')) {
- return path.join(path.dirname(base.target), target);
- }
- return target;
- } else if (type === 'url') {
- return integrations.resolveUrl({ url: target, base: base.target });
- }
- return target;
- } catch (e) {
- return target;
- }
-}
-
-export class DefaultCatalogProcessingOrchestrator
- implements CatalogProcessingOrchestrator {
- constructor(
- private readonly options: {
- processors: CatalogProcessor[];
- integrations: ScmIntegrationRegistry;
- logger: Logger;
- parser: CatalogProcessorParser;
- policy: EntityPolicy;
- },
- ) {}
-
- async process(
- request: EntityProcessingRequest,
- ): Promise {
- // TODO: implement dryRun/eager
- return this.processSingleEntity(request.entity);
- }
-
- private async processSingleEntity(
- unprocessedEntity: Entity,
- ): Promise {
- // TODO: validate that this doesn't change during processing
- const entityRef = stringifyEntityRef(unprocessedEntity);
- // TODO: which one do we actually use here? source-location? - maybe probably doesn't exist yet?
- const locationRef =
- unprocessedEntity.metadata?.annotations?.[LOCATION_ANNOTATION];
- if (!locationRef) {
- throw new InputError(`Entity '${entityRef}' does not have a location`);
- }
- const location = parseLocationReference(locationRef);
- const originLocation = parseLocationReference(
- getEntityOriginLocationRef(unprocessedEntity),
- );
-
- const emitter = createEmitter(this.options.logger, unprocessedEntity);
- try {
- // Pre-process phase, used to populate entities with data that is required during main processing step
- let entity = unprocessedEntity;
- for (const processor of this.options.processors) {
- if (processor.preProcessEntity) {
- try {
- entity = await processor.preProcessEntity(
- entity,
- location,
- emitter.emit,
- originLocation,
- );
- } catch (e) {
- throw new Error(
- `Processor ${processor.constructor.name} threw an error while preprocessing entity ${entityRef} at ${locationRef}, ${e}`,
- );
- }
- }
- }
-
- // Enforce entity policies making sure that entities conform to a general schema
- let policyEnforcedEntity;
- try {
- policyEnforcedEntity = await this.options.policy.enforce(entity);
- } catch (e) {
- throw new InputError(
- `Policy check failed while analyzing entity ${entityRef} at ${locationRef}, ${e}`,
- );
- }
- if (!policyEnforcedEntity) {
- throw new Error(
- `Policy unexpectedly returned no data while analyzing entity ${entityRef} at ${locationRef}`,
- );
- }
- entity = policyEnforcedEntity;
-
- // Validate the given entity kind against its schema
- let handled = false;
- for (const processor of this.options.processors) {
- if (processor.validateEntityKind) {
- try {
- handled = await processor.validateEntityKind(entity);
- if (handled) {
- break;
- }
- } catch (e) {
- throw new InputError(
- `Processor ${processor.constructor.name} threw an error while validating the entity ${entityRef} at ${locationRef}, ${e}`,
- );
- }
- }
- }
- if (!handled) {
- throw new InputError(
- `No processor recognized the entity ${entityRef} at ${locationRef}`,
- );
- }
-
- // Backwards compatible processing of location entities
- if (isLocationEntity(entity)) {
- const { type = location.type } = entity.spec;
- const targets = new Array();
- if (entity.spec.target) {
- targets.push(entity.spec.target);
- }
- if (entity.spec.targets) {
- targets.push(...entity.spec.targets);
- }
-
- for (const maybeRelativeTarget of targets) {
- if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) {
- emitter.emit(
- results.inputError(
- location,
- `LocationEntityProcessor cannot handle ${type} type location with target ${location.target} that ends with a path separator`,
- ),
- );
- continue;
- }
- const target = toAbsoluteUrl(
- this.options.integrations,
- location,
- type,
- maybeRelativeTarget,
- );
-
- for (const processor of this.options.processors) {
- if (processor.readLocation) {
- try {
- const read = await processor.readLocation(
- {
- type,
- target,
- presence: 'required',
- },
- false,
- emitter.emit,
- this.options.parser,
- );
- if (read) {
- break;
- }
- } catch (e) {
- throw new Error(
- `Processor ${processor.constructor.name} threw an error while postprocessing entity ${entityRef} at ${locationRef}, ${e}`,
- );
- }
- }
- }
- }
- }
-
- // Main processing step of the entity
- for (const processor of this.options.processors) {
- if (processor.postProcessEntity) {
- try {
- entity = await processor.postProcessEntity(
- entity,
- location,
- emitter.emit,
- );
- } catch (e) {
- throw new Error(
- `Processor ${processor.constructor.name} threw an error while postprocessing entity ${entityRef} at ${locationRef}, ${e}`,
- );
- }
- }
- }
-
- return {
- ...emitter.results(),
- completedEntity: entity,
- state: new Map(),
- ok: true,
- };
- } catch (error) {
- this.options.logger.warn(error.message);
- return { ok: false, errors: emitter.results().errors.concat(error) };
- }
- }
-}
-
-function createEmitter(logger: Logger, parentEntity: Entity) {
- let done = false;
-
- const errors = new Array();
- const relations = new Array();
- const deferredEntities = new Array();
-
- const emit = (i: CatalogProcessorResult) => {
- if (done) {
- logger.warn(
- `Item if type ${i.type} was emitted after processing had completed at ${
- new Error().stack
- }`,
- );
- return;
- }
- if (i.type === 'entity') {
- // TODO(freben): Perform the most basic validation here
- // (apiVersion, kind, metadata, metadata.name, metadata.namespace, spec)
-
- const originLocation = getEntityOriginLocationRef(parentEntity);
-
- deferredEntities.push({
- ...i.entity,
- metadata: {
- ...i.entity.metadata,
- annotations: {
- ...i.entity.metadata.annotations,
- [ORIGIN_LOCATION_ANNOTATION]: originLocation,
- [LOCATION_ANNOTATION]: stringifyLocationReference(i.location),
- },
- },
- });
- } else if (i.type === 'location') {
- deferredEntities.push(
- locationSpecToLocationEntity(i.location, parentEntity),
- );
- } else if (i.type === 'relation') {
- relations.push(i.relation);
- } else if (i.type === 'error') {
- errors.push(i.error);
- }
- };
-
- return {
- emit,
- results() {
- done = true;
- return {
- errors,
- relations,
- deferredEntities,
- };
- },
- };
-}
diff --git a/plugins/catalog-backend/src/next/DefaultLocationService.test.ts b/plugins/catalog-backend/src/next/DefaultLocationService.test.ts
index 662be5d3a6..2958028c5d 100644
--- a/plugins/catalog-backend/src/next/DefaultLocationService.test.ts
+++ b/plugins/catalog-backend/src/next/DefaultLocationService.test.ts
@@ -15,7 +15,8 @@
*/
import { DefaultLocationService } from './DefaultLocationService';
-import { CatalogProcessingOrchestrator, LocationStore } from './types';
+import { CatalogProcessingOrchestrator } from './processing/types';
+import { LocationStore } from './types';
describe('DefaultLocationServiceTest', () => {
const orchestrator: jest.Mocked = {
@@ -27,9 +28,12 @@ describe('DefaultLocationServiceTest', () => {
listLocations: jest.fn(),
getLocation: jest.fn(),
};
-
- beforeEach(() => jest.resetAllMocks());
const locationService = new DefaultLocationService(store, orchestrator);
+
+ beforeEach(() => {
+ jest.resetAllMocks();
+ });
+
describe('createLocation', () => {
it('should support dry run', async () => {
orchestrator.process.mockResolvedValueOnce({
@@ -136,6 +140,7 @@ describe('DefaultLocationServiceTest', () => {
});
});
});
+
describe('listLocations', () => {
it('should call locationStore.deleteLocation', async () => {
await locationService.listLocations();
diff --git a/plugins/catalog-backend/src/next/DefaultLocationService.ts b/plugins/catalog-backend/src/next/DefaultLocationService.ts
index e628b1d77d..9cc60682de 100644
--- a/plugins/catalog-backend/src/next/DefaultLocationService.ts
+++ b/plugins/catalog-backend/src/next/DefaultLocationService.ts
@@ -14,17 +14,14 @@
* limitations under the License.
*/
import {
- LocationSpec,
- Location,
Entity,
+ Location,
+ LocationSpec,
LOCATION_ANNOTATION,
ORIGIN_LOCATION_ANNOTATION,
} from '@backstage/catalog-model';
-import {
- LocationService,
- LocationStore,
- CatalogProcessingOrchestrator,
-} from './types';
+import { CatalogProcessingOrchestrator } from './processing/types';
+import { LocationService, LocationStore } from './types';
import { locationSpecToMetadataName } from './util';
export class DefaultLocationService implements LocationService {
diff --git a/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts b/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts
index 88806d0ace..382f0ccaf5 100644
--- a/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts
+++ b/plugins/catalog-backend/src/next/DefaultLocationStore.test.ts
@@ -23,7 +23,6 @@ const createLocationStore = async () => {
const connection = { applyMutation: jest.fn() };
const store = new DefaultLocationStore(knex);
await store.connect(connection);
-
return { store, connection };
};
diff --git a/plugins/catalog-backend/src/next/DefaultLocationStore.ts b/plugins/catalog-backend/src/next/DefaultLocationStore.ts
index fe1eb30467..7421b24782 100644
--- a/plugins/catalog-backend/src/next/DefaultLocationStore.ts
+++ b/plugins/catalog-backend/src/next/DefaultLocationStore.ts
@@ -14,22 +14,17 @@
* limitations under the License.
*/
-import { LocationSpec, Location } from '@backstage/catalog-model';
-import {
- LocationStore,
- EntityProvider,
- EntityProviderConnection,
-} from './types';
-import { v4 as uuid } from 'uuid';
-import { locationSpecToLocationEntity } from './util';
+import { Location, LocationSpec } from '@backstage/catalog-model';
import { ConflictError, NotFoundError } from '@backstage/errors';
import { Knex } from 'knex';
-
-type DbLocationsRow = {
- id: string;
- type: string;
- target: string;
-};
+import { v4 as uuid } from 'uuid';
+import { DbLocationsRow } from './database/tables';
+import {
+ EntityProvider,
+ EntityProviderConnection,
+ LocationStore,
+} from './types';
+import { locationSpecToLocationEntity } from './util';
export class DefaultLocationStore implements LocationStore, EntityProvider {
private _connection: EntityProviderConnection | undefined;
diff --git a/plugins/catalog-backend/src/next/NextCatalogBuilder.ts b/plugins/catalog-backend/src/next/NextCatalogBuilder.ts
index e10d13c129..7a74576ea7 100644
--- a/plugins/catalog-backend/src/next/NextCatalogBuilder.ts
+++ b/plugins/catalog-backend/src/next/NextCatalogBuilder.ts
@@ -67,11 +67,11 @@ import { CatalogProcessingEngine, LocationService } from '../next/types';
import { ConfigLocationEntityProvider } from './ConfigLocationEntityProvider';
import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase';
import { DefaultCatalogProcessingEngine } from './DefaultCatalogProcessingEngine';
-import { DefaultCatalogProcessingOrchestrator } from './DefaultCatalogProcessingOrchestrator';
import { DefaultLocationService } from './DefaultLocationService';
import { DefaultLocationStore } from './DefaultLocationStore';
import { NextEntitiesCatalog } from './NextEntitiesCatalog';
-import { Stitcher } from './Stitcher';
+import { DefaultCatalogProcessingOrchestrator } from './processing/DefaultCatalogProcessingOrchestrator';
+import { Stitcher } from './stitching/Stitcher';
export type CatalogEnvironment = {
logger: Logger;
diff --git a/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts b/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts
index 9400bf664f..537ea17eef 100644
--- a/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts
+++ b/plugins/catalog-backend/src/next/NextEntitiesCatalog.ts
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-import { Knex } from 'knex';
-import { DbFinalEntitiesRow } from './Stitcher';
-import { EntitiesCatalog } from '../catalog';
-import { EntitiesRequest, EntitiesResponse } from '../catalog/types';
-import { DbRefreshStateRow } from './database/DefaultProcessingDatabase';
-import {
- DbEntitiesSearchRow,
- DbPageInfo,
- EntityPagination,
-} from '../database/types';
import { InputError } from '@backstage/errors';
+import { Knex } from 'knex';
+import {
+ EntitiesCatalog,
+ EntitiesRequest,
+ EntitiesResponse,
+} from '../catalog/types';
+import { DbPageInfo, EntityPagination } from '../database/types';
+import {
+ DbFinalEntitiesRow,
+ DbRefreshStateRow,
+ DbSearchRow,
+} from './database/tables';
function parsePagination(
input?: EntityPagination,
@@ -80,7 +82,7 @@ export class NextEntitiesCatalog implements EntitiesCatalog {
// NOTE(freben): This used to be a set of OUTER JOIN, which may seem to
// make a lot of sense. However, it had abysmal performance on sqlite
// when datasets grew large, so we're using IN instead.
- const matchQuery = db('search')
+ const matchQuery = db('search')
.select('entity_id')
.where(function keyFilter() {
this.andWhere({ key: key.toLowerCase() });
diff --git a/plugins/catalog-backend/src/next/NextRouter.ts b/plugins/catalog-backend/src/next/NextRouter.ts
index b4660edea0..dc1eef74be 100644
--- a/plugins/catalog-backend/src/next/NextRouter.ts
+++ b/plugins/catalog-backend/src/next/NextRouter.ts
@@ -33,8 +33,8 @@ import {
parseEntityPaginationParams,
parseEntityTransformParams,
} from '../service/request';
-import { LocationService } from './types';
import { disallowReadonlyMode, validateRequestBody } from '../service/util';
+import { LocationService } from './types';
export interface RouterOptions {
entitiesCatalog?: EntitiesCatalog;
diff --git a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts
index 7a0094dbf0..4a390058b8 100644
--- a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts
+++ b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.test.ts
@@ -14,19 +14,18 @@
* limitations under the License.
*/
// import { DefaultProcessingDatabase } from './DefaultProcessingDatabase';
-import { DatabaseManager } from './DatabaseManager';
+import { getVoidLogger } from '@backstage/backend-common';
+import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
+import { JsonObject } from '@backstage/config';
import { Knex } from 'knex';
+import * as uuid from 'uuid';
+import { DatabaseManager } from './DatabaseManager';
+import { DefaultProcessingDatabase } from './DefaultProcessingDatabase';
import {
DbRefreshStateReferencesRow,
DbRefreshStateRow,
DbRelationsRow,
- DefaultProcessingDatabase,
-} from './DefaultProcessingDatabase';
-
-import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
-import * as uuid from 'uuid';
-import { getVoidLogger } from '@backstage/backend-common';
-import { JsonObject } from '@backstage/config';
+} from './tables';
describe('Default Processing Database', () => {
let db: Knex;
diff --git a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts
index cd3ddf037b..7eb2cd1a74 100644
--- a/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts
+++ b/plugins/catalog-backend/src/next/database/DefaultProcessingDatabase.ts
@@ -14,48 +14,27 @@
* limitations under the License.
*/
-import { ConflictError, NotFoundError } from '@backstage/errors';
-import { stringifyEntityRef, Entity } from '@backstage/catalog-model';
-import { Knex } from 'knex';
-import { Transaction } from '../../database';
-import lodash from 'lodash';
-
-import {
- ProcessingDatabase,
- AddUnprocessedEntitiesOptions,
- UpdateProcessedEntityOptions,
- GetProcessableEntitiesResult,
- ReplaceUnprocessedEntitiesOptions,
- RefreshStateItem,
-} from './types';
-import type { Logger } from 'winston';
-
-import { v4 as uuid } from 'uuid';
+import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { JsonObject } from '@backstage/config';
-
-export type DbRefreshStateRow = {
- entity_id: string;
- entity_ref: string;
- unprocessed_entity: string;
- processed_entity?: string;
- cache?: string;
- next_update_at: string;
- last_discovery_at: string; // remove?
- errors?: string;
-};
-
-export type DbRelationsRow = {
- originating_entity_id: string;
- source_entity_ref: string;
- target_entity_ref: string;
- type: string;
-};
-
-export type DbRefreshStateReferencesRow = {
- source_key?: string;
- source_entity_ref?: string;
- target_entity_ref: string;
-};
+import { ConflictError, NotFoundError } from '@backstage/errors';
+import { Knex } from 'knex';
+import lodash from 'lodash';
+import { v4 as uuid } from 'uuid';
+import type { Logger } from 'winston';
+import { Transaction } from '../../database';
+import {
+ DbRefreshStateReferencesRow,
+ DbRefreshStateRow,
+ DbRelationsRow,
+} from './tables';
+import {
+ AddUnprocessedEntitiesOptions,
+ GetProcessableEntitiesResult,
+ ProcessingDatabase,
+ RefreshStateItem,
+ ReplaceUnprocessedEntitiesOptions,
+ UpdateProcessedEntityOptions,
+} from './types';
// The number of items that are sent per batch to the database layer, when
// doing .batchInsert calls to knex. This needs to be low enough to not cause
diff --git a/plugins/catalog-backend/src/next/database/tables.ts b/plugins/catalog-backend/src/next/database/tables.ts
new file mode 100644
index 0000000000..eddd6d12b0
--- /dev/null
+++ b/plugins/catalog-backend/src/next/database/tables.ts
@@ -0,0 +1,58 @@
+/*
+ * 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 DbLocationsRow = {
+ id: string;
+ type: string;
+ target: string;
+};
+
+export type DbRefreshStateRow = {
+ entity_id: string;
+ entity_ref: string;
+ unprocessed_entity: string;
+ processed_entity?: string;
+ cache?: string;
+ next_update_at: string;
+ last_discovery_at: string; // remove?
+ errors?: string;
+};
+
+export type DbRefreshStateReferencesRow = {
+ source_key?: string;
+ source_entity_ref?: string;
+ target_entity_ref: string;
+};
+
+export type DbRelationsRow = {
+ originating_entity_id: string;
+ source_entity_ref: string;
+ target_entity_ref: string;
+ type: string;
+};
+
+export type DbFinalEntitiesRow = {
+ entity_id: string;
+ hash: string;
+ stitch_ticket: string;
+ final_entity?: string;
+};
+
+export type DbSearchRow = {
+ entity_id: string;
+ key: string;
+ value: string | null;
+};
diff --git a/plugins/catalog-backend/src/next/index.ts b/plugins/catalog-backend/src/next/index.ts
index 76867e05c8..8a1e6e4a2c 100644
--- a/plugins/catalog-backend/src/next/index.ts
+++ b/plugins/catalog-backend/src/next/index.ts
@@ -16,3 +16,5 @@
export { NextCatalogBuilder } from './NextCatalogBuilder';
export { createNextRouter } from './NextRouter';
+export * from './processing';
+export * from './stitching';
diff --git a/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts
new file mode 100644
index 0000000000..c3eeea3d8e
--- /dev/null
+++ b/plugins/catalog-backend/src/next/processing/DefaultCatalogProcessingOrchestrator.ts
@@ -0,0 +1,333 @@
+/*
+ * 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,
+ EntityPolicy,
+ LocationEntity,
+ LocationSpec,
+ parseLocationReference,
+ stringifyEntityRef,
+} from '@backstage/catalog-model';
+import { ConflictError, InputError } from '@backstage/errors';
+import { ScmIntegrationRegistry } from '@backstage/integration';
+import path from 'path';
+import { Logger } from 'winston';
+import {
+ CatalogProcessor,
+ CatalogProcessorParser,
+} from '../../ingestion/processors';
+import * as results from '../../ingestion/processors/results';
+import {
+ CatalogProcessingOrchestrator,
+ EntityProcessingRequest,
+ EntityProcessingResult,
+} from './types';
+import { ProcessorOutputCollector } from './ProcessorOutputCollector';
+import {
+ getEntityLocationRef,
+ getEntityOriginLocationRef,
+ isLocationEntity,
+ toAbsoluteUrl,
+ validateEntity,
+ validateEntityEnvelope,
+} from './util';
+
+type Context = {
+ entityRef: string;
+ location: LocationSpec;
+ originLocation: LocationSpec;
+ collector: ProcessorOutputCollector;
+};
+
+export class DefaultCatalogProcessingOrchestrator
+ implements CatalogProcessingOrchestrator {
+ constructor(
+ private readonly options: {
+ processors: CatalogProcessor[];
+ integrations: ScmIntegrationRegistry;
+ logger: Logger;
+ parser: CatalogProcessorParser;
+ policy: EntityPolicy;
+ },
+ ) {}
+
+ async process(
+ request: EntityProcessingRequest,
+ ): Promise {
+ // TODO: implement dryRun/eager
+ return this.processSingleEntity(request.entity);
+ }
+
+ private async processSingleEntity(
+ unprocessedEntity: Entity,
+ ): Promise {
+ const collector = new ProcessorOutputCollector(
+ this.options.logger,
+ unprocessedEntity,
+ );
+
+ try {
+ // This will be checked and mutated step by step below
+ let entity: Entity = unprocessedEntity;
+
+ // NOTE: At this early point, we can only rely on the envelope having to
+ // be valid; full entity + kind validation happens after the (potentially
+ // mutative) pre-steps. This means that the code below can't make a lot
+ // of assumptions about the data despite it using the Entity type.
+ try {
+ validateEntityEnvelope(entity);
+ } catch (e) {
+ throw new InputError(
+ `Entity envelope failed validation before processing`,
+ e,
+ );
+ }
+
+ // TODO: which one do we actually use for the location?
+ // source-location? - maybe probably doesn't exist yet?
+ const context: Context = {
+ entityRef: stringifyEntityRef(entity),
+ location: parseLocationReference(getEntityLocationRef(entity)),
+ originLocation: parseLocationReference(
+ getEntityOriginLocationRef(entity),
+ ),
+ collector,
+ };
+
+ // Run the steps
+ entity = await this.runPreProcessStep(entity, context);
+ entity = await this.runPolicyStep(entity);
+ await this.runValidateStep(entity, context);
+ if (isLocationEntity(entity)) {
+ await this.runSpecialLocationStep(entity, context);
+ }
+ entity = await this.runPostProcessStep(entity, context);
+
+ return {
+ ...context.collector.results(),
+ completedEntity: entity,
+ state: new Map(),
+ ok: true,
+ };
+ } catch (error) {
+ this.options.logger.warn(error.message);
+ return {
+ ok: false,
+ errors: collector.results().errors.concat(error),
+ };
+ }
+ }
+
+ // Pre-process phase, used to populate entities with data that is required
+ // during the main processing step
+ private async runPreProcessStep(
+ entity: Entity,
+ context: Context,
+ ): Promise {
+ let result = entity;
+
+ for (const processor of this.options.processors) {
+ if (processor.preProcessEntity) {
+ try {
+ result = await processor.preProcessEntity(
+ result,
+ context.location,
+ context.collector.onEmit,
+ context.originLocation,
+ );
+ } catch (e) {
+ throw new InputError(
+ `Processor ${processor.constructor.name} threw an error while preprocessing`,
+ e,
+ );
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * Enforce entity policies making sure that entities conform to a general schema
+ */
+ private async runPolicyStep(entity: Entity): Promise {
+ let policyEnforcedEntity: Entity | undefined;
+
+ try {
+ policyEnforcedEntity = await this.options.policy.enforce(entity);
+ } catch (e) {
+ throw new InputError('Policy check failed', e);
+ }
+
+ if (!policyEnforcedEntity) {
+ throw new Error('Policy unexpectedly returned no data');
+ }
+
+ return policyEnforcedEntity;
+ }
+
+ /**
+ * Validate the given entity
+ */
+ private async runValidateStep(
+ entity: Entity,
+ context: Context,
+ ): Promise {
+ // Double check that none of the previous steps tried to change something
+ // related to the entity ref, which would break downstream
+ if (stringifyEntityRef(entity) !== context.entityRef) {
+ throw new ConflictError(
+ 'Fatal: The entity kind, namespace, or name changed during processing',
+ );
+ }
+
+ // Validate that the end result is a valid Entity at all
+ try {
+ validateEntity(entity);
+ } catch (e) {
+ throw new ConflictError(
+ `Entity envelope failed validation after preprocessing`,
+ e,
+ );
+ }
+
+ let foundKind = false;
+
+ for (const processor of this.options.processors) {
+ if (processor.validateEntityKind) {
+ try {
+ foundKind = await processor.validateEntityKind(entity);
+ if (foundKind) {
+ // TODO(freben): It would make sense to keep running, so that
+ // multiple processors could have a go at making checks. For
+ // example, an org may want to add additional rules on top of the
+ // provided ones. But that would be a breaking change, so we'll
+ // postpone that to a future processors rewrite.
+ break;
+ }
+ } catch (e) {
+ throw new InputError(
+ `Processor ${processor.constructor.name} threw an error while validating the entity`,
+ e,
+ );
+ }
+ }
+ }
+
+ if (!foundKind) {
+ throw new InputError(
+ 'No processor recognized the entity as valid, possibly caused by a foreign kind or apiVersion',
+ );
+ }
+ }
+
+ /**
+ * Backwards compatible processing of location entities
+ */
+ private async runSpecialLocationStep(
+ entity: LocationEntity,
+ context: Context,
+ ): Promise {
+ const { type = context.location.type } = entity.spec;
+ const targets = new Array();
+ if (entity.spec.target) {
+ targets.push(entity.spec.target);
+ }
+ if (entity.spec.targets) {
+ targets.push(...entity.spec.targets);
+ }
+
+ for (const maybeRelativeTarget of targets) {
+ if (type === 'file' && maybeRelativeTarget.endsWith(path.sep)) {
+ context.collector.onEmit(
+ results.inputError(
+ context.location,
+ `LocationEntityProcessor cannot handle ${type} type location with target ${context.location.target} that ends with a path separator`,
+ ),
+ );
+ continue;
+ }
+ const target = toAbsoluteUrl(
+ this.options.integrations,
+ context.location,
+ type,
+ maybeRelativeTarget,
+ );
+
+ let didRead = false;
+ for (const processor of this.options.processors) {
+ if (processor.readLocation) {
+ try {
+ const read = await processor.readLocation(
+ {
+ type,
+ target,
+ presence: 'required',
+ },
+ false,
+ context.collector.onEmit,
+ this.options.parser,
+ );
+ if (read) {
+ didRead = true;
+ break;
+ }
+ } catch (e) {
+ throw new InputError(
+ `Processor ${processor.constructor.name} threw an error while reading ${type}:${target}`,
+ e,
+ );
+ }
+ }
+ }
+ if (!didRead) {
+ throw new InputError(
+ `No processor was able to handle reading of ${type}:${target}`,
+ );
+ }
+ }
+ }
+
+ /**
+ * Main processing step of the entity
+ */
+ private async runPostProcessStep(
+ entity: Entity,
+ context: Context,
+ ): Promise {
+ let result = entity;
+
+ for (const processor of this.options.processors) {
+ if (processor.postProcessEntity) {
+ try {
+ result = await processor.postProcessEntity(
+ result,
+ context.location,
+ context.collector.onEmit,
+ );
+ } catch (e) {
+ throw new InputError(
+ `Processor ${processor.constructor.name} threw an error while postprocessing`,
+ e,
+ );
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts b/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts
new file mode 100644
index 0000000000..0c3d74aaa1
--- /dev/null
+++ b/plugins/catalog-backend/src/next/processing/ProcessorOutputCollector.ts
@@ -0,0 +1,109 @@
+/*
+ * 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,
+ EntityRelationSpec,
+ LOCATION_ANNOTATION,
+ ORIGIN_LOCATION_ANNOTATION,
+ stringifyLocationReference,
+} from '@backstage/catalog-model';
+import { Logger } from 'winston';
+import { CatalogProcessorResult } from '../../ingestion';
+import { locationSpecToLocationEntity } from '../util';
+import { getEntityOriginLocationRef, validateEntityEnvelope } from './util';
+
+/**
+ * Helper class for aggregating all of the emitted data from processors.
+ */
+export class ProcessorOutputCollector {
+ private readonly errors = new Array();
+ private readonly relations = new Array();
+ private readonly deferredEntities = new Array();
+ private done = false;
+
+ constructor(
+ private readonly logger: Logger,
+ private readonly parentEntity: Entity,
+ ) {}
+
+ get onEmit(): (i: CatalogProcessorResult) => void {
+ return i => this.receive(i);
+ }
+
+ results() {
+ this.done = true;
+ return {
+ errors: this.errors,
+ relations: this.relations,
+ deferredEntities: this.deferredEntities,
+ };
+ }
+
+ private receive(i: CatalogProcessorResult) {
+ if (this.done) {
+ this.logger.warn(
+ `Item if type ${i.type} was emitted after processing had completed at ${
+ new Error().stack
+ }`,
+ );
+ return;
+ }
+
+ if (i.type === 'entity') {
+ let entity: Entity;
+ try {
+ entity = validateEntityEnvelope(i.entity);
+ } catch (e) {
+ this.logger.debug(`Envelope validation failed at ${i.location}, ${e}`);
+ this.errors.push(e);
+ return;
+ }
+
+ // Note that at this point, we have only validated the envelope part of
+ // the entity data. Annotations are not part of that, so we have to be
+ // defensive. If the annotations were malformed (e.g. were not a valid
+ // object), we just skip over this step and let the full entity
+ // validation at the next step of processing catch that.
+ const annotations = entity.metadata.annotations || {};
+ if (typeof annotations === 'object' && !Array.isArray(annotations)) {
+ const originLocation = getEntityOriginLocationRef(this.parentEntity);
+ const location = stringifyLocationReference(i.location);
+ entity = {
+ ...entity,
+ metadata: {
+ ...entity.metadata,
+ annotations: {
+ ...annotations,
+ [ORIGIN_LOCATION_ANNOTATION]: originLocation,
+ [LOCATION_ANNOTATION]: location,
+ },
+ },
+ };
+ }
+
+ this.deferredEntities.push(entity);
+ } else if (i.type === 'location') {
+ this.deferredEntities.push(
+ locationSpecToLocationEntity(i.location, this.parentEntity),
+ );
+ } else if (i.type === 'relation') {
+ this.relations.push(i.relation);
+ } else if (i.type === 'error') {
+ this.errors.push(i.error);
+ }
+ }
+}
diff --git a/plugins/catalog-backend/src/next/processing/index.ts b/plugins/catalog-backend/src/next/processing/index.ts
new file mode 100644
index 0000000000..7ac4c6a35e
--- /dev/null
+++ b/plugins/catalog-backend/src/next/processing/index.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.
+ */
+
+export type {
+ CatalogProcessingOrchestrator,
+ EntityProcessingRequest,
+ EntityProcessingResult,
+} from './types';
+export { DefaultCatalogProcessingOrchestrator } from './DefaultCatalogProcessingOrchestrator';
diff --git a/plugins/catalog-backend/src/next/processing/types.ts b/plugins/catalog-backend/src/next/processing/types.ts
new file mode 100644
index 0000000000..a287ff7bd7
--- /dev/null
+++ b/plugins/catalog-backend/src/next/processing/types.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.
+ */
+
+import { Entity, EntityRelationSpec } from '@backstage/catalog-model';
+import { JsonObject } from '@backstage/config';
+
+export type EntityProcessingRequest = {
+ entity: Entity;
+ state: Map; // Versions for multiple deployments etc
+};
+
+export type EntityProcessingResult =
+ | {
+ ok: true;
+ state: Map;
+ completedEntity: Entity;
+ deferredEntities: Entity[];
+ relations: EntityRelationSpec[];
+ errors: Error[];
+ }
+ | {
+ ok: false;
+ errors: Error[];
+ };
+
+export interface CatalogProcessingOrchestrator {
+ process(request: EntityProcessingRequest): Promise;
+}
diff --git a/plugins/catalog-backend/src/next/processing/util.ts b/plugins/catalog-backend/src/next/processing/util.ts
new file mode 100644
index 0000000000..b1fff2449e
--- /dev/null
+++ b/plugins/catalog-backend/src/next/processing/util.ts
@@ -0,0 +1,81 @@
+/*
+ * 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,
+ entityEnvelopeSchemaValidator,
+ entitySchemaValidator,
+ LocationEntity,
+ LocationSpec,
+ LOCATION_ANNOTATION,
+ ORIGIN_LOCATION_ANNOTATION,
+ stringifyEntityRef,
+} from '@backstage/catalog-model';
+import { InputError } from '@backstage/errors';
+import { ScmIntegrationRegistry } from '@backstage/integration';
+import path from 'path';
+
+export function isLocationEntity(entity: Entity): entity is LocationEntity {
+ return entity.kind === 'Location';
+}
+
+export function getEntityLocationRef(entity: Entity): string {
+ const ref = entity.metadata.annotations?.[LOCATION_ANNOTATION];
+ if (!ref) {
+ const entityRef = stringifyEntityRef(entity);
+ throw new InputError(`Entity '${entityRef}' does not have a location`);
+ }
+ return ref;
+}
+
+export function getEntityOriginLocationRef(entity: Entity): string {
+ const ref = entity.metadata.annotations?.[ORIGIN_LOCATION_ANNOTATION];
+ if (!ref) {
+ const entityRef = stringifyEntityRef(entity);
+ throw new InputError(
+ `Entity '${entityRef}' does not have an origin location`,
+ );
+ }
+ return ref;
+}
+
+export function toAbsoluteUrl(
+ integrations: ScmIntegrationRegistry,
+ base: LocationSpec,
+ type: string,
+ target: string,
+): string {
+ if (base.type !== type) {
+ return target;
+ }
+ try {
+ if (type === 'file') {
+ if (target.startsWith('.')) {
+ return path.join(path.dirname(base.target), target);
+ }
+ return target;
+ } else if (type === 'url') {
+ return integrations.resolveUrl({ url: target, base: base.target });
+ }
+ return target;
+ } catch (e) {
+ return target;
+ }
+}
+
+export const validateEntity = entitySchemaValidator();
+
+export const validateEntityEnvelope = entityEnvelopeSchemaValidator();
diff --git a/plugins/catalog-backend/src/next/Stitcher.test.ts b/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts
similarity index 96%
rename from plugins/catalog-backend/src/next/Stitcher.test.ts
rename to plugins/catalog-backend/src/next/stitching/Stitcher.test.ts
index a6af6f01a1..89f1fda0b3 100644
--- a/plugins/catalog-backend/src/next/Stitcher.test.ts
+++ b/plugins/catalog-backend/src/next/stitching/Stitcher.test.ts
@@ -17,14 +17,15 @@
import { getVoidLogger } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
import { Knex } from 'knex';
-import { DatabaseManager } from './database/DatabaseManager';
+import { DatabaseManager } from '../database/DatabaseManager';
import {
+ DbFinalEntitiesRow,
DbRefreshStateReferencesRow,
DbRefreshStateRow,
DbRelationsRow,
-} from './database/DefaultProcessingDatabase';
-import { DbSearchRow } from './search';
-import { DbFinalEntitiesRow, Stitcher } from './Stitcher';
+ DbSearchRow,
+} from '../database/tables';
+import { Stitcher } from './Stitcher';
describe('Stitcher', () => {
let db: Knex;
diff --git a/plugins/catalog-backend/src/next/Stitcher.ts b/plugins/catalog-backend/src/next/stitching/Stitcher.ts
similarity index 91%
rename from plugins/catalog-backend/src/next/Stitcher.ts
rename to plugins/catalog-backend/src/next/stitching/Stitcher.ts
index 3090c4a971..c735696d7f 100644
--- a/plugins/catalog-backend/src/next/Stitcher.ts
+++ b/plugins/catalog-backend/src/next/stitching/Stitcher.ts
@@ -21,32 +21,16 @@ import {
UNSTABLE_EntityStatusItem,
} from '@backstage/catalog-model';
import { SerializedError } from '@backstage/errors';
-import { createHash } from 'crypto';
-import stableStringify from 'fast-json-stable-stringify';
import { Knex } from 'knex';
-import { Logger } from 'winston';
-import { buildEntitySearch, DbSearchRow } from './search';
import { v4 as uuid } from 'uuid';
-import { DbRefreshStateRow } from './database/DefaultProcessingDatabase';
-
-// The number of items that are sent per batch to the database layer, when
-// doing .batchInsert calls to knex. This needs to be low enough to not cause
-// errors in the underlying engine due to exceeding query limits, but large
-// enough to get the speed benefits.
-const BATCH_SIZE = 50;
-
-export type DbFinalEntitiesRow = {
- entity_id: string;
- hash: string;
- stitch_ticket: string;
- final_entity?: string;
-};
-
-function generateStableHash(entity: Entity) {
- return createHash('sha1')
- .update(stableStringify({ ...entity }))
- .digest('hex');
-}
+import { Logger } from 'winston';
+import {
+ DbFinalEntitiesRow,
+ DbRefreshStateRow,
+ DbSearchRow,
+} from '../database/tables';
+import { buildEntitySearch } from './buildEntitySearch';
+import { BATCH_SIZE, generateStableHash } from './util';
/**
* Performs the act of stitching - to take all of the various outputs from the
@@ -176,7 +160,7 @@ export class Stitcher {
statusItems = parsedErrors.map(e => ({
type: ENTITY_STATUS_CATALOG_PROCESSING_TYPE,
level: 'error',
- message: e.toString(),
+ message: `${e.name}: ${e.message}`,
error: e,
}));
}
diff --git a/plugins/catalog-backend/src/next/search.test.ts b/plugins/catalog-backend/src/next/stitching/buildEntitySearch.test.ts
similarity index 97%
rename from plugins/catalog-backend/src/next/search.test.ts
rename to plugins/catalog-backend/src/next/stitching/buildEntitySearch.test.ts
index be896319b7..3f04ed79d4 100644
--- a/plugins/catalog-backend/src/next/search.test.ts
+++ b/plugins/catalog-backend/src/next/stitching/buildEntitySearch.test.ts
@@ -15,9 +15,9 @@
*/
import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model';
-import { buildEntitySearch, mapToRows, traverse } from './search';
+import { buildEntitySearch, mapToRows, traverse } from './buildEntitySearch';
-describe('search', () => {
+describe('buildEntitySearch', () => {
describe('traverse', () => {
it('expands lists of strings to several rows', () => {
const input = { a: ['b', 'c', 'd'] };
diff --git a/plugins/catalog-backend/src/next/search.ts b/plugins/catalog-backend/src/next/stitching/buildEntitySearch.ts
similarity index 98%
rename from plugins/catalog-backend/src/next/search.ts
rename to plugins/catalog-backend/src/next/stitching/buildEntitySearch.ts
index 0681bffa17..48a4e779bd 100644
--- a/plugins/catalog-backend/src/next/search.ts
+++ b/plugins/catalog-backend/src/next/stitching/buildEntitySearch.ts
@@ -19,12 +19,7 @@ import {
ENTITY_DEFAULT_NAMESPACE,
stringifyEntityRef,
} from '@backstage/catalog-model';
-
-export type DbSearchRow = {
- entity_id: string;
- key: string;
- value: string | null;
-};
+import { DbSearchRow } from '../database/tables';
// These are excluded in the generic loop, either because they do not make sense
// to index, or because they are special-case always inserted whether they are
diff --git a/plugins/catalog-backend/src/next/stitching/index.ts b/plugins/catalog-backend/src/next/stitching/index.ts
new file mode 100644
index 0000000000..4e230aca20
--- /dev/null
+++ b/plugins/catalog-backend/src/next/stitching/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 {};
diff --git a/plugins/catalog-backend/src/next/stitching/util.ts b/plugins/catalog-backend/src/next/stitching/util.ts
new file mode 100644
index 0000000000..a72f22d19d
--- /dev/null
+++ b/plugins/catalog-backend/src/next/stitching/util.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 { Entity } from '@backstage/catalog-model';
+import { createHash } from 'crypto';
+import stableStringify from 'fast-json-stable-stringify';
+
+// The number of items that are sent per batch to the database layer, when
+// doing .batchInsert calls to knex. This needs to be low enough to not cause
+// errors in the underlying engine due to exceeding query limits, but large
+// enough to get the speed benefits.
+export const BATCH_SIZE = 50;
+
+export function generateStableHash(entity: Entity) {
+ return createHash('sha1')
+ .update(stableStringify({ ...entity }))
+ .digest('hex');
+}
diff --git a/plugins/catalog-backend/src/next/types.ts b/plugins/catalog-backend/src/next/types.ts
index fbc243ce69..f189f3e975 100644
--- a/plugins/catalog-backend/src/next/types.ts
+++ b/plugins/catalog-backend/src/next/types.ts
@@ -14,13 +14,7 @@
* limitations under the License.
*/
-import {
- Entity,
- LocationSpec,
- Location,
- EntityRelationSpec,
-} from '@backstage/catalog-model';
-import { JsonObject } from '@backstage/config';
+import { Entity, Location, LocationSpec } from '@backstage/catalog-model';
export interface LocationService {
createLocation(
@@ -56,26 +50,3 @@ export interface EntityProvider {
getProviderName(): string;
connect(connection: EntityProviderConnection): Promise;
}
-
-export type EntityProcessingRequest = {
- entity: Entity;
- state: Map; // Versions for multiple deployments etc
-};
-
-export type EntityProcessingResult =
- | {
- ok: true;
- state: Map;
- completedEntity: Entity;
- deferredEntities: Entity[];
- relations: EntityRelationSpec[];
- errors: Error[];
- }
- | {
- ok: false;
- errors: Error[];
- };
-
-export interface CatalogProcessingOrchestrator {
- process(request: EntityProcessingRequest): Promise;
-}
diff --git a/plugins/catalog-backend/src/next/util.ts b/plugins/catalog-backend/src/next/util.ts
index f8e6f1f978..ef3a953384 100644
--- a/plugins/catalog-backend/src/next/util.ts
+++ b/plugins/catalog-backend/src/next/util.ts
@@ -16,8 +16,8 @@
import {
Entity,
- LocationSpec,
LocationEntityV1alpha1,
+ LocationSpec,
LOCATION_ANNOTATION,
ORIGIN_LOCATION_ANNOTATION,
stringifyEntityRef,
@@ -29,7 +29,6 @@ export function locationSpecToMetadataName(location: LocationSpec) {
const hash = createHash('sha1')
.update(`${location.type}:${location.target}`)
.digest('hex');
-
return `generated-${hash}`;
}
diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts
index 068b9e36ae..70b8010d87 100644
--- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts
+++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts
@@ -30,6 +30,7 @@ export interface CatalogEntityDocument extends IndexableDocument {
export class DefaultCatalogCollator implements DocumentCollator {
protected discovery: PluginEndpointDiscovery;
protected locationTemplate: string;
+ public readonly type: string = 'software-catalog';
constructor({
discovery,
diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json
index 7b4278393f..e2db1968d8 100644
--- a/plugins/catalog-import/package.json
+++ b/plugins/catalog-import/package.json
@@ -36,7 +36,7 @@
"@backstage/integration": "^0.5.4",
"@backstage/integration-react": "^0.1.2",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -53,9 +53,9 @@
"yaml": "^1.10.0"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.3.0",
diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md
index 4eb61c43a9..16f2d3e1b1 100644
--- a/plugins/catalog-react/CHANGELOG.md
+++ b/plugins/catalog-react/CHANGELOG.md
@@ -1,5 +1,19 @@
# @backstage/plugin-catalog-react
+## 0.2.1
+
+### Patch Changes
+
+- deaba2e13: Sort `EntityTagPicker` entries.
+- 8e919a6f8: Tweak the `EntityListProvider` to do single-cycle updates
+- Updated dependencies [031ccd45f]
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [1cf1d351f]
+ - @backstage/core-plugin-api@0.1.1
+ - @backstage/catalog-model@0.8.1
+ - @backstage/core@0.7.12
+
## 0.2.0
### Minor Changes
diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json
index 3d8dcd22c1..eb01aba8af 100644
--- a/plugins/catalog-react/package.json
+++ b/plugins/catalog-react/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog-react",
- "version": "0.2.0",
+ "version": "0.2.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,9 +29,9 @@
},
"dependencies": {
"@backstage/catalog-client": "^0.3.12",
- "@backstage/catalog-model": "^0.8.0",
- "@backstage/core": "^0.7.11",
- "@backstage/core-plugin-api": "^0.1.0",
+ "@backstage/catalog-model": "^0.8.1",
+ "@backstage/core": "^0.7.12",
+ "@backstage/core-plugin-api": "^0.1.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -43,10 +43,10 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/core": "^0.7.11",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.11",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/core": "^0.7.12",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.3.0",
diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md
index 3c40fa33c8..c04e205715 100644
--- a/plugins/catalog/CHANGELOG.md
+++ b/plugins/catalog/CHANGELOG.md
@@ -1,5 +1,24 @@
# @backstage/plugin-catalog
+## 0.6.1
+
+### Patch Changes
+
+- 2a942cc9e: invert logic for when to show type column
+- f46a9e82d: Move dependency to `@microsoft/microsoft-graph-types` from `@backstage/plugin-catalog`
+ to `@backstage/plugin-catalog-backend`.
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [49d7ec169]
+- Updated dependencies [1cf1d351f]
+- Updated dependencies [deaba2e13]
+- Updated dependencies [8e919a6f8]
+ - @backstage/theme@0.2.8
+ - @backstage/catalog-model@0.8.1
+ - @backstage/integration@0.5.5
+ - @backstage/core@0.7.12
+ - @backstage/plugin-catalog-react@0.2.1
+
## 0.6.0
### Minor Changes
diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json
index 0836efc829..0133ae3bbb 100644
--- a/plugins/catalog/package.json
+++ b/plugins/catalog/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-catalog",
- "version": "0.6.0",
+ "version": "0.6.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,13 +31,13 @@
},
"dependencies": {
"@backstage/catalog-client": "^0.3.12",
- "@backstage/catalog-model": "^0.8.0",
- "@backstage/core": "^0.7.11",
+ "@backstage/catalog-model": "^0.8.1",
+ "@backstage/core": "^0.7.12",
"@backstage/errors": "^0.1.1",
- "@backstage/integration": "^0.5.4",
+ "@backstage/integration": "^0.5.5",
"@backstage/integration-react": "^0.1.2",
- "@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/plugin-catalog-react": "^0.2.1",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -53,9 +53,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.3.0",
diff --git a/plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx b/plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx
new file mode 100644
index 0000000000..8533de22f7
--- /dev/null
+++ b/plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx
@@ -0,0 +1,59 @@
+/*
+ * 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 { Link } from '@backstage/core';
+import {
+ Box,
+ Chip,
+ Divider,
+ ListItem,
+ ListItemText,
+ makeStyles,
+} from '@material-ui/core';
+
+const useStyles = makeStyles({
+ flexContainer: {
+ flexWrap: 'wrap',
+ },
+ itemText: {
+ width: '100%',
+ marginBottom: '1rem',
+ },
+});
+
+export const CatalogResultListItem = ({ result }: any) => {
+ const classes = useStyles();
+ return (
+
+
+
+
+ {result.kind && }
+ {result.lifecycle && (
+
+ )}
+
+
+
+
+ );
+};
diff --git a/plugins/catalog/src/components/CatalogResultListItem/index.ts b/plugins/catalog/src/components/CatalogResultListItem/index.ts
new file mode 100644
index 0000000000..8f418c1dc7
--- /dev/null
+++ b/plugins/catalog/src/components/CatalogResultListItem/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 { CatalogResultListItem } from './CatalogResultListItem';
diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts
index f7506b534a..052f055dc0 100644
--- a/plugins/catalog/src/index.ts
+++ b/plugins/catalog/src/index.ts
@@ -15,6 +15,7 @@
*/
export { AboutCard } from './components/AboutCard';
+export { CatalogResultListItem } from './components/CatalogResultListItem';
export { EntityLayout } from './components/EntityLayout';
export { EntityPageLayout } from './components/EntityPageLayout';
export { CatalogTable } from './components/CatalogTable';
diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json
index b3633e7cf9..20cac43ac9 100644
--- a/plugins/circleci/package.json
+++ b/plugins/circleci/package.json
@@ -34,7 +34,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -50,9 +50,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json
index 686d88334c..03408ac6c1 100644
--- a/plugins/cloudbuild/package.json
+++ b/plugins/cloudbuild/package.json
@@ -33,7 +33,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/plugin-catalog-react": "^0.2.0",
"@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -47,9 +47,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json
index 9c69015640..a7d7126646 100644
--- a/plugins/code-coverage/package.json
+++ b/plugins/code-coverage/package.json
@@ -25,7 +25,7 @@
"@backstage/core": "^0.7.11",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/styles": "^4.11.0",
@@ -39,9 +39,9 @@
"recharts": "^1.8.5"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json
index d5fdbe2ee8..12e1c65664 100644
--- a/plugins/config-schema/package.json
+++ b/plugins/config-schema/package.json
@@ -23,7 +23,7 @@
"@backstage/config": "^0.1.4",
"@backstage/core": "^0.7.11",
"@backstage/errors": "^0.1.1",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -34,9 +34,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json
index 3069ffb322..bb7e9b6984 100644
--- a/plugins/cost-insights/package.json
+++ b/plugins/cost-insights/package.json
@@ -32,14 +32,13 @@
"dependencies": {
"@backstage/config": "^0.1.5",
"@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@material-ui/styles": "^4.9.6",
"@types/react": "^16.9",
"@types/recharts": "^1.8.14",
- "canvas": "^2.6.1",
"classnames": "^2.2.6",
"dayjs": "^1.9.4",
"history": "^5.0.0",
@@ -55,9 +54,9 @@
"yup": "^0.29.3"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
@@ -67,6 +66,7 @@
"@types/recharts": "^1.8.14",
"@types/regression": "^2.0.0",
"@types/yup": "^0.29.8",
+ "canvas": "^2.6.1",
"cross-fetch": "^3.0.6",
"msw": "^0.21.2"
},
diff --git a/plugins/explore/package.json b/plugins/explore/package.json
index 4511b13716..a05ed2185d 100644
--- a/plugins/explore/package.json
+++ b/plugins/explore/package.json
@@ -34,7 +34,7 @@
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
"@backstage/plugin-explore-react": "^0.0.5",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -45,9 +45,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json
index 2cdfd3d946..fe0057b200 100644
--- a/plugins/fossa/package.json
+++ b/plugins/fossa/package.json
@@ -35,7 +35,7 @@
"@backstage/core": "^0.7.11",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -47,9 +47,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json
index 5e40d20f64..affb0bf0c6 100644
--- a/plugins/gcp-projects/package.json
+++ b/plugins/gcp-projects/package.json
@@ -31,7 +31,7 @@
},
"dependencies": {
"@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -41,9 +41,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json
index b744223a9f..1c075c420c 100644
--- a/plugins/git-release-manager/package.json
+++ b/plugins/git-release-manager/package.json
@@ -22,7 +22,7 @@
"dependencies": {
"@backstage/core": "^0.7.11",
"@backstage/integration": "^0.5.3",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"recharts": "^1.8.5",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -36,9 +36,9 @@
"react": "^16.13.1"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@types/recharts": "^1.8.15",
"@testing-library/react-hooks": "^3.4.2",
diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json
index f787533504..51678940bf 100644
--- a/plugins/github-actions/package.json
+++ b/plugins/github-actions/package.json
@@ -36,7 +36,7 @@
"@backstage/plugin-catalog-react": "^0.2.0",
"@backstage/core": "^0.7.11",
"@backstage/integration": "^0.5.4",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -50,9 +50,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json
index 821d9f440e..aba58f4410 100644
--- a/plugins/github-deployments/package.json
+++ b/plugins/github-deployments/package.json
@@ -26,7 +26,7 @@
"@backstage/integration": "^0.5.4",
"@backstage/integration-react": "^0.1.2",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -37,9 +37,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json
index e5113e1696..5116c797b8 100644
--- a/plugins/gitops-profiles/package.json
+++ b/plugins/gitops-profiles/package.json
@@ -32,7 +32,7 @@
},
"dependencies": {
"@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -42,9 +42,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json
index 44ac82c3f4..c6929fe208 100644
--- a/plugins/graphiql/package.json
+++ b/plugins/graphiql/package.json
@@ -33,7 +33,7 @@
"dependencies": {
"@backstage/core": "^0.7.11",
"@backstage/core-plugin-api": "^0.1.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -44,9 +44,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json
index 195696fca3..cc3a212c42 100644
--- a/plugins/ilert/package.json
+++ b/plugins/ilert/package.json
@@ -24,7 +24,7 @@
"@backstage/core": "^0.7.11",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@date-io/luxon": "1.x",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -37,9 +37,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json
index abec18f34d..ada9815baf 100644
--- a/plugins/jenkins/package.json
+++ b/plugins/jenkins/package.json
@@ -34,7 +34,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -47,9 +47,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json
index 99564fa95c..ce8bc35bd9 100644
--- a/plugins/kafka/package.json
+++ b/plugins/kafka/package.json
@@ -23,7 +23,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -33,9 +33,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.4.2",
diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json
index f45d431c2e..5f3ca01e37 100644
--- a/plugins/kubernetes/package.json
+++ b/plugins/kubernetes/package.json
@@ -35,7 +35,7 @@
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
"@backstage/plugin-kubernetes-common": "^0.1.1",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@kubernetes/client-node": "^0.14.0",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -49,9 +49,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.4.2",
diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json
index 672e90e210..ead00d9d7d 100644
--- a/plugins/lighthouse/package.json
+++ b/plugins/lighthouse/package.json
@@ -35,7 +35,7 @@
"@backstage/config": "^0.1.4",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -46,9 +46,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json
index b343964b81..e5fa8d2e88 100644
--- a/plugins/newrelic/package.json
+++ b/plugins/newrelic/package.json
@@ -32,7 +32,7 @@
},
"dependencies": {
"@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -41,9 +41,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/org/package.json b/plugins/org/package.json
index 4b04fff827..c9195d5709 100644
--- a/plugins/org/package.json
+++ b/plugins/org/package.json
@@ -24,7 +24,7 @@
"@backstage/core": "^0.7.11",
"@backstage/core-api": "^0.2.20",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -35,9 +35,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json
index 32bddc57ac..81ac6e23fc 100644
--- a/plugins/pagerduty/package.json
+++ b/plugins/pagerduty/package.json
@@ -33,7 +33,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -46,9 +46,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json
index beb49db85a..80b0e96a50 100644
--- a/plugins/register-component/package.json
+++ b/plugins/register-component/package.json
@@ -33,7 +33,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -45,9 +45,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json
index 000621e73d..06ce51eada 100644
--- a/plugins/rollbar/package.json
+++ b/plugins/rollbar/package.json
@@ -34,7 +34,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -47,9 +47,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/react-hooks": "^3.3.0",
diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md
index a82a278993..4d48ea5713 100644
--- a/plugins/scaffolder-backend/CHANGELOG.md
+++ b/plugins/scaffolder-backend/CHANGELOG.md
@@ -1,5 +1,15 @@
# @backstage/plugin-scaffolder-backend
+## 0.11.5
+
+### Patch Changes
+
+- 6fe1567a7: This adds a configuration option to the scaffolder plugin router, so we can allow for multiple `TaskWorkers`. Currently with only one `TaskWorker` you are limited to scaffolding one thing at a time. Set the `taskWorkers?: number` option in your scaffolder router to get more than 1 `TaskWorker`
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [49d7ec169]
+ - @backstage/catalog-model@0.8.1
+ - @backstage/integration@0.5.5
+
## 0.11.4
### Patch Changes
diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json
index f20c43b4fa..10dfaea709 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.4",
+ "version": "0.11.5",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,10 +31,10 @@
"dependencies": {
"@backstage/backend-common": "^0.8.1",
"@backstage/catalog-client": "^0.3.12",
- "@backstage/catalog-model": "^0.8.0",
+ "@backstage/catalog-model": "^0.8.1",
"@backstage/config": "^0.1.5",
"@backstage/errors": "^0.1.1",
- "@backstage/integration": "^0.5.4",
+ "@backstage/integration": "^0.5.5",
"@gitbeaker/core": "^29.2.0",
"@gitbeaker/node": "^29.2.0",
"@octokit/rest": "^18.5.3",
@@ -64,8 +64,8 @@
"yaml": "^1.10.0"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/test-utils": "^0.1.13",
"@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 0d0ff58754..25f27aac81 100644
--- a/plugins/scaffolder/CHANGELOG.md
+++ b/plugins/scaffolder/CHANGELOG.md
@@ -1,5 +1,54 @@
# @backstage/plugin-scaffolder
+## 0.9.7
+
+### Patch Changes
+
+- 497f4ce18: Scaffolder Field Extensions are here! This means you'll now the ability to create custom field extensions and have the Scaffolder use the components when collecting information from the user in the wizard. By default we supply the `RepoUrlPicker` and the `OwnerPicker`, but if you want to provide some more extensions or override the built on ones you will have to change how the `ScaffolderPage` is wired up in your `app/src/App.tsx` to pass in the custom fields to the Scaffolder.
+
+ You'll need to move this:
+
+ ```tsx
+ } />
+ ```
+
+ To this:
+
+ ```tsx
+ import {
+ ScaffolderFieldExtensions,
+ RepoUrlPickerFieldExtension,
+ OwnerPickerFieldExtension,
+ } from '@backstage/plugin-scaffolder';
+
+ }>
+
+
+
+
+ {/*Any other extensions you want to provide*/}
+
+ ;
+ ```
+
+ More documentation on how to write your own `FieldExtensions` to follow.
+
+- 3772de8ba: Remove the trailing space from a the aria-label of the Template "CHOOSE" button.
+- f430b6c6f: Don't merge with previous from state on form changes.
+- 76f99a1a0: Export `createScaffolderFieldExtension` to enable the creation of new field extensions.
+- 1157fa307: Add a `` field to the scaffolder to pick arbitrary entity kinds, like systems.
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [49d7ec169]
+- Updated dependencies [1cf1d351f]
+- Updated dependencies [deaba2e13]
+- Updated dependencies [8e919a6f8]
+ - @backstage/theme@0.2.8
+ - @backstage/catalog-model@0.8.1
+ - @backstage/integration@0.5.5
+ - @backstage/core@0.7.12
+ - @backstage/plugin-catalog-react@0.2.1
+
## 0.9.6
### Patch Changes
diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json
index f1ad6c50bc..dceb207ed1 100644
--- a/plugins/scaffolder/package.json
+++ b/plugins/scaffolder/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-scaffolder",
- "version": "0.9.6",
+ "version": "0.9.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -31,14 +31,14 @@
},
"dependencies": {
"@backstage/catalog-client": "^0.3.12",
- "@backstage/catalog-model": "^0.8.0",
+ "@backstage/catalog-model": "^0.8.1",
"@backstage/config": "^0.1.5",
"@backstage/errors": "^0.1.1",
- "@backstage/core": "^0.7.11",
- "@backstage/integration": "^0.5.4",
+ "@backstage/core": "^0.7.12",
+ "@backstage/integration": "^0.5.5",
"@backstage/integration-react": "^0.1.2",
- "@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/plugin-catalog-react": "^0.2.1",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -61,9 +61,9 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts
index 2e0fd96f08..c49edd2f60 100644
--- a/plugins/scaffolder/src/index.ts
+++ b/plugins/scaffolder/src/index.ts
@@ -16,7 +16,10 @@
export { scaffolderApiRef, ScaffolderClient } from './api';
export type { ScaffolderApi } from './api';
-export { ScaffolderFieldExtensions } from './extensions';
+export {
+ createScaffolderFieldExtension,
+ ScaffolderFieldExtensions,
+} from './extensions';
export {
EntityPickerFieldExtension,
OwnerPickerFieldExtension,
diff --git a/plugins/search-backend-node/src/IndexBuilder.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts
index e1dc511b97..cac2975781 100644
--- a/plugins/search-backend-node/src/IndexBuilder.test.ts
+++ b/plugins/search-backend-node/src/IndexBuilder.test.ts
@@ -24,22 +24,33 @@ import { IndexBuilder } from './IndexBuilder';
import { LunrSearchEngine, SearchEngine } from './index';
class TestDocumentCollator implements DocumentCollator {
- async execute() {
+ readonly type: string = 'anything';
+ async execute(): Promise {
return [];
}
}
+class TypedDocumentCollator extends TestDocumentCollator {
+ readonly type = 'an-expected-type';
+}
+
class TestDocumentDecorator implements DocumentDecorator {
async execute(documents: IndexableDocument[]) {
return documents;
}
}
+class TypedDocumentDecorator extends TestDocumentDecorator {
+ readonly types = ['an-expected-type'];
+}
+
+class DifferentlyTypedDocumentDecorator extends TestDocumentDecorator {
+ readonly types = ['not-the-expected-type'];
+}
+
describe('IndexBuilder', () => {
let testSearchEngine: SearchEngine;
let testIndexBuilder: IndexBuilder;
- let testCollator: DocumentCollator;
- let testDecorator: DocumentDecorator;
beforeEach(() => {
const logger = getVoidLogger();
@@ -48,18 +59,16 @@ describe('IndexBuilder', () => {
logger,
searchEngine: testSearchEngine,
});
- testCollator = new TestDocumentCollator();
- testDecorator = new TestDocumentDecorator();
});
describe('addCollator', () => {
it('adds a collator', async () => {
jest.useFakeTimers();
+ const testCollator = new TestDocumentCollator();
const collatorSpy = jest.spyOn(testCollator, 'execute');
// Add a collator.
testIndexBuilder.addCollator({
- type: 'anything',
defaultRefreshIntervalSeconds: 6,
collator: testCollator,
});
@@ -75,11 +84,12 @@ describe('IndexBuilder', () => {
describe('addDecorator', () => {
it('adds a decorator', async () => {
jest.useFakeTimers();
+ const testCollator = new TestDocumentCollator();
+ const testDecorator = new TestDocumentDecorator();
const decoratorSpy = jest.spyOn(testDecorator, 'execute');
// Add a collator.
testIndexBuilder.addCollator({
- type: 'anything',
defaultRefreshIntervalSeconds: 6,
collator: testCollator,
});
@@ -100,7 +110,8 @@ describe('IndexBuilder', () => {
it('adds a type-specific decorator', async () => {
jest.useFakeTimers();
- const expectedType = 'an-expected-type';
+ const testCollator = new TypedDocumentCollator();
+ const testDecorator = new TypedDocumentDecorator();
const docFixture = {
title: 'Test',
text: 'Test text.',
@@ -113,14 +124,12 @@ describe('IndexBuilder', () => {
// Add a collator.
testIndexBuilder.addCollator({
- type: expectedType,
defaultRefreshIntervalSeconds: 6,
collator: testCollator,
});
// Add a decorator for the same type.
testIndexBuilder.addDecorator({
- types: [expectedType],
decorator: testDecorator,
});
@@ -135,12 +144,13 @@ describe('IndexBuilder', () => {
});
it('adds a type-specific decorator that should not be called', async () => {
- const expectedType = 'an-expected-type';
const docFixture = {
title: 'Test',
text: 'Test text.',
location: '/test/location',
};
+ const testCollator = new TestDocumentCollator();
+ const testDecorator = new DifferentlyTypedDocumentDecorator();
const collatorSpy = jest
.spyOn(testCollator, 'execute')
.mockImplementation(async () => [docFixture]);
@@ -148,14 +158,12 @@ describe('IndexBuilder', () => {
// Add a collator.
testIndexBuilder.addCollator({
- type: expectedType,
defaultRefreshIntervalSeconds: 6,
collator: testCollator,
});
// Add a decorator for a different type.
testIndexBuilder.addDecorator({
- types: ['not-the-expected-type'],
decorator: testDecorator,
});
diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts
index 71d374e704..046eaf7f34 100644
--- a/plugins/search-backend-node/src/IndexBuilder.ts
+++ b/plugins/search-backend-node/src/IndexBuilder.ts
@@ -55,28 +55,25 @@ export class IndexBuilder {
* given refresh interval.
*/
addCollator({
- type,
collator,
defaultRefreshIntervalSeconds,
}: RegisterCollatorParameters): void {
this.logger.info(
- `Added ${collator.constructor.name} collator for type ${type}`,
+ `Added ${collator.constructor.name} collator for type ${collator.type}`,
);
- this.collators[type] = {
+ this.collators[collator.type] = {
refreshInterval: defaultRefreshIntervalSeconds,
collate: collator,
};
}
/**
- * Makes the index builder aware of a decorator. If no types are provided, it
- * will be applied to documents from all known collators, otherwise it will
- * only be applied to documents of the given types.
+ * Makes the index builder aware of a decorator. If no types are provided on
+ * the decorator, it will be applied to documents from all known collators,
+ * otherwise it will only be applied to documents of the given types.
*/
- addDecorator({
- types = ['*'],
- decorator,
- }: RegisterDecoratorParameters): void {
+ addDecorator({ decorator }: RegisterDecoratorParameters): void {
+ const types = decorator.types || ['*'];
this.logger.info(
`Added decorator ${decorator.constructor.name} to types ${types.join(
', ',
diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts
index 6c348235e0..969e579a53 100644
--- a/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts
+++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.test.ts
@@ -18,6 +18,15 @@ import { getVoidLogger } from '@backstage/backend-common';
import { LunrSearchEngine } from './LunrSearchEngine';
import { SearchEngine } from '../types';
+/**
+ * Just used to test the default translator shipped with LunrSearchEngine.
+ */
+class LunrSearchEngineForTranslatorTests extends LunrSearchEngine {
+ getTranslator() {
+ return this.translator;
+ }
+}
+
describe('LunrSearchEngine', () => {
let testLunrSearchEngine: SearchEngine;
@@ -27,16 +36,21 @@ describe('LunrSearchEngine', () => {
describe('translator', () => {
it('query translator invoked', async () => {
- const translatorSpy = jest.spyOn(testLunrSearchEngine, 'translator');
+ // Given: Set a translator spy on the search engine.
+ const translatorSpy = jest.fn().mockReturnValue({
+ lunrQueryString: '',
+ documentTypes: [],
+ });
+ testLunrSearchEngine.setTranslator(translatorSpy);
- // Translate query and ensure the translator was invoked.
- await testLunrSearchEngine.translator({
+ // When: querying the search engine
+ testLunrSearchEngine.query({
term: 'testTerm',
filters: {},
pageCursor: '',
});
- expect(translatorSpy).toHaveBeenCalled();
+ // Then: the translator is invoked with expected args.
expect(translatorSpy).toHaveBeenCalledWith({
term: 'testTerm',
filters: {},
@@ -45,41 +59,56 @@ describe('LunrSearchEngine', () => {
});
it('should return translated query', async () => {
- const mockedTranslatedQuery = await testLunrSearchEngine.translator({
+ const inspectableSearchEngine = new LunrSearchEngineForTranslatorTests({
+ logger: getVoidLogger(),
+ });
+ const translatorUnderTest = inspectableSearchEngine.getTranslator();
+
+ const actualTranslatedQuery = translatorUnderTest({
term: 'testTerm',
filters: {},
pageCursor: '',
});
- expect(mockedTranslatedQuery).toMatchObject({
+ expect(actualTranslatedQuery).toMatchObject({
documentTypes: ['*'],
- lunrQueryString: 'testTerm',
+ lunrQueryString: '+testTerm',
});
});
it('should return translated query with 1 filter', async () => {
- const mockedTranslatedQuery = await testLunrSearchEngine.translator({
+ const inspectableSearchEngine = new LunrSearchEngineForTranslatorTests({
+ logger: getVoidLogger(),
+ });
+ const translatorUnderTest = inspectableSearchEngine.getTranslator();
+
+ const actualTranslatedQuery = translatorUnderTest({
term: 'testTerm',
filters: { kind: 'testKind' },
pageCursor: '',
});
- expect(mockedTranslatedQuery).toMatchObject({
+ expect(actualTranslatedQuery).toMatchObject({
documentTypes: ['*'],
- lunrQueryString: 'testTerm +kind:testKind',
+ lunrQueryString: '+testTerm +kind:testKind',
});
});
it('should return translated query with multiple filters', async () => {
- const mockedTranslatedQuery = await testLunrSearchEngine.translator({
+ const inspectableSearchEngine = new LunrSearchEngineForTranslatorTests({
+ logger: getVoidLogger(),
+ });
+ const translatorUnderTest = inspectableSearchEngine.getTranslator();
+
+ const actualTranslatedQuery = translatorUnderTest({
term: 'testTerm',
filters: { kind: 'testKind', namespace: 'testNameSpace' },
pageCursor: '',
});
- expect(mockedTranslatedQuery).toMatchObject({
+ expect(actualTranslatedQuery).toMatchObject({
documentTypes: ['*'],
- lunrQueryString: 'testTerm +kind:testKind +namespace:testNameSpace',
+ lunrQueryString: '+testTerm +kind:testKind +namespace:testNameSpace',
});
});
});
diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts
index cc8c5bb982..be51738920 100644
--- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts
+++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts
@@ -28,6 +28,13 @@ type ConcreteLunrQuery = {
documentTypes: string[];
};
+type LunrResultEnvelope = {
+ result: lunr.Index.Result;
+ type: string;
+};
+
+type LunrQueryTranslator = (query: SearchQuery) => ConcreteLunrQuery;
+
export class LunrSearchEngine implements SearchEngine {
protected lunrIndices: Record = {};
protected docStore: Record;
@@ -38,24 +45,49 @@ export class LunrSearchEngine implements SearchEngine {
this.docStore = {};
}
- translator: QueryTranslator = ({
+ protected translator: QueryTranslator = ({
term,
filters,
types,
}: SearchQuery): ConcreteLunrQuery => {
let lunrQueryFilters;
+ const lunrTerm = term ? `+${term}` : '';
if (filters) {
lunrQueryFilters = Object.entries(filters)
- .map(([key, value]) => ` +${key}:${value}`)
+ .map(([field, value]) => {
+ // Require that the given field has the given value (with +).
+ if (['string', 'number', 'boolean'].includes(typeof value)) {
+ return ` +${field}:${value}`;
+ }
+
+ // Illustrate how multi-value filters could work.
+ if (Array.isArray(value)) {
+ // But warn that Lurn supports this poorly.
+ this.logger.warn(
+ `Non-scalar filter value used for field ${field}. Consider using a different Search Engine for better results.`,
+ );
+ return ` ${value.map(v => {
+ return `${field}:${v}`;
+ })}`;
+ }
+
+ // Log a warning or something about unknown filter value
+ this.logger.warn(`Unknown filter type used on field ${field}`);
+ return '';
+ })
.join('');
}
return {
- lunrQueryString: `${term}${lunrQueryFilters || ''}`,
+ lunrQueryString: `${lunrTerm}${lunrQueryFilters || ''}`,
documentTypes: types || ['*'],
};
};
+ setTranslator(translator: LunrQueryTranslator) {
+ this.translator = translator;
+ }
+
index(type: string, documents: IndexableDocument[]): void {
const lunrBuilder = new lunr.Builder();
// Make this lunr index aware of all relevant fields.
@@ -83,13 +115,20 @@ export class LunrSearchEngine implements SearchEngine {
query,
) as ConcreteLunrQuery;
- const results: lunr.Index.Result[] = [];
+ const results: LunrResultEnvelope[] = [];
if (documentTypes.length === 1 && documentTypes[0] === '*') {
// Iterate over all this.lunrIndex values.
- Object.values(this.lunrIndices).forEach(i => {
+ Object.keys(this.lunrIndices).forEach(type => {
try {
- results.push(...i.search(lunrQueryString));
+ results.push(
+ ...this.lunrIndices[type].search(lunrQueryString).map(result => {
+ return {
+ result: result,
+ type: type,
+ };
+ }),
+ );
} catch (err) {
// if a field does not exist on a index, we can see that as a no-match
if (
@@ -102,10 +141,17 @@ export class LunrSearchEngine implements SearchEngine {
} else {
// Iterate over the filtered list of this.lunrIndex keys.
Object.keys(this.lunrIndices)
- .filter(d => documentTypes.includes(d))
- .forEach(d => {
+ .filter(type => documentTypes.includes(type))
+ .forEach(type => {
try {
- results.push(...this.lunrIndices[d].search(lunrQueryString));
+ results.push(
+ ...this.lunrIndices[type].search(lunrQueryString).map(result => {
+ return {
+ result: result,
+ type: type,
+ };
+ }),
+ );
} catch (err) {
// if a field does not exist on a index, we can see that as a no-match
if (
@@ -119,16 +165,16 @@ export class LunrSearchEngine implements SearchEngine {
// Sort results.
results.sort((doc1, doc2) => {
- return doc2.score - doc1.score;
+ return doc2.result.score - doc1.result.score;
});
// Translate results into SearchResultSet
- const resultSet: SearchResultSet = {
+ const realResultSet: SearchResultSet = {
results: results.map(d => {
- return { document: this.docStore[d.ref] };
+ return { type: d.type, document: this.docStore[d.result.ref] };
}),
};
- return Promise.resolve(resultSet);
+ return Promise.resolve(realResultSet);
}
}
diff --git a/plugins/search-backend-node/src/types.ts b/plugins/search-backend-node/src/types.ts
index 4abfa77fb1..e7643ee5aa 100644
--- a/plugins/search-backend-node/src/types.ts
+++ b/plugins/search-backend-node/src/types.ts
@@ -26,11 +26,6 @@ import {
* Parameters required to register a collator.
*/
export interface RegisterCollatorParameters {
- /**
- * The type of document to be indexed (used to name indices, to configure refresh loop, etc).
- */
- type: string;
-
/**
* The default interval (in seconds) that the provided collator will be called (can be overridden in config).
*/
@@ -50,12 +45,6 @@ export interface RegisterDecoratorParameters {
* The decorator class responsible for appending or modifying documents of the given type(s).
*/
decorator: DocumentDecorator;
-
- /**
- * (Optional) An array of document types that the given decorator should apply to. If none are provided,
- * the decorator will be applied to all types.
- */
- types?: string[];
}
/**
@@ -70,7 +59,18 @@ export type QueryTranslator = (query: SearchQuery) => unknown;
* concrete, search engine-specific queries.
*/
export interface SearchEngine {
- translator: QueryTranslator;
+ /**
+ * Override the default translator provided by the SearchEngine.
+ */
+ setTranslator(translator: QueryTranslator): void;
+
+ /**
+ * Add the given documents to the SearchEngine index of the given type.
+ */
index(type: string, documents: IndexableDocument[]): void;
+
+ /**
+ * Perform a search query against the SearchEngine.
+ */
query(query: SearchQuery): Promise;
}
diff --git a/plugins/search/package.json b/plugins/search/package.json
index aca7ba3868..a74091e497 100644
--- a/plugins/search/package.json
+++ b/plugins/search/package.json
@@ -33,23 +33,28 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/plugin-catalog-react": "^0.2.0",
"@backstage/search-common": "^0.1.1",
- "@backstage/theme": "^0.2.7",
+ "@backstage/config": "^0.1.5",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
+ "@types/react": "^16.9",
"qs": "^6.9.4",
"react": "^16.13.1",
"react-dom": "^16.13.1",
+ "react-router": "6.0.0-beta.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
+ "@testing-library/react-hooks": "^3.4.2",
"@testing-library/user-event": "^13.1.8",
+ "@types/react": "^16.9",
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32",
"cross-fetch": "^3.0.6",
diff --git a/plugins/search/src/apis.test.ts b/plugins/search/src/apis.test.ts
new file mode 100644
index 0000000000..32ec0f0d0e
--- /dev/null
+++ b/plugins/search/src/apis.test.ts
@@ -0,0 +1,55 @@
+/*
+ * 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 { CatalogApi } from '@backstage/plugin-catalog-react';
+
+import { SearchClient } from './apis';
+
+describe('apis', () => {
+ const query = {
+ term: '',
+ filters: {},
+ types: [],
+ pageCursor: '',
+ };
+
+ const baseUrl = 'https://base-url.com/';
+ const getBaseUrl = jest.fn().mockResolvedValue(baseUrl);
+ const client = new SearchClient({
+ catalogApi: {} as CatalogApi,
+ discoveryApi: { getBaseUrl },
+ });
+
+ const json = jest.fn();
+ const originalFetch = window.fetch;
+ window.fetch = jest.fn().mockResolvedValue({ json });
+
+ afterAll(() => {
+ window.fetch = originalFetch;
+ });
+
+ it('Fetch is called with expected URL (including stringified Q params)', async () => {
+ await client._alphaPerformSearch(query);
+ expect(getBaseUrl).toHaveBeenLastCalledWith('search/query');
+ expect(fetch).toHaveBeenLastCalledWith(`${baseUrl}?term=&pageCursor=`);
+ });
+
+ it('Resolves JSON from fetch response', async () => {
+ const result = { loading: false, error: '', value: {} };
+ json.mockReturnValueOnce(result);
+ expect(await client._alphaPerformSearch(query)).toStrictEqual(result);
+ });
+});
diff --git a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.test.jsx b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.test.jsx
new file mode 100644
index 0000000000..60ad453597
--- /dev/null
+++ b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.test.jsx
@@ -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.
+ */
+
+import React from 'react';
+import { screen } from '@testing-library/react';
+import { renderInTestApp } from '@backstage/test-utils';
+
+import { DefaultResultListItem } from './DefaultResultListItem';
+
+describe('DefaultResultListItem', () => {
+ const result = {
+ title: 'title',
+ text: 'text',
+ location: '/location',
+ };
+
+ it('Links to result.location', async () => {
+ await renderInTestApp();
+ expect(screen.getByRole('link')).toHaveAttribute('href', result.location);
+ });
+
+ it('Includes primary/secondary text (title / text)', async () => {
+ await renderInTestApp();
+ expect(screen.getByRole('listitem')).toHaveTextContent(
+ result.title + result.text,
+ );
+ });
+});
diff --git a/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.tsx
new file mode 100644
index 0000000000..5f2d4bf87f
--- /dev/null
+++ b/plugins/search/src/components/DefaultResultListItem/DefaultResultListItem.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 { Link } from '@backstage/core';
+import { IndexableDocument } from '@backstage/search-common';
+import { ListItem, ListItemText, Divider } from '@material-ui/core';
+
+type Props = {
+ result: IndexableDocument;
+};
+
+export const DefaultResultListItem = ({ result }: Props) => {
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/search/src/components/DefaultResultListItem/index.ts b/plugins/search/src/components/DefaultResultListItem/index.ts
new file mode 100644
index 0000000000..7562aff1af
--- /dev/null
+++ b/plugins/search/src/components/DefaultResultListItem/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 { DefaultResultListItem } from './DefaultResultListItem';
diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx
new file mode 100644
index 0000000000..a5075c5593
--- /dev/null
+++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.test.tsx
@@ -0,0 +1,153 @@
+/*
+ * 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 { screen, render, waitFor, act } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { useApi } from '@backstage/core';
+
+import { SearchContextProvider } from '../SearchContext';
+import { SearchBarNext } from './SearchBarNext';
+
+jest.mock('@backstage/core', () => ({
+ ...jest.requireActual('@backstage/core'),
+ useApi: jest.fn().mockReturnValue({}),
+}));
+
+describe('SearchBarNext', () => {
+ const initialState = {
+ term: '',
+ pageCursor: '',
+ filters: {},
+ types: ['*'],
+ };
+
+ const name = 'Search term';
+ const term = 'term';
+
+ const _alphaPerformSearch = jest.fn().mockResolvedValue({});
+ (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch });
+
+ afterAll(() => {
+ jest.resetAllMocks();
+ });
+
+ it('Renders without exploding', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox', { name })).toBeInTheDocument();
+ });
+ });
+
+ it('Renders based on initial search', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox', { name })).toHaveValue(term);
+ });
+ });
+
+ it('Updates term state when text is entered', async () => {
+ render(
+
+
+ ,
+ );
+
+ const textbox = screen.getByRole('textbox', { name });
+
+ const value = 'value';
+
+ userEvent.type(textbox, value);
+
+ await waitFor(() => {
+ expect(textbox).toHaveValue(value);
+ });
+
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({ term: value }),
+ );
+ });
+
+ it('Clear button clears term state', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox', { name })).toHaveValue(term);
+ });
+
+ userEvent.click(screen.getByRole('button', { name: 'Clear term' }));
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox', { name })).toHaveValue('');
+ });
+
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({ term: '' }),
+ );
+ });
+
+ it('Adheres to provided debounceTime', async () => {
+ jest.useFakeTimers();
+
+ const debounceTime = 600;
+
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByRole('textbox', { name })).toBeInTheDocument();
+ });
+
+ const textbox = screen.getByRole('textbox', { name });
+
+ const value = 'value';
+
+ userEvent.type(textbox, value);
+
+ expect(_alphaPerformSearch).not.toHaveBeenLastCalledWith(
+ expect.objectContaining({ term: value }),
+ );
+
+ act(() => {
+ jest.advanceTimersByTime(debounceTime);
+ });
+
+ await waitFor(() => {
+ expect(textbox).toHaveValue(value);
+ });
+
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({ term: value }),
+ );
+ });
+});
diff --git a/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx b/plugins/search/src/components/SearchBarNext/SearchBarNext.tsx
new file mode 100644
index 0000000000..76bc9bf4f9
--- /dev/null
+++ b/plugins/search/src/components/SearchBarNext/SearchBarNext.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, { ChangeEvent, useState } from 'react';
+import { useDebounce } from 'react-use';
+import { InputBase, InputAdornment, IconButton } from '@material-ui/core';
+import SearchIcon from '@material-ui/icons/Search';
+import ClearButton from '@material-ui/icons/Clear';
+
+import { useSearch } from '../SearchContext';
+
+type Props = {
+ className?: string;
+ debounceTime?: number;
+};
+
+export const SearchBarNext = ({ className, debounceTime = 0 }: Props) => {
+ const { term, setTerm } = useSearch();
+ const [value, setValue] = useState(term);
+
+ useDebounce(() => setTerm(value), debounceTime, [value]);
+
+ const handleQuery = (e: ChangeEvent) => {
+ setValue(e.target.value);
+ };
+
+ const handleClear = () => setValue('');
+
+ return (
+
+
+
+
+
+ }
+ endAdornment={
+
+
+
+
+
+ }
+ />
+ );
+};
diff --git a/plugins/search/src/components/SearchBarNext/index.tsx b/plugins/search/src/components/SearchBarNext/index.tsx
new file mode 100644
index 0000000000..5f46a6dda6
--- /dev/null
+++ b/plugins/search/src/components/SearchBarNext/index.tsx
@@ -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 { SearchBarNext } from './SearchBarNext';
diff --git a/plugins/search/src/components/SearchContext/SearchContext.test.tsx b/plugins/search/src/components/SearchContext/SearchContext.test.tsx
new file mode 100644
index 0000000000..1b79e62698
--- /dev/null
+++ b/plugins/search/src/components/SearchContext/SearchContext.test.tsx
@@ -0,0 +1,219 @@
+/*
+ * 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 { render, screen, waitFor } from '@testing-library/react';
+import { renderHook, act } from '@testing-library/react-hooks';
+
+import { useApi } from '@backstage/core';
+
+import { useSearch, SearchContextProvider } from './SearchContext';
+
+jest.mock('@backstage/core', () => ({
+ ...jest.requireActual('@backstage/core'),
+ useApi: jest.fn(),
+}));
+
+describe('SearchContext', () => {
+ const _alphaPerformSearch = jest.fn();
+
+ const wrapper = ({ children, initialState }: any) => (
+
+ {children}
+
+ );
+
+ const initialState = {
+ term: '',
+ pageCursor: '',
+ filters: {},
+ types: ['*'],
+ };
+
+ beforeEach(() => {
+ _alphaPerformSearch.mockResolvedValue({});
+ (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch });
+ });
+
+ afterAll(() => {
+ jest.resetAllMocks();
+ });
+
+ it('Passes children', async () => {
+ const text = 'text';
+
+ render(
+
+ {text}
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(text)).toBeInTheDocument();
+ });
+ });
+
+ it('Throws error when no context is set', () => {
+ const { result } = renderHook(() => useSearch());
+
+ expect(result.error).toEqual(
+ Error('useSearch must be used within a SearchContextProvider'),
+ );
+ });
+
+ it('Uses initial state values', async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
+ wrapper,
+ initialProps: {
+ initialState,
+ },
+ });
+
+ await waitForNextUpdate();
+
+ expect(result.current).toEqual(expect.objectContaining(initialState));
+ });
+
+ it('Resets cursor when term is set (and different from previous)', async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
+ wrapper,
+ initialProps: {
+ initialState: {
+ ...initialState,
+ pageCursor: '1',
+ },
+ },
+ });
+
+ await waitForNextUpdate();
+
+ expect(result.current.pageCursor).toBe('1');
+
+ act(() => {
+ result.current.setTerm('first term');
+ });
+
+ await waitForNextUpdate();
+
+ expect(result.current.pageCursor).toBe('1');
+
+ act(() => {
+ result.current.setTerm('second term');
+ });
+
+ await waitForNextUpdate();
+
+ expect(result.current.pageCursor).toBe('');
+ });
+
+ describe('Performs search (and sets results)', () => {
+ it('When term is set', async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
+ wrapper,
+ initialProps: {
+ initialState,
+ },
+ });
+
+ await waitForNextUpdate();
+
+ const term = 'term';
+
+ act(() => {
+ result.current.setTerm(term);
+ });
+
+ await waitForNextUpdate();
+
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith({
+ ...initialState,
+ term,
+ });
+ });
+
+ it('When filters are set', async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
+ wrapper,
+ initialProps: {
+ initialState,
+ },
+ });
+
+ await waitForNextUpdate();
+
+ const filters = { filter: 'filter' };
+
+ act(() => {
+ result.current.setFilters(filters);
+ });
+
+ await waitForNextUpdate();
+
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith({
+ ...initialState,
+ filters,
+ });
+ });
+
+ it('When pageCursor is set', async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
+ wrapper,
+ initialProps: {
+ initialState,
+ },
+ });
+
+ await waitForNextUpdate();
+
+ const pageCursor = 'pageCursor';
+
+ act(() => {
+ result.current.setPageCursor(pageCursor);
+ });
+
+ await waitForNextUpdate();
+
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith({
+ ...initialState,
+ pageCursor,
+ });
+ });
+
+ it('When types is set', async () => {
+ const { result, waitForNextUpdate } = renderHook(() => useSearch(), {
+ wrapper,
+ initialProps: {
+ initialState,
+ },
+ });
+
+ await waitForNextUpdate();
+
+ const types = ['type'];
+
+ act(() => {
+ result.current.setTypes(types);
+ });
+
+ await waitForNextUpdate();
+
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith({
+ ...initialState,
+ types,
+ });
+ });
+ });
+});
diff --git a/plugins/search/src/components/SearchContext/SearchContext.tsx b/plugins/search/src/components/SearchContext/SearchContext.tsx
new file mode 100644
index 0000000000..55ccd096a1
--- /dev/null
+++ b/plugins/search/src/components/SearchContext/SearchContext.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, {
+ PropsWithChildren,
+ createContext,
+ useContext,
+ useState,
+ useEffect,
+} from 'react';
+import { useAsync, usePrevious } from 'react-use';
+import { useApi } from '@backstage/core';
+import { SearchResultSet } from '@backstage/search-common';
+import { searchApiRef } from '../../apis';
+import { AsyncState } from 'react-use/lib/useAsync';
+import { JsonObject } from '@backstage/config';
+
+type SearchContextValue = {
+ result: AsyncState;
+ term: string;
+ setTerm: React.Dispatch>;
+ types: string[];
+ setTypes: React.Dispatch>;
+ filters: JsonObject;
+ setFilters: React.Dispatch>;
+ pageCursor: string;
+ setPageCursor: React.Dispatch>;
+};
+
+type SettableSearchContext = Omit<
+ SearchContextValue,
+ 'result' | 'setTerm' | 'setTypes' | 'setFilters' | 'setPageCursor'
+>;
+
+const SearchContext = createContext(undefined);
+
+export const SearchContextProvider = ({
+ initialState = {
+ term: '',
+ pageCursor: '',
+ filters: {},
+ types: ['*'],
+ },
+ children,
+}: PropsWithChildren<{ initialState?: SettableSearchContext }>) => {
+ const searchApi = useApi(searchApiRef);
+ const [pageCursor, setPageCursor] = useState(initialState.pageCursor);
+ const [filters, setFilters] = useState(initialState.filters);
+ const [term, setTerm] = useState(initialState.term);
+ const [types, setTypes] = useState(initialState.types);
+ const prevTerm = usePrevious(term);
+
+ const result = useAsync(
+ () =>
+ searchApi._alphaPerformSearch({
+ term,
+ filters,
+ pageCursor,
+ types,
+ }),
+ [term, filters, types, pageCursor],
+ );
+
+ useEffect(() => {
+ // Any time a term is reset, we want to start from page 0.
+ if (term && prevTerm && term !== prevTerm) {
+ setPageCursor('');
+ }
+ }, [term, prevTerm]);
+
+ const value: SearchContextValue = {
+ result,
+ filters,
+ setFilters,
+ term,
+ setTerm,
+ types,
+ setTypes,
+ pageCursor,
+ setPageCursor,
+ };
+
+ return ;
+};
+
+export const useSearch = () => {
+ const context = useContext(SearchContext);
+ if (context === undefined) {
+ throw new Error('useSearch must be used within a SearchContextProvider');
+ }
+ return context;
+};
diff --git a/plugins/search/src/components/SearchContext/index.tsx b/plugins/search/src/components/SearchContext/index.tsx
new file mode 100644
index 0000000000..b45c169879
--- /dev/null
+++ b/plugins/search/src/components/SearchContext/index.tsx
@@ -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 { SearchContextProvider, useSearch } from './SearchContext';
diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx
new file mode 100644
index 0000000000..1a35bf54d4
--- /dev/null
+++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.test.tsx
@@ -0,0 +1,375 @@
+/*
+ * 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 { screen, render, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { useApi } from '@backstage/core';
+
+import { SearchFilterNext } from './SearchFilterNext';
+import { SearchContextProvider } from '../SearchContext';
+
+jest.mock('@backstage/core', () => ({
+ ...jest.requireActual('@backstage/core'),
+ useApi: jest.fn().mockReturnValue({}),
+}));
+
+describe('SearchFilterNext', () => {
+ const initialState = {
+ term: '',
+ filters: {},
+ types: [],
+ pageCursor: '',
+ };
+
+ const name = 'field';
+ const values = ['value1', 'value2'];
+ const filters = { unrelated: 'unrelated' };
+
+ const _alphaPerformSearch = jest.fn().mockResolvedValue({});
+ (useApi as jest.Mock).mockReturnValue({ _alphaPerformSearch });
+
+ afterAll(() => {
+ jest.resetAllMocks();
+ });
+
+ it('Check that element was rendered and received props', async () => {
+ const CustomFilter = (props: { name: string }) => {props.name}
;
+
+ render();
+
+ expect(screen.getByRole('heading', { name })).toBeInTheDocument();
+ });
+
+ describe('Checkbox', () => {
+ it('Renders field name and values when provided as props', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ expect(
+ screen.getByRole('checkbox', { name: values[0] }),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('checkbox', { name: values[1] }),
+ ).toBeInTheDocument();
+ });
+
+ it('Renders correctly based on filter state', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ expect(
+ screen.getByRole('checkbox', { name: values[0] }),
+ ).not.toBeChecked();
+ expect(screen.getByRole('checkbox', { name: values[1] })).toBeChecked();
+ });
+
+ it('Renders correctly based on defaultValue', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ expect(screen.getByRole('checkbox', { name: values[0] })).toBeChecked();
+ expect(
+ screen.getByRole('checkbox', { name: values[1] }),
+ ).not.toBeChecked();
+ });
+
+ it('Checking / unchecking a value sets filter state', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ const checkBox = screen.getByRole('checkbox', { name: values[0] });
+
+ // Check the box.
+ userEvent.click(checkBox);
+ await waitFor(() => {
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({ filters: { field: [values[0]] } }),
+ );
+ });
+
+ // Uncheck the box.
+ userEvent.click(checkBox);
+ await waitFor(() => {
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({ filters: {} }),
+ );
+ });
+ });
+
+ it('Checking / unchecking a value maintains unrelated filter state', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ const checkBox = screen.getByRole('checkbox', { name: values[0] });
+
+ // Check the box.
+ userEvent.click(checkBox);
+ await waitFor(() => {
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ filters: { ...filters, field: [values[0]] },
+ }),
+ );
+ });
+
+ // Uncheck the box.
+ userEvent.click(checkBox);
+ await waitFor(() => {
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({ filters }),
+ );
+ });
+ });
+ });
+
+ describe('Select', () => {
+ it('Renders field name and values when provided as props', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ userEvent.click(screen.getByRole('button'));
+
+ await waitFor(() => {
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
+ });
+
+ expect(
+ screen.getByRole('option', { name: values[0] }),
+ ).toBeInTheDocument();
+ expect(
+ screen.getByRole('option', { name: values[1] }),
+ ).toBeInTheDocument();
+ });
+
+ it('Renders correctly based on filter state', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ userEvent.click(screen.getByRole('button'));
+
+ await waitFor(() => {
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
+ });
+
+ expect(screen.getByRole('option', { name: values[0] })).toHaveAttribute(
+ 'aria-selected',
+ 'true',
+ );
+ expect(
+ screen.getByRole('option', { name: values[1] }),
+ ).not.toHaveAttribute('aria-selected');
+ expect(screen.getByRole('option', { name: 'All' })).not.toHaveAttribute(
+ 'aria-selected',
+ );
+ });
+
+ it('Renders correctly based on defaultValue', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ userEvent.click(screen.getByRole('button'));
+
+ await waitFor(() => {
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
+ });
+
+ expect(screen.getByRole('option', { name: values[0] })).toHaveAttribute(
+ 'aria-selected',
+ 'true',
+ );
+ expect(
+ screen.getByRole('option', { name: values[1] }),
+ ).not.toHaveAttribute('aria-selected');
+ expect(screen.getByRole('option', { name: 'All' })).not.toHaveAttribute(
+ 'aria-selected',
+ );
+ });
+
+ it('Selecting a value sets filter state', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ const button = screen.getByRole('button');
+
+ userEvent.click(button);
+
+ await waitFor(() => {
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
+ });
+
+ userEvent.click(screen.getByRole('option', { name: values[0] }));
+
+ await waitFor(() => {
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ filters: { [name]: values[0] },
+ }),
+ );
+ });
+
+ userEvent.click(button);
+
+ await waitFor(() => {
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
+ });
+
+ userEvent.click(screen.getByRole('option', { name: 'All' }));
+
+ await waitFor(() => {
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ filters: {},
+ }),
+ );
+ });
+ });
+
+ it('Selecting a value maintains unrelated filter state', async () => {
+ render(
+
+
+ ,
+ );
+
+ await waitFor(() => {
+ expect(screen.getByText(name)).toBeInTheDocument();
+ });
+
+ const button = screen.getByRole('button');
+
+ userEvent.click(button);
+
+ await waitFor(() => {
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
+ });
+
+ userEvent.click(screen.getByRole('option', { name: values[0] }));
+
+ await waitFor(() => {
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({
+ filters: { ...filters, [name]: values[0] },
+ }),
+ );
+ });
+
+ userEvent.click(button);
+
+ await waitFor(() => {
+ expect(screen.getByRole('listbox')).toBeInTheDocument();
+ });
+
+ userEvent.click(screen.getByRole('option', { name: 'All' }));
+
+ await waitFor(() => {
+ expect(_alphaPerformSearch).toHaveBeenLastCalledWith(
+ expect.objectContaining({ filters }),
+ );
+ });
+ });
+ });
+});
diff --git a/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx
new file mode 100644
index 0000000000..7beef58bc9
--- /dev/null
+++ b/plugins/search/src/components/SearchFilterNext/SearchFilterNext.tsx
@@ -0,0 +1,179 @@
+/*
+ * 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, { ReactElement, ChangeEvent, useEffect } from 'react';
+import {
+ makeStyles,
+ FormControl,
+ FormControlLabel,
+ InputLabel,
+ Checkbox,
+ Select,
+ MenuItem,
+ FormLabel,
+} from '@material-ui/core';
+
+import { useSearch } from '../SearchContext';
+
+const useStyles = makeStyles({
+ label: {
+ textTransform: 'capitalize',
+ },
+});
+
+export type Component = {
+ className?: string;
+ name: string;
+ values?: string[];
+ defaultValue?: string[] | string | null;
+};
+
+export type Props = Component & {
+ component: (props: Component) => ReactElement;
+ debug?: boolean;
+};
+
+const CheckboxFilter = ({
+ className,
+ name,
+ defaultValue,
+ values = [],
+}: Component) => {
+ const classes = useStyles();
+ const { filters, setFilters } = useSearch();
+
+ useEffect(() => {
+ if (Array.isArray(defaultValue)) {
+ setFilters(prevFilters => ({
+ ...prevFilters,
+ [name]: defaultValue,
+ }));
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const handleChange = (e: ChangeEvent) => {
+ const {
+ target: { value, checked },
+ } = e;
+
+ setFilters(prevFilters => {
+ const { [name]: filter, ...others } = prevFilters;
+ const rest = ((filter as string[]) || []).filter(i => i !== value);
+ const items = checked ? [...rest, value] : rest;
+ return items.length ? { ...others, [name]: items } : others;
+ });
+ };
+
+ return (
+
+ {name}
+ {values.map((value: string) => (
+
+ }
+ label={value}
+ />
+ ))}
+
+ );
+};
+
+const SelectFilter = ({
+ className,
+ name,
+ defaultValue,
+ values = [],
+}: Component) => {
+ const classes = useStyles();
+ const { filters, setFilters } = useSearch();
+
+ useEffect(() => {
+ if (typeof defaultValue === 'string') {
+ setFilters(prevFilters => ({
+ ...prevFilters,
+ [name]: defaultValue,
+ }));
+ }
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ const handleChange = (e: ChangeEvent<{ value: unknown }>) => {
+ const {
+ target: { value },
+ } = e;
+
+ setFilters(prevFilters => {
+ const { [name]: filter, ...others } = prevFilters;
+ return value ? { ...others, [name]: value as string } : others;
+ });
+ };
+
+ return (
+
+
+ {name}
+
+
+
+ );
+};
+
+const SearchFilterNext = ({ component: Element, ...props }: Props) => (
+
+);
+
+SearchFilterNext.Checkbox = (props: Omit & Component) => (
+
+);
+
+SearchFilterNext.Select = (props: Omit & Component) => (
+
+);
+
+export { SearchFilterNext };
diff --git a/plugins/search/src/components/SearchFilterNext/index.ts b/plugins/search/src/components/SearchFilterNext/index.ts
new file mode 100644
index 0000000000..322abe64d7
--- /dev/null
+++ b/plugins/search/src/components/SearchFilterNext/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 { SearchFilterNext } from './SearchFilterNext';
diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx
new file mode 100644
index 0000000000..5890f8cf41
--- /dev/null
+++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.test.tsx
@@ -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 { renderInTestApp } from '@backstage/test-utils';
+import { useLocation, Outlet } from 'react-router';
+
+import { useSearch, SearchContextProvider } from '../SearchContext';
+import { SearchPageNext } from './';
+
+jest.mock('react-router', () => ({
+ ...jest.requireActual('react-router'),
+ useLocation: jest.fn().mockReturnValue({
+ search: '',
+ }),
+ Outlet: jest.fn().mockReturnValue(null),
+}));
+
+jest.mock('../SearchContext', () => ({
+ ...jest.requireActual('../SearchContext'),
+ SearchContextProvider: jest
+ .fn()
+ .mockImplementation(({ children }) => children),
+ useSearch: jest.fn().mockReturnValue({
+ term: '',
+ types: [],
+ filters: {},
+ pageCursor: '',
+ }),
+}));
+
+describe('SearchPage', () => {
+ const origReplaceState = window.history.replaceState;
+
+ beforeEach(() => {
+ window.history.replaceState = jest.fn();
+ });
+
+ afterEach(() => {
+ window.history.replaceState = origReplaceState;
+ });
+
+ it('uses initial term state from location', async () => {
+ // Given this initial location.search value...
+ const expectedFilterField = 'anyKey';
+ const expectedFilterValue = 'anyValue';
+ const expectedTerm = 'justin bieber';
+ const expectedTypes = ['software-catalog'];
+ const expectedFilters = { [expectedFilterField]: expectedFilterValue };
+ const expectedPageCursor = 'page2-or-something';
+
+ // e.g. ?query=petstore&pageCursor=1&filters[lifecycle][]=experimental&filters[kind]=Component
+ (useLocation as jest.Mock).mockReturnValueOnce({
+ search: `?query=${expectedTerm}&types[]=${expectedTypes[0]}&filters[${expectedFilterField}]=${expectedFilterValue}&pageCursor=${expectedPageCursor}`,
+ });
+
+ // When we render the page...
+ await renderInTestApp();
+
+ // Then search context should be initialized with these values...
+ const calls = (SearchContextProvider as jest.Mock).mock.calls[0];
+ const actualInitialState = calls[0].initialState;
+ expect(actualInitialState.term).toEqual(expectedTerm);
+ expect(actualInitialState.types).toEqual(expectedTypes);
+ expect(actualInitialState.pageCursor).toEqual(expectedPageCursor);
+ expect(actualInitialState.filters).toStrictEqual(expectedFilters);
+ });
+
+ it('renders provided router element', async () => {
+ await renderInTestApp();
+
+ expect(Outlet).toHaveBeenCalled();
+ });
+
+ it('replaces window history with expected query parameters', async () => {
+ (useSearch as jest.Mock).mockReturnValueOnce({
+ term: 'bieber',
+ types: ['software-catalog'],
+ pageCursor: 'page2-or-something',
+ filters: { anyKey: 'anyValue' },
+ });
+ const expectedLocation = encodeURI(
+ '?query=bieber&types[]=software-catalog&pageCursor=page2-or-something&filters[anyKey]=anyValue',
+ );
+
+ await renderInTestApp();
+
+ const calls = (window.history.replaceState as jest.Mock).mock.calls[0];
+ expect(calls[2]).toContain(expectedLocation);
+ });
+});
diff --git a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx
index a6655e37ac..f851778ddf 100644
--- a/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx
+++ b/plugins/search/src/components/SearchPageNext/SearchPageNext.tsx
@@ -13,59 +13,55 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import {
- Content,
- Header,
- Lifecycle,
- Page,
- useQueryParamState,
-} from '@backstage/core';
-import { Grid } from '@material-ui/core';
-import React, { useEffect, useState } from 'react';
-import { useDebounce } from 'react-use';
-import { SearchBar } from '../SearchBar';
-import { SearchResult } from '../SearchResult';
+
+import React from 'react';
+import qs from 'qs';
+import { Outlet, useLocation } from 'react-router';
+import { SearchContextProvider, useSearch } from '../SearchContext';
+import { JsonObject } from '@backstage/config';
+
+export const UrlUpdater = () => {
+ const { term, types, pageCursor, filters } = useSearch();
+
+ const newParams = qs.stringify(
+ {
+ query: term,
+ types,
+ pageCursor,
+ filters,
+ },
+ { arrayFormat: 'brackets' },
+ );
+ const newUrl = `${window.location.pathname}?${newParams}`;
+
+ // We directly manipulate window history here in order to not re-render
+ // infinitely (state => location => state => etc). The intention of this
+ // code is just to ensure the right query/filters are loaded when a user
+ // clicks the "back" button after clicking a result.
+ window.history.replaceState(null, document.title, newUrl);
+
+ return null;
+};
export const SearchPageNext = () => {
- const [queryString, setQueryString] = useQueryParamState('query');
- const [searchQuery, setSearchQuery] = useState(queryString ?? '');
+ const location = useLocation();
+ const query = qs.parse(location.search.substring(1), { arrayLimit: 0 }) || {};
+ const filters = (query.filters as JsonObject) || {};
+ const queryString = (query.query as string) || '';
+ const pageCursor = (query.pageCursor as string) || '';
+ const types = (query.types as string[]) || [];
- const handleSearch = (event: React.ChangeEvent) => {
- event.preventDefault();
- setSearchQuery(event.target.value);
- };
-
- useEffect(() => setSearchQuery(queryString ?? ''), [queryString]);
-
- useDebounce(
- () => {
- setQueryString(searchQuery);
- },
- 200,
- [searchQuery],
- );
-
- const handleClearSearchBar = () => {
- setSearchQuery('');
+ const initialState = {
+ term: queryString || '',
+ types,
+ pageCursor,
+ filters,
};
return (
-
- } />
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
);
};
diff --git a/plugins/search/src/components/SearchResultNext/SearchResultNext.test.tsx b/plugins/search/src/components/SearchResultNext/SearchResultNext.test.tsx
new file mode 100644
index 0000000000..98fc285265
--- /dev/null
+++ b/plugins/search/src/components/SearchResultNext/SearchResultNext.test.tsx
@@ -0,0 +1,92 @@
+/*
+ * 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 { render, waitFor } from '@testing-library/react';
+
+import { SearchResultNext } from './SearchResultNext';
+import { useSearch } from '../SearchContext';
+
+jest.mock('../SearchContext', () => ({
+ ...jest.requireActual('../SearchContext'),
+ useSearch: jest.fn().mockReturnValue({
+ result: {},
+ }),
+}));
+
+describe('SearchResultNext', () => {
+ it('Progress rendered on Loading state', async () => {
+ (useSearch as jest.Mock).mockReturnValueOnce({
+ result: { loading: true },
+ });
+
+ const { getByRole } = render(
+ {() => <>>},
+ );
+
+ await waitFor(() => {
+ expect(getByRole('progressbar')).toBeInTheDocument();
+ });
+ });
+
+ it('Alert rendered on Error state', async () => {
+ const error = 'error';
+ (useSearch as jest.Mock).mockReturnValueOnce({
+ result: { loading: false, error },
+ });
+
+ const { getByRole } = render(
+ {() => <>>},
+ );
+
+ await waitFor(() => {
+ expect(getByRole('alert')).toHaveTextContent(
+ `Error encountered while fetching search results. ${error}`,
+ );
+ });
+ });
+
+ it('On empty result value state', async () => {
+ (useSearch as jest.Mock).mockReturnValueOnce({
+ result: { loading: false, error: '', value: undefined },
+ });
+
+ const { getByRole } = render(
+ {() => <>>},
+ );
+
+ await waitFor(() => {
+ expect(
+ getByRole('heading', { name: 'Sorry, no results were found' }),
+ ).toBeInTheDocument();
+ });
+ });
+
+ it('Calls children with results set to result.value', () => {
+ (useSearch as jest.Mock).mockReturnValueOnce({
+ result: { loading: false, error: '', value: { results: [] } },
+ });
+
+ render(
+
+ {({ results }) => {
+ expect(results).toEqual([]);
+ return <>>;
+ }}
+ ,
+ );
+ });
+});
diff --git a/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx
new file mode 100644
index 0000000000..84d3759ad3
--- /dev/null
+++ b/plugins/search/src/components/SearchResultNext/SearchResultNext.tsx
@@ -0,0 +1,49 @@
+/*
+ * 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, Progress } from '@backstage/core';
+import { SearchResult } from '@backstage/search-common';
+import { Alert } from '@material-ui/lab';
+
+import { useSearch } from '../SearchContext';
+
+type Props = {
+ children: (results: { results: SearchResult[] }) => JSX.Element;
+};
+
+export const SearchResultNext = ({ children }: Props) => {
+ const {
+ result: { loading, error, value },
+ } = useSearch();
+
+ if (loading) {
+ return ;
+ }
+ if (error) {
+ return (
+
+ Error encountered while fetching search results. {error.toString()}
+
+ );
+ }
+
+ if (!value) {
+ return ;
+ }
+
+ return children({ results: value.results });
+};
diff --git a/plugins/search/src/components/SearchResultNext/index.tsx b/plugins/search/src/components/SearchResultNext/index.tsx
new file mode 100644
index 0000000000..73fabfdc7d
--- /dev/null
+++ b/plugins/search/src/components/SearchResultNext/index.tsx
@@ -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 { SearchResultNext } from './SearchResultNext';
diff --git a/plugins/search/src/components/index.tsx b/plugins/search/src/components/index.tsx
index f571c61373..92e24b60df 100644
--- a/plugins/search/src/components/index.tsx
+++ b/plugins/search/src/components/index.tsx
@@ -15,8 +15,13 @@
*/
export * from './Filters';
+export * from './SearchFilterNext';
export * from './SearchBar';
+export * from './SearchBarNext';
export * from './SearchPage';
export * from './SearchPageNext';
export * from './SearchResult';
+export * from './SearchResultNext';
+export * from './DefaultResultListItem';
export * from './SidebarSearch';
+export * from './SearchContext';
diff --git a/plugins/search/src/index.ts b/plugins/search/src/index.ts
index 519d7fcf66..27fd1706fc 100644
--- a/plugins/search/src/index.ts
+++ b/plugins/search/src/index.ts
@@ -20,12 +20,18 @@ export {
searchPlugin as plugin,
SearchPage,
SearchPageNext,
+ SearchBarNext,
+ SearchResultNext,
+ DefaultResultListItem,
} from './plugin';
export {
Filters,
FiltersButton,
SearchBar,
+ SearchContextProvider,
+ useSearch,
SearchPage as Router,
+ SearchFilterNext,
SearchResult,
SidebarSearch,
} from './components';
diff --git a/plugins/search/src/plugin.ts b/plugins/search/src/plugin.ts
index e3e953e07d..6ff40ebaf7 100644
--- a/plugins/search/src/plugin.ts
+++ b/plugins/search/src/plugin.ts
@@ -19,6 +19,7 @@ import {
createRouteRef,
createRoutableExtension,
discoveryApiRef,
+ createComponentExtension,
} from '@backstage/core';
import { SearchClient, searchApiRef } from './apis';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
@@ -70,3 +71,32 @@ export const SearchPageNext = searchPlugin.provide(
mountPoint: rootNextRouteRef,
}),
);
+
+export const SearchBarNext = searchPlugin.provide(
+ createComponentExtension({
+ component: {
+ lazy: () =>
+ import('./components/SearchBarNext').then(m => m.SearchBarNext),
+ },
+ }),
+);
+
+export const SearchResultNext = searchPlugin.provide(
+ createComponentExtension({
+ component: {
+ lazy: () =>
+ import('./components/SearchResultNext').then(m => m.SearchResultNext),
+ },
+ }),
+);
+
+export const DefaultResultListItem = searchPlugin.provide(
+ createComponentExtension({
+ component: {
+ lazy: () =>
+ import('./components/DefaultResultListItem').then(
+ m => m.DefaultResultListItem,
+ ),
+ },
+ }),
+);
diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json
index aa55f7db84..b028963bc3 100644
--- a/plugins/sentry/package.json
+++ b/plugins/sentry/package.json
@@ -34,7 +34,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -46,9 +46,9 @@
"timeago.js": "^4.0.2"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json
index 3ff01b1fcc..c778cad93a 100644
--- a/plugins/shortcuts/package.json
+++ b/plugins/shortcuts/package.json
@@ -21,7 +21,7 @@
},
"dependencies": {
"@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -35,9 +35,9 @@
"zen-observable": "^0.8.15"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json
index ac8f5a190f..0f4f800d57 100644
--- a/plugins/sonarqube/package.json
+++ b/plugins/sonarqube/package.json
@@ -35,7 +35,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/plugin-catalog-react": "^0.2.0",
"@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -47,9 +47,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json
index f873f75c1e..22e4811f8b 100644
--- a/plugins/splunk-on-call/package.json
+++ b/plugins/splunk-on-call/package.json
@@ -33,7 +33,7 @@
"@backstage/catalog-model": "^0.8.0",
"@backstage/core": "^0.7.11",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -45,9 +45,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md
index 64f255adb1..5a0592b078 100644
--- a/plugins/tech-radar/CHANGELOG.md
+++ b/plugins/tech-radar/CHANGELOG.md
@@ -1,5 +1,45 @@
# @backstage/plugin-tech-radar
+## 0.4.0
+
+### Minor Changes
+
+- 90a505a77: Migrating the Tech Radar to support using `ApiRefs` to load custom data.
+
+ If you had a `getData` function, you'll now need to encapsulate that logic in a class that can override the `techRadarApiRef`.
+
+ ```ts
+ // app/src/lib/MyClient.ts
+ import {
+ TechRadarApi,
+ TechRadarLoaderResponse,
+ } from '@backstage/plugin-tech-radar';
+
+ class MyOwnClient implements TechRadarApi {
+ async load(): Promise {
+ // here's where you would put you logic to load the response that was previously passed into getData
+ }
+ }
+
+ // app/src/apis.ts
+ import { MyOwnClient } from './lib/MyClient';
+ import { techRadarApiRef } from '@backstage/plugin-tech-radar';
+
+ export const apis: AnyApiFactory[] = [
+ /*
+ ...
+ */
+ createApiFactory(techRadarApiRef, new MyOwnClient()),
+ ];
+ ```
+
+### Patch Changes
+
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [1cf1d351f]
+ - @backstage/theme@0.2.8
+ - @backstage/core@0.7.12
+
## 0.3.11
### Patch Changes
diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md
index 814ab6cba5..35c510ddd3 100644
--- a/plugins/tech-radar/README.md
+++ b/plugins/tech-radar/README.md
@@ -60,7 +60,6 @@ export type TechRadarPageProps = TechRadarComponentProps & {
export interface TechRadarPageProps {
width: number;
height: number;
- getData?: () => Promise;
svgProps?: object;
}
```
@@ -73,36 +72,35 @@ export interface TechRadarPageProps {
### How do I load in my own data?
-It's simple, you can pass through a `getData` prop which expects a `Promise` signature.
+The `TechRadar` plugin uses the `techRadarApiRef` to get a client which implements the `TechRadarApi` interface. The default sample one is located [here](https://github.com/backstage/backstage/blob/master/plugins/tech-radar/src/sample.ts). To load your own data, you'll need to provide a class that implements the `TechRadarApi` and override the `techRadarApiRef` in the `app/src/apis.ts`.
-Here's an example:
+```ts
+// app/src/lib/MyClient.ts
+import {
+ TechRadarApi,
+ TechRadarLoaderResponse,
+} from '@backstage/plugin-tech-radar';
-```tsx
-const getHardCodedData = () =>
- Promise.resolve({
- quadrants: [{ id: 'infrastructure', name: 'Infrastructure' }],
- rings: [{ id: 'use', name: 'USE', color: '#93c47d' }],
- entries: [
- {
- url: '#',
- key: 'github-actions',
- id: 'github-actions',
- title: 'GitHub Actions',
- quadrant: 'infrastructure',
- timeline: [
- {
- moved: 0,
- ringId: 'use',
- date: new Date('2020-08-06'),
- description:
- 'Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat',
- },
- ],
- },
- ],
- });
+class MyOwnClient implements TechRadarApi {
+ async load(): Promise {
+ const data = await fetch('https://mydata.json').then(res => res.json());
-;
+ // maybe you'll need to do some data transformation here to make it look like TechRadarLoaderResponse
+
+ return data;
+ }
+}
+
+// app/src/apis.ts
+import { MyOwnClient } from './lib/MyClient';
+import { techRadarApiRef } from '@backstage/plugin-tech-radar';
+
+export const apis: AnyApiFactory[] = [
+ /*
+ ...
+ */
+ createApiFactory(techRadarApiRef, new MyOwnClient()),
+];
```
### How do I write tests?
diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json
index 423d516b6e..73134fb45d 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.11",
+ "version": "0.4.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,8 +30,8 @@
"start": "backstage-cli plugin:serve"
},
"dependencies": {
- "@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/core": "^0.7.12",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -43,9 +43,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/tech-radar/src/api.ts b/plugins/tech-radar/src/api.ts
index eca80420bf..6f2a1a5cb1 100644
--- a/plugins/tech-radar/src/api.ts
+++ b/plugins/tech-radar/src/api.ts
@@ -15,6 +15,17 @@
*/
import { MovedState } from './utils/types';
+import { createApiRef } from '@backstage/core';
+
+export const techRadarApiRef = createApiRef({
+ id: 'plugin.techradar.service',
+ description: 'Used to populate data in the TechRadar plugin',
+});
+
+export interface TechRadarApi {
+ // Loads the TechRadar response data to pass through to the TechRadar component
+ load: () => Promise;
+}
/**
* Types related to the Radar's visualization.
@@ -65,16 +76,5 @@ export interface TechRadarLoaderResponse {
export interface TechRadarComponentProps {
width: number;
height: number;
- getData?: () => Promise;
svgProps?: object;
}
-
-/**
- * Set up the Radar as a Backstage plugin.
- */
-
-export interface TechRadarApi extends TechRadarComponentProps {
- title?: string;
- subtitle?: string;
- pageTitle?: string;
-}
diff --git a/plugins/tech-radar/src/components/RadarComponent.test.tsx b/plugins/tech-radar/src/components/RadarComponent.test.tsx
index 59cababbc9..4b740597b5 100644
--- a/plugins/tech-radar/src/components/RadarComponent.test.tsx
+++ b/plugins/tech-radar/src/components/RadarComponent.test.tsx
@@ -24,6 +24,7 @@ import { withLogCollector } from '@backstage/test-utils';
import GetBBoxPolyfill from '../utils/polyfills/getBBox';
import RadarComponent from './RadarComponent';
+import { TechRadarLoaderResponse, techRadarApiRef, TechRadarApi } from '../api';
describe('RadarComponent', () => {
beforeAll(() => {
@@ -34,13 +35,30 @@ describe('RadarComponent', () => {
GetBBoxPolyfill.remove();
});
+ class MockClient implements TechRadarApi {
+ async load(): Promise {
+ return {
+ entries: [],
+ quadrants: [],
+ rings: [],
+ };
+ }
+ }
+
+ const mockClient = new MockClient();
+
it('should render a progress bar', async () => {
jest.useFakeTimers();
const errorApi = { post: () => {} };
const { getByTestId, queryByTestId } = render(
-
+
{
it('should call the errorApi if load fails', async () => {
const errorApi = { post: jest.fn() };
- const techRadarLoadFail = () =>
- Promise.reject(new Error('404 Page Not Found'));
+ jest
+ .spyOn(mockClient, 'load')
+ .mockRejectedValue(new Error('404 Page Not Found'));
const { queryByTestId } = render(
-
+
diff --git a/plugins/tech-radar/src/components/RadarComponent.tsx b/plugins/tech-radar/src/components/RadarComponent.tsx
index a6e033a08e..39746e48fe 100644
--- a/plugins/tech-radar/src/components/RadarComponent.tsx
+++ b/plugins/tech-radar/src/components/RadarComponent.tsx
@@ -15,38 +15,35 @@
*/
import React, { useEffect } from 'react';
-import { Progress, useApi, errorApiRef, ErrorApi } from '@backstage/core';
+import { Progress, useApi, errorApiRef } from '@backstage/core';
import { useAsync } from 'react-use';
import Radar from '../components/Radar';
-import { TechRadarComponentProps, TechRadarLoaderResponse } from '../api';
-import getSampleData from '../sampleData';
+import {
+ techRadarApiRef,
+ TechRadarComponentProps,
+ TechRadarLoaderResponse,
+} from '../api';
import { Entry } from '../utils/types';
-const useTechRadarLoader = (props: TechRadarComponentProps) => {
- const errorApi = useApi(errorApiRef);
+const useTechRadarLoader = () => {
+ const errorApi = useApi(errorApiRef);
+ const techRadarApi = useApi(techRadarApiRef);
- const { getData } = props;
-
- const state = useAsync(async () => {
- if (getData) {
- const response: TechRadarLoaderResponse = await getData();
- return response;
- }
- return undefined;
- }, [getData, errorApi]);
+ const { error, value, loading } = useAsync(async () => techRadarApi.load(), [
+ techRadarApi,
+ ]);
useEffect(() => {
- const { error } = state;
if (error) {
errorApi.post(error);
}
- }, [errorApi, state]);
+ }, [error, errorApi]);
- return state;
+ return { loading, value, error };
};
const RadarComponent = (props: TechRadarComponentProps): JSX.Element => {
- const { loading, error, value: data } = useTechRadarLoader(props);
+ const { loading, error, value: data } = useTechRadarLoader();
const mapToEntries = (
loaderResponse: TechRadarLoaderResponse | undefined,
@@ -89,8 +86,4 @@ const RadarComponent = (props: TechRadarComponentProps): JSX.Element => {
);
};
-RadarComponent.defaultProps = {
- getData: getSampleData,
-};
-
export default RadarComponent;
diff --git a/plugins/tech-radar/src/components/RadarPage.test.tsx b/plugins/tech-radar/src/components/RadarPage.test.tsx
index 918015001b..b5169fadd9 100644
--- a/plugins/tech-radar/src/components/RadarPage.test.tsx
+++ b/plugins/tech-radar/src/components/RadarPage.test.tsx
@@ -27,6 +27,7 @@ import React from 'react';
import { act } from 'react-dom/test-utils';
import GetBBoxPolyfill from '../utils/polyfills/getBBox';
import { RadarPage } from './RadarPage';
+import { TechRadarLoaderResponse, techRadarApiRef, TechRadarApi } from '../api';
describe('RadarPage', () => {
beforeAll(() => {
@@ -36,6 +37,17 @@ describe('RadarPage', () => {
afterAll(() => {
GetBBoxPolyfill.remove();
});
+ class MockClient implements TechRadarApi {
+ async load(): Promise {
+ return {
+ entries: [],
+ quadrants: [],
+ rings: [],
+ };
+ }
+ }
+
+ const mockClient = new MockClient();
it('should render a progress bar', async () => {
jest.useFakeTimers();
@@ -49,7 +61,9 @@ describe('RadarPage', () => {
const { getByTestId, queryByTestId } = render(
wrapInTestApp(
-
+
+
+
,
),
);
@@ -72,7 +86,9 @@ describe('RadarPage', () => {
const { getByText, getByTestId } = await renderInTestApp(
-
+
+
+
,
);
@@ -86,18 +102,25 @@ describe('RadarPage', () => {
it('should call the errorApi if load fails', async () => {
const errorApi = new MockErrorApi({ collect: true });
- const techRadarLoadFail = () =>
- Promise.reject(new Error('404 Page Not Found'));
+
+ jest
+ .spyOn(mockClient, 'load')
+ .mockRejectedValue(new Error('404 Page Not Found'));
+
const techRadarProps = {
width: 1200,
height: 800,
- getData: techRadarLoadFail,
svgProps: { 'data-testid': 'tech-radar-svg' },
};
const { queryByTestId } = await renderInTestApp(
-
+
,
diff --git a/plugins/tech-radar/src/plugin.ts b/plugins/tech-radar/src/plugin.ts
index 63af1f390f..e128600094 100644
--- a/plugins/tech-radar/src/plugin.ts
+++ b/plugins/tech-radar/src/plugin.ts
@@ -18,8 +18,12 @@ import {
createPlugin,
createRouteRef,
createRoutableExtension,
+ createApiFactory,
} from '@backstage/core';
+import { techRadarApiRef } from './api';
+import { SampleTechRadarApi } from './sample';
+
const rootRouteRef = createRouteRef({
title: 'Tech Radar',
});
@@ -29,6 +33,7 @@ export const techRadarPlugin = createPlugin({
routes: {
root: rootRouteRef,
},
+ apis: [createApiFactory(techRadarApiRef, new SampleTechRadarApi())],
});
export const TechRadarPage = techRadarPlugin.provide(
diff --git a/plugins/tech-radar/src/sampleData.ts b/plugins/tech-radar/src/sample.ts
similarity index 95%
rename from plugins/tech-radar/src/sampleData.ts
rename to plugins/tech-radar/src/sample.ts
index 70b9303f39..1e924d85d0 100644
--- a/plugins/tech-radar/src/sampleData.ts
+++ b/plugins/tech-radar/src/sample.ts
@@ -19,6 +19,7 @@ import {
RadarQuadrant,
RadarEntry,
TechRadarLoaderResponse,
+ TechRadarApi,
} from './api';
const rings = new Array();
@@ -169,10 +170,14 @@ entries.push({
quadrant: 'infrastructure',
});
-export default function getSampleData(): Promise {
- return Promise.resolve({
- rings,
- quadrants,
- entries,
- });
+export const mock: TechRadarLoaderResponse = {
+ entries,
+ quadrants,
+ rings,
+};
+
+export class SampleTechRadarApi implements TechRadarApi {
+ async load() {
+ return mock;
+ }
}
diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md
index 056dfe55a0..103ec1c5d7 100644
--- a/plugins/techdocs/CHANGELOG.md
+++ b/plugins/techdocs/CHANGELOG.md
@@ -1,5 +1,22 @@
# @backstage/plugin-techdocs
+## 0.9.5
+
+### Patch Changes
+
+- aad98c544: Fixes multiple XSS and sanitization bypass vulnerabilities in TechDocs.
+- Updated dependencies [e7c5e4b30]
+- Updated dependencies [ebe802bc4]
+- Updated dependencies [49d7ec169]
+- Updated dependencies [1cf1d351f]
+- Updated dependencies [deaba2e13]
+- Updated dependencies [8e919a6f8]
+ - @backstage/theme@0.2.8
+ - @backstage/catalog-model@0.8.1
+ - @backstage/integration@0.5.5
+ - @backstage/core@0.7.12
+ - @backstage/plugin-catalog-react@0.2.1
+
## 0.9.4
### Patch Changes
diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json
index 06b4f551de..747c44488d 100644
--- a/plugins/techdocs/package.json
+++ b/plugins/techdocs/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-techdocs",
- "version": "0.9.4",
+ "version": "0.9.5",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -32,12 +32,12 @@
},
"dependencies": {
"@backstage/config": "^0.1.5",
- "@backstage/catalog-model": "^0.8.0",
- "@backstage/core": "^0.7.11",
- "@backstage/integration": "^0.5.4",
+ "@backstage/catalog-model": "^0.8.1",
+ "@backstage/core": "^0.7.12",
+ "@backstage/integration": "^0.5.5",
"@backstage/integration-react": "^0.1.2",
- "@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/plugin-catalog-react": "^0.2.1",
+ "@backstage/theme": "^0.2.8",
"@backstage/errors": "^0.1.1",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
@@ -51,9 +51,9 @@
"sanitize-html": "^2.3.2"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts
index afed2eff73..2b17d04144 100644
--- a/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts
+++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.test.ts
@@ -47,8 +47,17 @@ const mockEntityId = {
namespace: '',
name: '',
};
-
describe('addBaseUrl', () => {
+ const originalFetch = global.fetch;
+
+ beforeEach(() => {
+ global.fetch = jest.fn();
+ });
+
+ afterEach(() => {
+ global.fetch = originalFetch;
+ });
+
it('contains relative paths', () => {
createTestShadowDom(fixture, {
preTransformers: [
@@ -86,4 +95,34 @@ describe('addBaseUrl', () => {
'',
);
});
+
+ it('transforms svg img src to data uri', async () => {
+ const svgContent = '';
+ const expectedSrc = `data:image/svg+xml;base64,${Buffer.from(
+ svgContent,
+ ).toString('base64')}`;
+
+ (global.fetch as jest.Mock).mockReturnValue({
+ text: jest.fn().mockResolvedValue(svgContent),
+ });
+
+ const root = createTestShadowDom('
', {
+ preTransformers: [
+ addBaseUrl({
+ techdocsStorageApi,
+ entityId: mockEntityId,
+ path: '',
+ }),
+ ],
+ postTransformers: [],
+ });
+
+ await new Promise(done => {
+ process.nextTick(() => {
+ const actualSrc = root.getElementById('x')?.getAttribute('src');
+ expect(expectedSrc).toEqual(actualSrc);
+ done();
+ });
+ });
+ });
});
diff --git a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts
index 9bb5418bc2..98db162b46 100644
--- a/plugins/techdocs/src/reader/transformers/addBaseUrl.ts
+++ b/plugins/techdocs/src/reader/transformers/addBaseUrl.ts
@@ -38,10 +38,27 @@ export const addBaseUrl = ({
.forEach(async (elem: T) => {
const elemAttribute = elem.getAttribute(attributeName);
if (!elemAttribute) return;
- elem.setAttribute(
- attributeName,
- await techdocsStorageApi.getBaseUrl(elemAttribute, entityId, path),
+
+ // Special handling for SVG images.
+ const newValue = await techdocsStorageApi.getBaseUrl(
+ elemAttribute,
+ entityId,
+ path,
);
+ if (attributeName === 'src' && elemAttribute.endsWith('.svg')) {
+ try {
+ const svg = await fetch(newValue);
+ const svgContent = await svg.text();
+ elem.setAttribute(
+ attributeName,
+ `data:image/svg+xml;base64,${btoa(svgContent)}`,
+ );
+ } catch (e) {
+ elem.setAttribute('alt', `Error: ${elemAttribute}`);
+ }
+ } else {
+ elem.setAttribute(attributeName, newValue);
+ }
});
};
diff --git a/plugins/techdocs/src/reader/transformers/sanitizeDOM/tags.ts b/plugins/techdocs/src/reader/transformers/sanitizeDOM/tags.ts
index 094c82b5ff..9bb641c09d 100644
--- a/plugins/techdocs/src/reader/transformers/sanitizeDOM/tags.ts
+++ b/plugins/techdocs/src/reader/transformers/sanitizeDOM/tags.ts
@@ -153,7 +153,6 @@ export const svg = [
'mask',
'metadata',
'mpath',
- 'object',
'path',
'pattern',
'polygon',
diff --git a/plugins/todo/package.json b/plugins/todo/package.json
index 3dd07a9cc7..88eb60b3ce 100644
--- a/plugins/todo/package.json
+++ b/plugins/todo/package.json
@@ -30,7 +30,7 @@
"@backstage/core": "^0.7.11",
"@backstage/errors": "^0.1.1",
"@backstage/plugin-catalog-react": "^0.2.0",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -39,9 +39,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json
index b425584c78..f9e1bfe5f3 100644
--- a/plugins/user-settings/package.json
+++ b/plugins/user-settings/package.json
@@ -31,7 +31,7 @@
},
"dependencies": {
"@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -41,9 +41,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json
index 8d17b57bcc..e803b37059 100644
--- a/plugins/welcome/package.json
+++ b/plugins/welcome/package.json
@@ -31,7 +31,7 @@
},
"dependencies": {
"@backstage/core": "^0.7.11",
- "@backstage/theme": "^0.2.7",
+ "@backstage/theme": "^0.2.8",
"@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
@@ -41,9 +41,9 @@
"react-use": "^17.2.4"
},
"devDependencies": {
- "@backstage/cli": "^0.6.13",
- "@backstage/dev-utils": "^0.1.16",
- "@backstage/test-utils": "^0.1.12",
+ "@backstage/cli": "^0.6.14",
+ "@backstage/dev-utils": "^0.1.17",
+ "@backstage/test-utils": "^0.1.13",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/yarn.lock b/yarn.lock
index 25202523c2..f944c675a5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1702,7 +1702,7 @@
to-fast-properties "^2.0.0"
"@backstage/catalog-model@^0.7.4":
- version "0.8.0"
+ version "0.8.1"
dependencies:
"@backstage/config" "^0.1.5"
"@backstage/errors" "^0.1.1"
@@ -1716,7 +1716,7 @@
yup "^0.29.3"
"@backstage/catalog-model@^0.7.9":
- version "0.8.0"
+ version "0.8.1"
dependencies:
"@backstage/config" "^0.1.5"
"@backstage/errors" "^0.1.1"
@@ -1746,16 +1746,16 @@
react-use "^17.2.4"
"@backstage/plugin-catalog@^0.5.1":
- version "0.6.0"
+ version "0.6.1"
dependencies:
"@backstage/catalog-client" "^0.3.12"
- "@backstage/catalog-model" "^0.8.0"
- "@backstage/core" "^0.7.11"
+ "@backstage/catalog-model" "^0.8.1"
+ "@backstage/core" "^0.7.12"
"@backstage/errors" "^0.1.1"
- "@backstage/integration" "^0.5.4"
+ "@backstage/integration" "^0.5.5"
"@backstage/integration-react" "^0.1.2"
- "@backstage/plugin-catalog-react" "^0.2.0"
- "@backstage/theme" "^0.2.7"
+ "@backstage/plugin-catalog-react" "^0.2.1"
+ "@backstage/theme" "^0.2.8"
"@material-ui/core" "^4.11.0"
"@material-ui/icons" "^4.9.1"
"@material-ui/lab" "4.0.0-alpha.45"
@@ -4466,9 +4466,9 @@
"@rollup/pluginutils" "^3.0.8"
"@rollup/plugin-node-resolve@^11.2.0":
- version "11.2.0"
- resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz#a5ab88c35bb7622d115f44984dee305112b6f714"
- integrity sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA==
+ version "11.2.1"
+ resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz#82aa59397a29cd4e13248b106e6a4a1880362a60"
+ integrity sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg==
dependencies:
"@rollup/pluginutils" "^3.1.0"
"@types/resolve" "1.17.1"
@@ -4599,10 +4599,10 @@
resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-9.0.0.tgz#be68cfaf212599f0bfeb6536c7c58ec05d2b6fba"
integrity sha512-ZsXTwMA68ZCz943U4N8XwprdWcc7ErOO/IW8PewLK5lycCZtLnmRkOvAbae7O5qNJPD8b/l0iUMTLaZuwjXWwg==
-"@spotify/prettier-config@^9.0.0":
- version "9.0.0"
- resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-9.0.0.tgz#7b562d56573c6fc0094446fbc92b22bc318945dc"
- integrity sha512-In1q0tIiqTYKAGe3KOHDcFDdZRFISyQeSeipeTHGfki23ebHRZcjxvqj5SSdBkw65D4VpSREMi0s9i5iJiMcTw==
+"@spotify/prettier-config@^10.0.0":
+ version "10.0.0"
+ resolved "https://registry.npmjs.org/@spotify/prettier-config/-/prettier-config-10.0.0.tgz#fa076d98d2e7e6c53dd3d86a696307a7010bd056"
+ integrity sha512-VYOdo8P7lIScAkl02nB9KpUAuOYMManryBIBuKJkAw5D3aVtLobfmdIKvdV6MqEmGMEQPbn7w/UpnjJYhUH+IA==
"@storybook/addon-actions@^6.1.11":
version "6.1.17"
@@ -6108,7 +6108,7 @@
resolved "https://registry.npmjs.org/@types/glob-base/-/glob-base-0.3.0.tgz#a581d688347e10e50dd7c17d6f2880a10354319d"
integrity sha1-pYHWiDR+EOUN18F9byiAoQNUMZ0=
-"@types/glob@*", "@types/glob@^7.1.1":
+"@types/glob@*", "@types/glob@^7.1.1", "@types/glob@^7.1.3":
version "7.1.3"
resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==
@@ -6297,7 +6297,7 @@
dependencies:
"@types/json-schema" "*"
-"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6":
+"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.7":
version "7.0.7"
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz#98a993516c859eb0d5c4c8f098317a9ea68db9ad"
integrity sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==
@@ -6507,6 +6507,11 @@
resolved "https://registry.npmjs.org/@types/node/-/node-13.13.45.tgz#e6676bcca092bae5751d015f074a234d5a82eb63"
integrity sha512-703YTEp8AwQeapI0PTXDOj+Bs/mtdV/k9VcTP7z/de+lx6XjFMKdB+JhKnK+6PZ5za7omgZ3V6qm/dNkMj/Zow==
+"@types/node@^15.6.1":
+ version "15.12.0"
+ resolved "https://registry.npmjs.org/@types/node/-/node-15.12.0.tgz#6a459d261450a300e6865faeddb5af01c3389bb3"
+ integrity sha512-+aHJvoCsVhO2ZCuT4o5JtcPrCPyDE3+1nvbDprYes+pPkEsbjH7AGUCNtjMOXS0fqH14t+B7yLzaqSz92FPWyw==
+
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
resolved "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
@@ -6732,12 +6737,13 @@
"@types/react" "*"
"@types/react@*", "@types/react@^16.9":
- version "16.9.37"
- resolved "https://registry.npmjs.org/@types/react/-/react-16.9.37.tgz#8fb93e7dbd5b1d3796f69aa979a7fe0439bc7bea"
- integrity sha512-ZqnAXallQiZ08LTSqMfWMNvAfJEzRLOxdlbbbCIJlYGjU98BEU6bE2uBpKPGeWn+v3hIgCraHKtqUcKZBzMP/Q==
+ version "16.14.8"
+ resolved "https://registry.npmjs.org/@types/react/-/react-16.14.8.tgz#4aee3ab004cb98451917c9b7ada3c7d7e52db3fe"
+ integrity sha512-QN0/Qhmx+l4moe7WJuTxNiTsjBwlBGHqKGvInSQCBdo7Qio0VtOqwsC0Wq7q3PbJlB0cR4Y4CVo1OOe6BOsOmA==
dependencies:
"@types/prop-types" "*"
- csstype "^2.2.0"
+ "@types/scheduler" "*"
+ csstype "^3.0.2"
"@types/react@16.4.6":
version "16.4.6"
@@ -6827,6 +6833,11 @@
"@types/node" "*"
rollup "^0.63.4"
+"@types/scheduler@*":
+ version "0.16.1"
+ resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
+ integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
+
"@types/semver@^6.0.0":
version "6.2.1"
resolved "https://registry.npmjs.org/@types/semver/-/semver-6.2.1.tgz#a236185670a7860f1597cf73bea2e16d001461ba"
@@ -7141,31 +7152,31 @@
resolved "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.2.tgz#808c9fa7e4517274ed555fa158f2de4b4f468e71"
integrity sha512-HrCIVMLjE1MOozVoD86622S7aunluLb2PJdPfb3nYiEtohm8mIB/vyv0Fd37AdeMFrTUQXEunw78YloMA3Qilg==
-"@typescript-eslint/eslint-plugin@^v4.15.2":
- version "4.15.2"
- resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.2.tgz#981b26b4076c62a5a55873fbef3fe98f83360c61"
- integrity sha512-uiQQeu9tWl3f1+oK0yoAv9lt/KXO24iafxgQTkIYO/kitruILGx3uH+QtIAHqxFV+yIsdnJH+alel9KuE3J15Q==
+"@typescript-eslint/eslint-plugin@^v4.26.0":
+ version "4.26.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.26.0.tgz#12bbd6ebd5e7fabd32e48e1e60efa1f3554a3242"
+ integrity sha512-yA7IWp+5Qqf+TLbd8b35ySFOFzUfL7i+4If50EqvjT6w35X8Lv0eBHb6rATeWmucks37w+zV+tWnOXI9JlG6Eg==
dependencies:
- "@typescript-eslint/experimental-utils" "4.15.2"
- "@typescript-eslint/scope-manager" "4.15.2"
- debug "^4.1.1"
+ "@typescript-eslint/experimental-utils" "4.26.0"
+ "@typescript-eslint/scope-manager" "4.26.0"
+ debug "^4.3.1"
functional-red-black-tree "^1.0.1"
- lodash "^4.17.15"
- regexpp "^3.0.0"
- semver "^7.3.2"
- tsutils "^3.17.1"
+ lodash "^4.17.21"
+ regexpp "^3.1.0"
+ semver "^7.3.5"
+ tsutils "^3.21.0"
-"@typescript-eslint/experimental-utils@4.15.2", "@typescript-eslint/experimental-utils@^4.0.1":
- version "4.15.2"
- resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.2.tgz#5efd12355bd5b535e1831282e6cf465b9a71cf36"
- integrity sha512-Fxoshw8+R5X3/Vmqwsjc8nRO/7iTysRtDqx6rlfLZ7HbT8TZhPeQqbPjTyk2RheH3L8afumecTQnUc9EeXxohQ==
+"@typescript-eslint/experimental-utils@4.26.0", "@typescript-eslint/experimental-utils@^4.0.1":
+ version "4.26.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.26.0.tgz#ba7848b3f088659cdf71bce22454795fc55be99a"
+ integrity sha512-TH2FO2rdDm7AWfAVRB5RSlbUhWxGVuxPNzGT7W65zVfl8H/WeXTk1e69IrcEVsBslrQSTDKQSaJD89hwKrhdkw==
dependencies:
- "@types/json-schema" "^7.0.3"
- "@typescript-eslint/scope-manager" "4.15.2"
- "@typescript-eslint/types" "4.15.2"
- "@typescript-eslint/typescript-estree" "4.15.2"
- eslint-scope "^5.0.0"
- eslint-utils "^2.0.0"
+ "@types/json-schema" "^7.0.7"
+ "@typescript-eslint/scope-manager" "4.26.0"
+ "@typescript-eslint/types" "4.26.0"
+ "@typescript-eslint/typescript-estree" "4.26.0"
+ eslint-scope "^5.1.1"
+ eslint-utils "^3.0.0"
"@typescript-eslint/parser@^v4.14.0":
version "4.14.0"
@@ -7185,23 +7196,23 @@
"@typescript-eslint/types" "4.14.0"
"@typescript-eslint/visitor-keys" "4.14.0"
-"@typescript-eslint/scope-manager@4.15.2":
- version "4.15.2"
- resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.2.tgz#5725bda656995960ae1d004bfd1cd70320f37f4f"
- integrity sha512-Zm0tf/MSKuX6aeJmuXexgdVyxT9/oJJhaCkijv0DvJVT3ui4zY6XYd6iwIo/8GEZGy43cd7w1rFMiCLHbRzAPQ==
+"@typescript-eslint/scope-manager@4.26.0":
+ version "4.26.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.26.0.tgz#60d1a71df162404e954b9d1c6343ff3bee496194"
+ integrity sha512-G6xB6mMo4xVxwMt5lEsNTz3x4qGDt0NSGmTBNBPJxNsrTXJSm21c6raeYroS2OwQsOyIXqKZv266L/Gln1BWqg==
dependencies:
- "@typescript-eslint/types" "4.15.2"
- "@typescript-eslint/visitor-keys" "4.15.2"
+ "@typescript-eslint/types" "4.26.0"
+ "@typescript-eslint/visitor-keys" "4.26.0"
"@typescript-eslint/types@4.14.0":
version "4.14.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.14.0.tgz#d8a8202d9b58831d6fd9cee2ba12f8a5a5dd44b6"
integrity sha512-VsQE4VvpldHrTFuVPY1ZnHn/Txw6cZGjL48e+iBxTi2ksa9DmebKjAeFmTVAYoSkTk7gjA7UqJ7pIsyifTsI4A==
-"@typescript-eslint/types@4.15.2":
- version "4.15.2"
- resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.2.tgz#04acf3a2dc8001a88985291744241e732ef22c60"
- integrity sha512-r7lW7HFkAarfUylJ2tKndyO9njwSyoy6cpfDKWPX6/ctZA+QyaYscAHXVAfJqtnY6aaTwDYrOhp+ginlbc7HfQ==
+"@typescript-eslint/types@4.26.0":
+ version "4.26.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.26.0.tgz#7c6732c0414f0a69595f4f846ebe12616243d546"
+ integrity sha512-rADNgXl1kS/EKnDr3G+m7fB9yeJNnR9kF7xMiXL6mSIWpr3Wg5MhxyfEXy/IlYthsqwBqHOr22boFbf/u6O88A==
"@typescript-eslint/typescript-estree@4.14.0":
version "4.14.0"
@@ -7217,18 +7228,18 @@
semver "^7.3.2"
tsutils "^3.17.1"
-"@typescript-eslint/typescript-estree@4.15.2":
- version "4.15.2"
- resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.2.tgz#c2f7a1e94f3428d229d5ecff3ead6581ee9b62fa"
- integrity sha512-cGR8C2g5SPtHTQvAymEODeqx90pJHadWsgTtx6GbnTWKqsg7yp6Eaya9nFzUd4KrKhxdYTTFBiYeTPQaz/l8bw==
+"@typescript-eslint/typescript-estree@4.26.0":
+ version "4.26.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.26.0.tgz#aea17a40e62dc31c63d5b1bbe9a75783f2ce7109"
+ integrity sha512-GHUgahPcm9GfBuy3TzdsizCcPjKOAauG9xkz9TR8kOdssz2Iz9jRCSQm6+aVFa23d5NcSpo1GdHGSQKe0tlcbg==
dependencies:
- "@typescript-eslint/types" "4.15.2"
- "@typescript-eslint/visitor-keys" "4.15.2"
- debug "^4.1.1"
- globby "^11.0.1"
+ "@typescript-eslint/types" "4.26.0"
+ "@typescript-eslint/visitor-keys" "4.26.0"
+ debug "^4.3.1"
+ globby "^11.0.3"
is-glob "^4.0.1"
- semver "^7.3.2"
- tsutils "^3.17.1"
+ semver "^7.3.5"
+ tsutils "^3.21.0"
"@typescript-eslint/visitor-keys@4.14.0":
version "4.14.0"
@@ -7238,12 +7249,12 @@
"@typescript-eslint/types" "4.14.0"
eslint-visitor-keys "^2.0.0"
-"@typescript-eslint/visitor-keys@4.15.2":
- version "4.15.2"
- resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.2.tgz#3d1c7979ce75bf6acf9691109bd0d6b5706192b9"
- integrity sha512-TME1VgSb7wTwgENN5KVj4Nqg25hP8DisXxNBojM4Nn31rYaNDIocNm5cmjOFfh42n7NVERxWrDFoETO/76ePyg==
+"@typescript-eslint/visitor-keys@4.26.0":
+ version "4.26.0"
+ resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.26.0.tgz#26d2583169222815be4dcd1da4fe5459bc3bcc23"
+ integrity sha512-cw4j8lH38V1ycGBbF+aFiLUls9Z0Bw8QschP3mkth50BbWzgFS33ISIgBzUMuQ2IdahoEv/rXstr8Zhlz4B1Zg==
dependencies:
- "@typescript-eslint/types" "4.15.2"
+ "@typescript-eslint/types" "4.26.0"
eslint-visitor-keys "^2.0.0"
"@webassemblyjs/ast@1.9.0":
@@ -8954,7 +8965,7 @@ bluebird@~3.4.1:
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9:
version "4.12.0"
- resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
+ resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
body-parser@1.19.0, body-parser@^1.18.3:
@@ -9057,7 +9068,7 @@ breakword@^1.0.5:
brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
- resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
+ resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=
browser-process-hrtime@^1.0.0:
@@ -9257,6 +9268,11 @@ buffers@~0.1.1:
resolved "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s=
+builtin-modules@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
+ integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
+
builtin-modules@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz#aad97c15131eb76b65b50ef208e7584cd76a7484"
@@ -9581,7 +9597,7 @@ chainsaw@~0.1.0:
dependencies:
traverse ">=0.3.0 <0.4"
-chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -10153,7 +10169,7 @@ commander@2.3.0:
resolved "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz#fd430e889832ec353b9acd1de217c11cb3eef873"
integrity sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=
-commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.7.1, commander@~2.20.3:
+commander@^2.12.1, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.7.1, commander@~2.20.3:
version "2.20.3"
resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@@ -11043,7 +11059,12 @@ cssstyle@^2.2.0:
dependencies:
cssom "~0.3.6"
-csstype@^2.2.0, csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.7:
+csstype@^2.2.0:
+ version "2.6.17"
+ resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e"
+ integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==
+
+csstype@^2.5.2, csstype@^2.5.7, csstype@^2.6.7:
version "2.6.9"
resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.9.tgz#05141d0cd557a56b8891394c1911c40c8a98d098"
integrity sha512-xz39Sb4+OaTsULgUERcCk+TJj8ylkL4aSVDQiX/ksxbELSqwkgt4d4RD7fovIdgJGSuNYqwZEiVjYY5l0ask+Q==
@@ -12167,7 +12188,7 @@ element-resize-detector@^1.2.1:
elliptic@^6.0.0:
version "6.5.4"
- resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
+ resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb"
integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==
dependencies:
bn.js "^4.11.9"
@@ -12692,7 +12713,7 @@ eslint-scope@^4.0.3:
esrecurse "^4.1.0"
estraverse "^4.1.1"
-eslint-scope@^5.0.0, eslint-scope@^5.1.1:
+eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
@@ -12700,13 +12721,20 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.1:
esrecurse "^4.3.0"
estraverse "^4.1.1"
-eslint-utils@^2.0.0, eslint-utils@^2.1.0:
+eslint-utils@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
dependencies:
eslint-visitor-keys "^1.1.0"
+eslint-utils@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672"
+ integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==
+ dependencies:
+ eslint-visitor-keys "^2.0.0"
+
eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
version "1.3.0"
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
@@ -13356,7 +13384,7 @@ fetch-readablestream@^0.2.0:
figgy-pudding@^3.5.1:
version "3.5.2"
- resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
+ resolved "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
figures@^1.7.0:
@@ -13607,7 +13635,7 @@ for-own@^0.1.3:
foreach@^2.0.4:
version "2.0.5"
- resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
+ resolved "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k=
forever-agent@~0.6.1:
@@ -14307,7 +14335,7 @@ globby@11.0.1:
merge2 "^1.3.0"
slash "^3.0.0"
-globby@11.0.3:
+globby@11.0.3, globby@^11.0.3:
version "11.0.3"
resolved "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz#9b1f0cb523e171dd1ad8c7b2a9fb4b644b9593cb"
integrity sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==
@@ -14688,7 +14716,7 @@ growl@1.9.2:
growly@^1.3.0:
version "1.3.0"
- resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
+ resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
gtoken@^2.3.2:
@@ -14867,7 +14895,7 @@ hash-stream-validation@^0.2.1, hash-stream-validation@^0.2.2:
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.7"
- resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
+ resolved "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42"
integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==
dependencies:
inherits "^2.0.3"
@@ -14936,7 +14964,7 @@ history@^5.0.0:
hmac-drbg@^1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
+ resolved "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=
dependencies:
hash.js "^1.0.3"
@@ -14957,7 +14985,7 @@ hoopy@^0.1.4:
hosted-git-info@^2.1.4:
version "2.8.9"
- resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+ resolved "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
hosted-git-info@^3.0.6:
@@ -15498,7 +15526,7 @@ inflight@^1.0.4:
inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
version "2.0.4"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
inherits@2.0.1:
@@ -15511,14 +15539,14 @@ inherits@2.0.3:
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-ini@2.0.0:
+ini@2.0.0, ini@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==
ini@^1.3.2, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
version "1.3.8"
- resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
+ resolved "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
init-package-json@^2.0.2:
@@ -15826,9 +15854,9 @@ is-directory@^0.3.1:
integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
is-docker@^2.0.0:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.1.1.tgz#4125a88e44e450d384e09047ede71adc2d144156"
- integrity sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==
+ version "2.2.1"
+ resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
+ integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==
is-dom@^1.0.9, is-dom@^1.1.0:
version "1.1.0"
@@ -16270,7 +16298,7 @@ isarray@^2.0.5:
isexe@^2.0.0:
version "2.0.0"
- resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
isobject@^2.0.0:
@@ -17073,7 +17101,7 @@ json-parse-even-better-errors@^2.3.0:
json-pointer@^0.6.0:
version "0.6.1"
- resolved "https://registry.yarnpkg.com/json-pointer/-/json-pointer-0.6.1.tgz#3c6caa6ac139e2599f5a1659d39852154015054d"
+ resolved "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.1.tgz#3c6caa6ac139e2599f5a1659d39852154015054d"
integrity sha512-3OvjqKdCBvH41DLpV4iSt6v2XhZXV1bPB4OROuknvUXI7ZQNofieCPkmE26stEJ9zdQuvIxDHCuYhfgxFAAs+Q==
dependencies:
foreach "^2.0.4"
@@ -17410,7 +17438,7 @@ keytar@^5.4.0:
keyv-memcache@^1.2.5:
version "1.2.5"
- resolved "https://registry.yarnpkg.com/keyv-memcache/-/keyv-memcache-1.2.5.tgz#9097af5c617dc740e7300ebfd4a2efb8917429e3"
+ resolved "https://registry.npmjs.org/keyv-memcache/-/keyv-memcache-1.2.5.tgz#9097af5c617dc740e7300ebfd4a2efb8917429e3"
integrity sha512-iG+GHlhXyV83gmlCtMFIqNYVvv0i0towAy42NvziUR7D+k9jp81fAq0lu66H4gooOnW+ojkdigRvRpL40j1f7w==
dependencies:
json-buffer "^3.0.1"
@@ -17432,7 +17460,7 @@ keyv@^4.0.0:
keyv@^4.0.3:
version "4.0.3"
- resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254"
+ resolved "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254"
integrity sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==
dependencies:
json-buffer "3.0.1"
@@ -18207,7 +18235,7 @@ lru-cache@^5.0.0, lru-cache@^5.1.1:
lru-cache@^6.0.0:
version "6.0.0"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+ resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
@@ -18512,7 +18540,7 @@ memfs@^3.1.2:
memjs@^1.3.0:
version "1.3.0"
- resolved "https://registry.yarnpkg.com/memjs/-/memjs-1.3.0.tgz#b7959b4ff3770e4c785463fd147f1e4fafd47a24"
+ resolved "https://registry.npmjs.org/memjs/-/memjs-1.3.0.tgz#b7959b4ff3770e4c785463fd147f1e4fafd47a24"
integrity sha512-y/V9a0auepA9Lgyr4QieK6K2FczjHucEdTpSS+hHVNmVEkYxruXhkHu8n6DSRQ4HXHEE3cc6Sf9f88WCJXGXsQ==
memoize-one@^5.1.1:
@@ -18633,7 +18661,7 @@ merge2@^1.2.3, merge2@^1.3.0:
merge@^2.1.0:
version "2.1.1"
- resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98"
+ resolved "https://registry.npmjs.org/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98"
integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w==
meros@1.1.4, meros@^1.1.2:
@@ -18813,12 +18841,12 @@ mini-css-extract-plugin@^0.9.0:
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
+ resolved "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==
minimalistic-crypto-utils@^1.0.1:
version "1.0.1"
- resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
+ resolved "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
minimatch@0.3:
@@ -19497,9 +19525,9 @@ node-modules-regexp@^1.0.0:
integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
node-notifier@^8.0.0:
- version "8.0.1"
- resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.1.tgz#f86e89bbc925f2b068784b31f382afdc6ca56be1"
- integrity sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==
+ version "8.0.2"
+ resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5"
+ integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg==
dependencies:
growly "^1.3.0"
is-wsl "^2.2.0"
@@ -19876,7 +19904,7 @@ object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1:
object-path@^0.11.4:
version "0.11.5"
- resolved "https://registry.yarnpkg.com/object-path/-/object-path-0.11.5.tgz#d4e3cf19601a5140a55a16ad712019a9c50b577a"
+ resolved "https://registry.npmjs.org/object-path/-/object-path-0.11.5.tgz#d4e3cf19601a5140a55a16ad712019a9c50b577a"
integrity sha512-jgSbThcoR/s+XumvGMTMf81QVBmah+/Q7K7YduKeKVWL7N111unR2d6pZZarSk6kY/caeNxUDyxOvMWyzoU2eg==
object-visit@^1.0.0:
@@ -21696,7 +21724,7 @@ prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0,
property-expr@^2.0.2:
version "2.0.4"
- resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910"
+ resolved "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910"
integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg==
property-information@^5.0.0:
@@ -22956,7 +22984,7 @@ regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.3.0:
define-properties "^1.1.3"
es-abstract "^1.17.0-next.1"
-regexpp@^3.0.0, regexpp@^3.1.0:
+regexpp@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
@@ -23259,7 +23287,7 @@ resolve-url@^0.2.1:
resolved "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
-resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.9.0:
+resolve@^1.1.6, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.17.0, resolve@^1.18.1, resolve@^1.19.0, resolve@^1.3.2, resolve@^1.9.0:
version "1.20.0"
resolved "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
@@ -23686,7 +23714,7 @@ semver@7.0.0:
resolved "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
-semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@~7.3.0:
+semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@~7.3.0:
version "7.3.5"
resolved "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
@@ -23899,7 +23927,7 @@ shelljs@^0.8.2, shelljs@^0.8.3, shelljs@^0.8.4:
shellwords@^0.1.1:
version "0.1.1"
- resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
+ resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
shortid@^2.2.14:
@@ -24387,7 +24415,7 @@ sshpk@^1.7.0:
ssri@^6.0.1:
version "6.0.2"
- resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
+ resolved "https://registry.npmjs.org/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5"
integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q==
dependencies:
figgy-pudding "^3.5.1"
@@ -25789,7 +25817,7 @@ tslib@2.0.0:
resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.0.tgz#18d13fc2dce04051e20f074cc8387fd8089ce4f3"
integrity sha512-lTqkx847PI7xEDYJntxZH89L2/aXInsyF2luSafe/+0fHOMjlBNXdH6th7f70qxLDhul7KZK0zC8V5ZIyHl0/g==
-tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
+tslib@^1.10.0, tslib@^1.11.1, tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -25809,10 +25837,36 @@ tslib@~2.1.0:
resolved "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
-tsutils@^3.17.1:
- version "3.17.1"
- resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
- integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
+tslint@^6.1.3:
+ version "6.1.3"
+ resolved "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz#5c23b2eccc32487d5523bd3a470e9aa31789d904"
+ integrity sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ builtin-modules "^1.1.1"
+ chalk "^2.3.0"
+ commander "^2.12.1"
+ diff "^4.0.1"
+ glob "^7.1.1"
+ js-yaml "^3.13.1"
+ minimatch "^3.0.4"
+ mkdirp "^0.5.3"
+ resolve "^1.3.2"
+ semver "^5.3.0"
+ tslib "^1.13.0"
+ tsutils "^2.29.0"
+
+tsutils@^2.29.0:
+ version "2.29.0"
+ resolved "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
+ integrity sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==
+ dependencies:
+ tslib "^1.8.1"
+
+tsutils@^3.17.1, tsutils@^3.21.0:
+ version "3.21.0"
+ resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
+ integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
dependencies:
tslib "^1.8.1"
@@ -25960,9 +26014,14 @@ typescript@^4.0.3, typescript@^4.1.3, typescript@~4.1.3:
resolved "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
+typescript@^4.3.2:
+ version "4.3.2"
+ resolved "https://registry.npmjs.org/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805"
+ integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw==
+
ua-parser-js@^0.7.18:
version "0.7.28"
- resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
+ resolved "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
uc.micro@^1.0.1, uc.micro@^1.0.5:
@@ -26440,7 +26499,7 @@ uuid@^7.0.3:
uuid@^8.0.0, uuid@^8.2.0, uuid@^8.3.0, uuid@^8.3.2:
version "8.3.2"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
+ resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache@^2.0.3:
@@ -26803,7 +26862,7 @@ websocket-driver@>=0.5.1:
websocket-extensions@>=0.1.1:
version "0.1.4"
- resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
+ resolved "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42"
integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==
whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5:
@@ -27239,7 +27298,7 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
y18n@^3.2.1:
version "3.2.2"
- resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
+ resolved "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696"
integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==
y18n@^4.0.0:
@@ -27264,7 +27323,7 @@ yallist@^3.0.0, yallist@^3.0.2, yallist@^3.0.3:
yallist@^4.0.0:
version "4.0.0"
- resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
yaml-ast-parser@0.0.43, yaml-ast-parser@^0.0.43:
@@ -27392,6 +27451,19 @@ yargs@^5.0.0:
y18n "^3.2.1"
yargs-parser "^3.2.0"
+yarn-lock-check@^1.0.4:
+ version "1.0.4"
+ resolved "https://registry.npmjs.org/yarn-lock-check/-/yarn-lock-check-1.0.4.tgz#a0373de051be0c8442d8933070df7a45595263b4"
+ integrity sha512-Gj0wRN85c4OPZUlE7WsQ0a1COv38uyeWWR0YAvJr2Vxw1f32bwK19xySjUZlxt9o4QopJkd8g6x6CLv81OHwYg==
+ dependencies:
+ "@types/glob" "^7.1.3"
+ "@types/node" "^15.6.1"
+ "@yarnpkg/lockfile" "^1.1.0"
+ glob "^7.1.7"
+ ini "^2.0.0"
+ tslint "^6.1.3"
+ typescript "^4.3.2"
+
yauzl@^2.10.0:
version "2.10.0"
resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"