diff --git a/.changeset/afraid-carpets-know.md b/.changeset/afraid-carpets-know.md
new file mode 100644
index 0000000000..94a5452606
--- /dev/null
+++ b/.changeset/afraid-carpets-know.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-search': patch
+---
+
+Add Optional Props to Override Icon for SidebarSearch and SidebarSearchModal Component
diff --git a/.changeset/beige-mangos-knock.md b/.changeset/beige-mangos-knock.md
deleted file mode 100644
index d8723c3f87..0000000000
--- a/.changeset/beige-mangos-knock.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'@backstage/plugin-github-actions': patch
----
-
-Show empty state only when workflow API call has completed
diff --git a/.changeset/blue-queens-sniff.md b/.changeset/blue-queens-sniff.md
new file mode 100644
index 0000000000..9a26507f92
--- /dev/null
+++ b/.changeset/blue-queens-sniff.md
@@ -0,0 +1,7 @@
+---
+'@backstage/app-defaults': patch
+'@backstage/core-app-api': patch
+'@backstage/core-plugin-api': patch
+---
+
+Add `FetchApi` and related `fetchApiRef` which implement fetch, with an added Backstage token header when available.
diff --git a/.changeset/brave-impalas-switch.md b/.changeset/brave-impalas-switch.md
new file mode 100644
index 0000000000..729a4ec96a
--- /dev/null
+++ b/.changeset/brave-impalas-switch.md
@@ -0,0 +1,22 @@
+---
+'@backstage/backend-common': patch
+---
+
+Fixed bug in backend-common to allow passing of remote option in order to enable passing remote url in --config option. The remote option should be passed along with reloadIntervalSeconds from packages/backend/src/index.ts (Updated the file as well)
+
+These changes are needed in `packages/backend/src/index.ts` if remote URLs are desired to be passed in --config option and read and watch remote files for config.
+
+```diff
+@@ -86,7 +86,11 @@ async function main() {
+ const config = await loadBackendConfig({
+ argv: process.argv,
+ logger,
++ remote: {
++ reloadIntervalSeconds: 60 * 10 // Check remote config changes every 10 minutes. Change to your desired interval in seconds
++ }
+ });
++
+ const createEnv = makeCreateEnv(config);
+
+ const healthcheckEnv = useHotMemoize(module, () => createEnv('healthcheck'));
+```
diff --git a/.changeset/cuddly-cooks-enjoy.md b/.changeset/cuddly-cooks-enjoy.md
new file mode 100644
index 0000000000..d39844e7a9
--- /dev/null
+++ b/.changeset/cuddly-cooks-enjoy.md
@@ -0,0 +1,5 @@
+---
+'@backstage/config-loader': patch
+---
+
+In case remote.reloadIntervalSeconds is passed, it must be a valid positive value
diff --git a/.changeset/cuddly-suns-sit.md b/.changeset/cuddly-suns-sit.md
new file mode 100644
index 0000000000..f1fe16add6
--- /dev/null
+++ b/.changeset/cuddly-suns-sit.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-user-settings': patch
+---
+
+Fix undefined identity bug in UserSettingsProfileCard caused by using deprecated methods of the IdentityApi
diff --git a/.changeset/cyan-seahorses-film.md b/.changeset/cyan-seahorses-film.md
new file mode 100644
index 0000000000..80a27baec0
--- /dev/null
+++ b/.changeset/cyan-seahorses-film.md
@@ -0,0 +1,11 @@
+---
+'@backstage/core-plugin-api': minor
+---
+
+**BREAKING CHANGE** The `StorageApi` has received several updates that fills in gaps for some use-cases and makes it easier to avoid mistakes:
+
+- The `StorageValueChange` type has been renamed to `StorageValueSnapshot`, the `newValue` property has been renamed to `value`, the stored value type has been narrowed to `JsonValue`, and it has received a new `presence` property that is `'unknown'`, `'absent'`, or `'present'`.
+- The `get` method has been deprecated in favor of a new `snapshot` method, which returns a `StorageValueSnapshot`.
+- The `observe$` method has had its contract changed. It should now emit values when the `presence` of a key changes, this may for example happen when remotely stored values are requested on page load and the presence switches from `'unknown'` to either `'absent'` or `'present'`.
+
+The above changes have been made with deprecations in place to maintain much of the backwards compatibility for consumers of the `StorageApi`. The only breaking change is the narrowing of the stored value type, which may in some cases require the addition of an explicit type parameter to the `get` and `observe$` methods.
diff --git a/.changeset/dirty-ways-learn.md b/.changeset/dirty-ways-learn.md
new file mode 100644
index 0000000000..e83fbbdfa2
--- /dev/null
+++ b/.changeset/dirty-ways-learn.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-config-schema': patch
+'@backstage/plugin-scaffolder': patch
+---
+
+Fix a small browser console warning
diff --git a/.changeset/eleven-baboons-sparkle.md b/.changeset/eleven-baboons-sparkle.md
new file mode 100644
index 0000000000..e8c28af482
--- /dev/null
+++ b/.changeset/eleven-baboons-sparkle.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-react': patch
+---
+
+added useOwnedEntities hook to get the list of entities of the logged-in user
diff --git a/.changeset/expor-k8s-backend-client.md b/.changeset/expor-k8s-backend-client.md
new file mode 100644
index 0000000000..b4b7bd9973
--- /dev/null
+++ b/.changeset/expor-k8s-backend-client.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-kubernetes': minor
+---
+
+Includes `KubernetesBackendClient` in the export to allow developers to use it externally.
diff --git a/.changeset/fluffy-grapes-decide.md b/.changeset/fluffy-grapes-decide.md
new file mode 100644
index 0000000000..17a6cc965f
--- /dev/null
+++ b/.changeset/fluffy-grapes-decide.md
@@ -0,0 +1,5 @@
+---
+'@backstage/cli': patch
+---
+
+Added peerPluginDependencies option to experimentalInstallationRecipe for install command to install plugins it depends on.
diff --git a/.changeset/fresh-months-approve.md b/.changeset/fresh-months-approve.md
new file mode 100644
index 0000000000..c2da09daf6
--- /dev/null
+++ b/.changeset/fresh-months-approve.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-tech-radar': minor
+---
+
+Add new property to enable open links in a new window/tab
diff --git a/.changeset/funny-chefs-guess.md b/.changeset/funny-chefs-guess.md
new file mode 100644
index 0000000000..66824892f2
--- /dev/null
+++ b/.changeset/funny-chefs-guess.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-org': patch
+---
+
+Fixed bug in OwnershipCard component where text wasn't correctly pluralized
diff --git a/.changeset/gentle-humans-hope.md b/.changeset/gentle-humans-hope.md
new file mode 100644
index 0000000000..b8c337cec9
--- /dev/null
+++ b/.changeset/gentle-humans-hope.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend-module-rails': patch
+---
+
+Add new options to rails new (force and skipTests)
diff --git a/.changeset/gold-seas-wave.md b/.changeset/gold-seas-wave.md
new file mode 100644
index 0000000000..ec0c6451b7
--- /dev/null
+++ b/.changeset/gold-seas-wave.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-common': minor
+---
+
+Create catalog-common and add catalog permissions.
diff --git a/.changeset/good-fans-dance.md b/.changeset/good-fans-dance.md
new file mode 100644
index 0000000000..a24b467c01
--- /dev/null
+++ b/.changeset/good-fans-dance.md
@@ -0,0 +1,6 @@
+---
+'@backstage/plugin-bazaar': patch
+'@backstage/plugin-bazaar-backend': patch
+---
+
+made the linkage between a Bazaar project to a catalog Entity optional
diff --git a/.changeset/gorgeous-beers-teach.md b/.changeset/gorgeous-beers-teach.md
new file mode 100644
index 0000000000..014398a5be
--- /dev/null
+++ b/.changeset/gorgeous-beers-teach.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-app-api': minor
+---
+
+Updated `WebStorageApi` to reflect the `StorageApi` changes in `@backstage/core-plugin-api`.
diff --git a/.changeset/green-timers-film.md b/.changeset/green-timers-film.md
new file mode 100644
index 0000000000..be94b13f0d
--- /dev/null
+++ b/.changeset/green-timers-film.md
@@ -0,0 +1,5 @@
+---
+'@backstage/test-utils': minor
+---
+
+Updated `MockStorageApi` to reflect the `StorageApi` changes in `@backstage/core-plugin-api`.
diff --git a/.changeset/itchy-grapes-think.md b/.changeset/itchy-grapes-think.md
new file mode 100644
index 0000000000..c453b24922
--- /dev/null
+++ b/.changeset/itchy-grapes-think.md
@@ -0,0 +1,25 @@
+---
+'@backstage/plugin-catalog': patch
+---
+
+Deprecated the `CatalogClientWrapper` class.
+
+The default implementation of `catalogApiRef` that this plugin exposes, is now powered by the new `fetchApiRef`. The default implementation of _that_ API, in turn, has the ability to inject the user's Backstage token in requests in a similar manner to what the deprecated `CatalogClientWrapper` used to do. The latter has therefore been taken out of the default catalog API implementation.
+
+If you use a custom `fetchApiRef` implementation that does NOT issue tokens, or use a custom `catalogApiRef` implementation which does NOT use the default `fetchApiRef`, you can still for some time wrap your catalog API in this class to get back the old behavior:
+
+```ts
+// Add this to your packages/app/src/plugins.ts if you want to get back the old
+// catalog client behavior:
+createApiFactory({
+ api: catalogApiRef,
+ deps: { discoveryApi: discoveryApiRef, identityApi: identityApiRef },
+ factory: ({ discoveryApi, identityApi }) =>
+ new CatalogClientWrapper({
+ client: new CatalogClient({ discoveryApi }),
+ identityApi,
+ }),
+}),
+```
+
+But do consider migrating to making use of the `fetchApiRef` as soon as convenient, since the wrapper class will be removed in a future release.
diff --git a/.changeset/lovely-drinks-kiss.md b/.changeset/lovely-drinks-kiss.md
new file mode 100644
index 0000000000..f343888920
--- /dev/null
+++ b/.changeset/lovely-drinks-kiss.md
@@ -0,0 +1,5 @@
+---
+'@backstage/catalog-client': patch
+---
+
+Add the ability to supply a custom `fetchApi`. In the default frontend app setup, this will use the `fetchApiRef` implementation.
diff --git a/.changeset/many-trees-happen.md b/.changeset/many-trees-happen.md
new file mode 100644
index 0000000000..f29c11fb22
--- /dev/null
+++ b/.changeset/many-trees-happen.md
@@ -0,0 +1,5 @@
+---
+'@backstage/config-loader': minor
+---
+
+Removed deprecated option `env` from `LoadConfigOptions` and associated tests
diff --git a/.changeset/modern-planets-fry.md b/.changeset/modern-planets-fry.md
new file mode 100644
index 0000000000..2da39473a1
--- /dev/null
+++ b/.changeset/modern-planets-fry.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-newrelic-dashboard': patch
+---
+
+Created a New Plugin , which imports dashboard links and snapshots from New Relic
diff --git a/.changeset/moody-scissors-yell.md b/.changeset/moody-scissors-yell.md
new file mode 100644
index 0000000000..deb06b3c6f
--- /dev/null
+++ b/.changeset/moody-scissors-yell.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-home': patch
+---
+
+Fix undefined identity bug in `WelcomeTitle` caused by using deprecated methods of the IdentityApi
diff --git a/.changeset/neat-stingrays-decide.md b/.changeset/neat-stingrays-decide.md
new file mode 100644
index 0000000000..f339d7ded6
--- /dev/null
+++ b/.changeset/neat-stingrays-decide.md
@@ -0,0 +1,5 @@
+---
+'@backstage/test-utils': minor
+---
+
+Removed deprecated `Keyboard` class which has been superseded by `@testing-library/user-event#userEvent`
diff --git a/.changeset/new-suns-agree.md b/.changeset/new-suns-agree.md
new file mode 100644
index 0000000000..c93bb1ca12
--- /dev/null
+++ b/.changeset/new-suns-agree.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-rollbar-backend': patch
+---
+
+Bump `axios`
diff --git a/.changeset/new-waves-rule.md b/.changeset/new-waves-rule.md
new file mode 100644
index 0000000000..7470a1d08e
--- /dev/null
+++ b/.changeset/new-waves-rule.md
@@ -0,0 +1,7 @@
+---
+'@backstage/core-app-api': minor
+---
+
+- Removed deprecated definition `createApp` from `@backstage/core-app-api` which has been replaced by `@backstage/app-defaults#createApp`
+- Removed deprecated type `BackstagePluginWithAnyOutput`
+- Removed deprecated constructors for `GithubAuth`, `OAuth2`, and `SamlAuth` as the `create` method should be used instead
diff --git a/.changeset/nice-apricots-grow.md b/.changeset/nice-apricots-grow.md
new file mode 100644
index 0000000000..290041091d
--- /dev/null
+++ b/.changeset/nice-apricots-grow.md
@@ -0,0 +1,8 @@
+---
+'@backstage/core-plugin-api': minor
+---
+
+- Removed deprecated option `description` from `ApiRefConfig`
+- Removed descriptions from all plugin API refs
+- Removed deprecated parameters `path`, `icon`, and `title` in `createRouteRef`
+- Removed deprecated types `Error` and `ErrorContext` from `ErrorApi`
diff --git a/.changeset/nice-feet-hope.md b/.changeset/nice-feet-hope.md
new file mode 100644
index 0000000000..cfc660020f
--- /dev/null
+++ b/.changeset/nice-feet-hope.md
@@ -0,0 +1,30 @@
+---
+'@backstage/create-app': patch
+---
+
+The `index.html` template of the app has been updated to use the new `config` global provided by the Backstage CLI.
+
+To apply this change to an existing app, make the following changes to `packages/app/public/index.html`:
+
+```diff
+-
<%= app.title %>
++ <%= config.getString('app.title') %>
+```
+
+```diff
+- <% if (app.googleAnalyticsTrackingId && typeof app.googleAnalyticsTrackingId === 'string') { %>
++ <% if (config.has('app.googleAnalyticsTrackingId')) { %>
+
+```
+
+```diff
+- gtag('config', '<%= app.googleAnalyticsTrackingId %>');
++ gtag(
++ 'config',
++ '<%= config.getString("app.googleAnalyticsTrackingId") %>',
++ );
+```
diff --git a/.changeset/olive-cameras-rush.md b/.changeset/olive-cameras-rush.md
new file mode 100644
index 0000000000..9a83aea699
--- /dev/null
+++ b/.changeset/olive-cameras-rush.md
@@ -0,0 +1,5 @@
+---
+'@backstage/test-utils': minor
+---
+
+Removed deprecated `msw` definition which was replaced by calling `setupRequestMockHandlers` directly
diff --git a/.changeset/perfect-apricots-raise.md b/.changeset/perfect-apricots-raise.md
new file mode 100644
index 0000000000..d7a40e6343
--- /dev/null
+++ b/.changeset/perfect-apricots-raise.md
@@ -0,0 +1,5 @@
+---
+'@backstage/config-loader': minor
+---
+
+Removed deprecated option `configPaths` as it has been superseded by `configTargets`
diff --git a/.changeset/perfect-buses-collect.md b/.changeset/perfect-buses-collect.md
new file mode 100644
index 0000000000..e0df504087
--- /dev/null
+++ b/.changeset/perfect-buses-collect.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Added OwnedEntityPicker field which displays Owned Entities in options
diff --git a/.changeset/purple-grapes-kick.md b/.changeset/purple-grapes-kick.md
new file mode 100644
index 0000000000..9d64d4e17c
--- /dev/null
+++ b/.changeset/purple-grapes-kick.md
@@ -0,0 +1,15 @@
+---
+'@backstage/cli': patch
+---
+
+The frontend configuration is now available as a `config` global during templating of the `index.html` file. This allows for much more flexibility as the values available during templating is not longer hardcoded to a fixed set of values.
+
+For example, to access the app title, you would now do the following:
+
+```html
+<%= config.getString('app.title') %>
+```
+
+Along with this change, usage of the existing `app.` values has been deprecated and will be removed in a future release. The general pattern for migrating existing usage is to replace `<%= app. %>` with `<%= config.getString('app.') %>`, although in some cases you may need to use for example `config.has('app.')` or `config.getOptionalString('app.')` instead.
+
+The [`@backstage/create-app` changelog](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md#049) also contains more details how to migrate existing usage.
diff --git a/.changeset/rare-ladybugs-invite.md b/.changeset/rare-ladybugs-invite.md
new file mode 100644
index 0000000000..4153363b9c
--- /dev/null
+++ b/.changeset/rare-ladybugs-invite.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-auth-backend': patch
+---
+
+Fixed the fallback identity population to correctly generate an entity reference for `userEntityRef` if no token is provided.
diff --git a/.changeset/short-rockets-remain.md b/.changeset/short-rockets-remain.md
new file mode 100644
index 0000000000..603caec71a
--- /dev/null
+++ b/.changeset/short-rockets-remain.md
@@ -0,0 +1,9 @@
+---
+'@backstage/app-defaults': patch
+'@backstage/core-app-api': minor
+'@backstage/core-plugin-api': minor
+---
+
+- Removed deprecation configuration option `theme` from `AppTheme` of the `AppThemeApi`
+- Removed reference to `theme` in the `app-defaults` default `AppTheme`
+- Removed logic in `AppThemeProvider` that creates `ThemeProvider` from `appTheme.theme`
diff --git a/.changeset/silver-books-clean.md b/.changeset/silver-books-clean.md
new file mode 100644
index 0000000000..9685cc2119
--- /dev/null
+++ b/.changeset/silver-books-clean.md
@@ -0,0 +1,5 @@
+---
+'@backstage/catalog-client': patch
+---
+
+Add support for passing paging parameters to the getEntities call of the catalog client
diff --git a/.changeset/sixty-pandas-switch.md b/.changeset/sixty-pandas-switch.md
new file mode 100644
index 0000000000..91e767e154
--- /dev/null
+++ b/.changeset/sixty-pandas-switch.md
@@ -0,0 +1,8 @@
+---
+'@backstage/plugin-search-backend': minor
+---
+
+Search result location filtering
+
+This change introduces a filter for search results based on their location protocol. The intention is to filter out unsafe or
+malicious values before they can be consumed by the frontend. By default locations must be http/https URLs (or paths).
diff --git a/.changeset/smooth-news-melt.md b/.changeset/smooth-news-melt.md
new file mode 100644
index 0000000000..deb745a46a
--- /dev/null
+++ b/.changeset/smooth-news-melt.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-components': patch
+---
+
+Do not `setState` when unmounted in `OverflowTooltip`
diff --git a/.changeset/soft-scissors-own.md b/.changeset/soft-scissors-own.md
new file mode 100644
index 0000000000..e57a018d50
--- /dev/null
+++ b/.changeset/soft-scissors-own.md
@@ -0,0 +1,5 @@
+---
+'embedded-techdocs-app': patch
+---
+
+The `index.html` template was updated to use the new `config` global.
diff --git a/.changeset/techdocs-sakta-vi-gar.md b/.changeset/techdocs-sakta-vi-gar.md
new file mode 100644
index 0000000000..183d90300b
--- /dev/null
+++ b/.changeset/techdocs-sakta-vi-gar.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs-backend': patch
+---
+
+Fixed a bug preventing cache from being enabled in TechDocs "recommended" deployment mode.
diff --git a/.changeset/ten-candles-call.md b/.changeset/ten-candles-call.md
new file mode 100644
index 0000000000..8907da06bd
--- /dev/null
+++ b/.changeset/ten-candles-call.md
@@ -0,0 +1,9 @@
+---
+'@backstage/core-components': patch
+'@backstage/plugin-scaffolder': patch
+---
+
+In @backstage/plugin-scaffolder - When user will have one option available in hostUrl or owner - autoselect and select component should be readonly.
+
+in @backstage/core-components - Select component has extended API with few more props: native : boolean, disabled: boolean. native - if set to true - Select component will use native browser select picker (not rendered by Material UI lib ).
+disabled - if set to true - action on component will not be possible.
diff --git a/.changeset/thick-deers-trade.md b/.changeset/thick-deers-trade.md
new file mode 100644
index 0000000000..9b023d709a
--- /dev/null
+++ b/.changeset/thick-deers-trade.md
@@ -0,0 +1,17 @@
+---
+'@backstage/core-components': patch
+'@backstage/plugin-catalog': patch
+'@backstage/plugin-circleci': patch
+'@backstage/plugin-cloudbuild': patch
+'@backstage/plugin-github-actions': patch
+'@backstage/plugin-gitops-profiles': patch
+'@backstage/plugin-jenkins': patch
+'@backstage/plugin-lighthouse': patch
+'@backstage/plugin-org': patch
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-search': patch
+'@backstage/plugin-sentry': patch
+'@backstage/plugin-sonarqube': patch
+---
+
+Prefer using `Link` from `@backstage/core-components` rather than material-UI.
diff --git a/.changeset/twenty-tigers-smash.md b/.changeset/twenty-tigers-smash.md
new file mode 100644
index 0000000000..3ad694fbba
--- /dev/null
+++ b/.changeset/twenty-tigers-smash.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-jenkins-backend': patch
+---
+
+feature: add crumbIssuer option to Jenkins (optional) configuration, improve the UI to show a notification after executing the action re-build
diff --git a/.changeset/twenty-tigers-ymash.md b/.changeset/twenty-tigers-ymash.md
new file mode 100644
index 0000000000..c3e03ba75d
--- /dev/null
+++ b/.changeset/twenty-tigers-ymash.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-jenkins': patch
+---
+
+feature: add crumbIssuer option to Jenkins (optional) configuration, improve the UI to show a notification after executing the action re-build
diff --git a/.changeset/unlucky-wolves-reply.md b/.changeset/unlucky-wolves-reply.md
new file mode 100644
index 0000000000..ac42b59abb
--- /dev/null
+++ b/.changeset/unlucky-wolves-reply.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Add DescriptionField override to support Markdown
diff --git a/.changeset/weak-berries-heal.md b/.changeset/weak-berries-heal.md
deleted file mode 100644
index 4160d078fc..0000000000
--- a/.changeset/weak-berries-heal.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-'@backstage/plugin-apache-airflow': minor
----
-
-Introduces a new plugin for the Apache Airflow workflow management platform.
-This implementation has been tested with the Apache Airflow v2 API,
-authenticating with basic authentication through the Backstage proxy plugin.
-
-Supported functionality includes:
-
-- Information card of version information of the Airflow instance
-- Information card of instance health for the meta-database and scheduler
-- Table of DAGs with meta information and status, along with a link to view
- details in the Airflow UI
diff --git a/.changeset/wise-melons-hope.md b/.changeset/wise-melons-hope.md
new file mode 100644
index 0000000000..24a615a7bc
--- /dev/null
+++ b/.changeset/wise-melons-hope.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-app-api': patch
+---
+
+Schema-validate local storage cached session info on load
diff --git a/.changeset/young-dodos-bake.md b/.changeset/young-dodos-bake.md
new file mode 100644
index 0000000000..f620c6a1a4
--- /dev/null
+++ b/.changeset/young-dodos-bake.md
@@ -0,0 +1,5 @@
+---
+'@backstage/core-plugin-api': minor
+---
+
+Removed deprecated types `ApiRefType` and `ApiRefsToTypes`
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index ed4b61099f..9cc5a2378e 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -42,3 +42,4 @@
/.changeset/search-* @backstage/techdocs-core
/.changeset/techdocs-* @backstage/techdocs-core
/cypress/src/integration/plugins/techdocs.spec.ts @backstage/techdocs-core
+/plugins/apache-airflow @backstage/reviewers @cmpadden
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 0000000000..f141107482
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,24 @@
+catalog:
+ - plugins/catalog/**/*
+ - plugins/catalog-*/**/*
+ - packages/catalog-*/**/*
+scaffolder:
+ - plugins/scaffolder/**/*
+ - plugins/scaffolder-*/**/*
+search:
+ - plugins/search/**/*
+ - plugins/search-*/**/*
+ - packages/search-*/**/*
+docs-like-code:
+ - plugins/techdocs/**/*
+ - plugins/techdocs-*/**/*
+ - packages/techdocs-*/**/*
+documentation:
+ - docs/**/*
+microsite:
+ - microsite/**/*
+auth:
+ - plugins/auth-backend/**/*
+ - packages/core-app-api/src/apis/implementations/auth/**/*
+ - packages/core-app-api/src/lib/Auth*/**/*
+ - packages/core-plugin-api/src/apis/definitions/auth.ts
diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt
index 4ec62923d8..8199855c9e 100644
--- a/.github/styles/vocab.txt
+++ b/.github/styles/vocab.txt
@@ -12,6 +12,7 @@ Atlassian
automations
autoscaling
Autoscaling
+autoselect
Avro
backrub
Bigtable
@@ -53,6 +54,7 @@ configs
const
cookiecutter
cron
+cronjobs
css
Datadog
dataflow
@@ -90,10 +92,12 @@ Figma
firehydrant
FireHydrant
Firekube
+Firestore
Fiverr
gitbeaker
GitHub
GitLab
+Gource
Grafana
graphql
GraphQL
@@ -187,6 +191,7 @@ OAuth
octokit
oidc
Okta
+Oldsberg
onboarding
Onboarding
OpenShift
@@ -194,6 +199,8 @@ orgs
pagerduty
pageview
parallelization
+Patrik
+Peloton
plantuml
Platformize
Podman
@@ -293,6 +300,7 @@ truthy
typeahead
ui
unbreak
+Unconference
unmanaged
unregister
unregistration
@@ -304,6 +312,7 @@ utils
validator
validators
varchar
+Wayfair
Weaveworks
Webpack
winston
@@ -316,4 +325,3 @@ Zalando
Zhou
zoomable
zsh
-Firestore
diff --git a/.github/workflows/changeset.yml b/.github/workflows/changeset.yml
index 70afd15eee..a649904a01 100644
--- a/.github/workflows/changeset.yml
+++ b/.github/workflows/changeset.yml
@@ -10,7 +10,7 @@ jobs:
name: Create Changeset PR
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v1
+ - uses: actions/checkout@v2
- name: Install Dependencies
run: yarn --frozen-lockfile
- name: Create Release Pull Request
diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml
index 9d054ff6df..3d0832f958 100644
--- a/.github/workflows/fossa.yml
+++ b/.github/workflows/fossa.yml
@@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v2.3.4
+ uses: actions/checkout@v2
- name: Install Fossa
run: "curl -H 'Cache-Control: no-cache' https://raw.githubusercontent.com/fossas/fossa-cli/master/install.sh | bash"
diff --git a/.github/workflows/label.yml b/.github/workflows/label.yml
new file mode 100644
index 0000000000..dc7127ab0d
--- /dev/null
+++ b/.github/workflows/label.yml
@@ -0,0 +1,12 @@
+name: 'Pull Request Labeler'
+on:
+ - pull_request_target
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/labeler@v3
+ with:
+ repo-token: '${{ secrets.GITHUB_TOKEN }}'
+ sync-labels: true
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index 96a2a20109..5cbf4e32ef 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -24,7 +24,7 @@ jobs:
recent activity from the author. It will be closed if no further activity occurs.
If you are the author and the PR has been closed, feel free to re-open the PR and continue the contribution!
days-before-pr-stale: 7
- days-before-pr-close: 3
+ days-before-pr-close: 5
exempt-pr-labels: reviewer-approved,awaiting-review
stale-pr-label: stale
operations-per-run: 100
diff --git a/README.md b/README.md
index 1078631926..3e91cfe7c3 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,10 @@
# [Backstage](https://backstage.io)
+> 🎄 The maintainers will be taking a break over the holidays from week beginning 20th Dec. The Repository and Discord may be quieter than usual. We will be back next year, rested and restored, on Jan. 3. 🎄
+
+> 🎅 Happy holidays and a Happy New Year to all, and especially those that have made this year special for Backstage! 🎅
+
[](https://opensource.org/licenses/Apache-2.0)
[](https://www.cncf.io/projects)
[](https://github.com/backstage/backstage/actions?query=workflow%3A%22Main+Master+Build%22)
diff --git a/app-config.yaml b/app-config.yaml
index 6cd05f44e8..2837d97d2f 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -74,6 +74,11 @@ proxy:
headers:
X-Api-Key: ${NEW_RELIC_REST_API_KEY}
+ '/newrelic/api':
+ target: https://api.newrelic.com
+ headers:
+ X-Api-Key: ${NEW_RELIC_USER_KEY}
+
'/pagerduty':
target: https://api.pagerduty.com
headers:
diff --git a/docs/api/deprecations.md b/docs/api/deprecations.md
index aca7e42d96..04c3da92bc 100644
--- a/docs/api/deprecations.md
+++ b/docs/api/deprecations.md
@@ -61,3 +61,125 @@ breaking change to make `theme` optional. This means that if you currently
construct the themes that you pass on to `createApp` using `AppTheme` as an
intermediate type, you will need to work around this in some way, for example by
passing the themes to `createApp` more directly.
+
+### Generic Auth API Refs
+
+`Released 2021-12-16 in @backstage/core-plugin-api v0.3.1`
+
+There are four auth Utility API references in `@backstage/core-plugin-api` that
+were too generic to be useful. The APIs in question are `auth0AuthApiRef`,
+`oauth2ApiRef`, `oidcAuthApiRef`, and `samlAuthApiRef`. The issue with these
+APIs was that they had no actual contract of what the backing auth provider was.
+This made it more or less impossible to use these providers in open source
+plugins in any meaningful way. We also did not want to keep these Utility API
+references around just as helpers either, instead opting to remove them and let
+integrators define their own APIs that are more specific to their auth provider.
+This is also falls in line with a long-term goal to unify all auth providers to
+not have separate frontend implementations.
+
+If you're currently using one of these API references for either Sign-In or
+access delegation within an app, there are a couple of steps you need to take to
+migrate to your own custom API.
+
+First, you'll need to define a new Utility API reference. If you're only using
+the API for sign-in, you can put the definition in `packages/app/src/apis.ts`.
+However, if you need to access your auth API inside plugins you you'll need to
+export it from a common package. If you don't already have one we recommended
+creating `@internal/apis` and from there export the API reference.
+
+```ts
+// `ProfileInfoApi & BackstageIdentityApi & SessionApi` are required for sign-in
+// Include `OAuthApi & OpenIdConnectApi` only if applicable
+export const acmeAuthApiRef: ApiRef<
+ OAuthApi &
+ OpenIdConnectApi &
+ ProfileInfoApi &
+ BackstageIdentityApi &
+ SessionApi
+> = createApiRef({
+ id: 'internal.auth.acme',
+});
+```
+
+Next you'll want to wire up the API inside `packages/app/src/apis.ts`, which
+varies depending on which API you're replacing. If you for example are replacing
+the `oauth2ApiRef`, the factory might look like this:
+
+```ts
+// oauth2
+createApiFactory({
+ api: acmeAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ configApi: configApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi, configApi }) =>
+ OAuth2.create({
+ discoveryApi,
+ oauthRequestApi,
+ environment: configApi.getOptionalString('auth.environment'),
+ }),
+});
+```
+
+Provider specific factory implementations, copy the code you need into the
+factory method depending on which apiRef you previously used.
+
+```ts
+// samlAuthApiRef
+SamlAuth.create({
+ discoveryApi,
+ environment: configApi.getOptionalString('auth.environment'),
+});
+
+// oidcAuthApiRef
+OAuth2.create({
+ discoveryApi,
+ oauthRequestApi,
+ provider: {
+ id: 'oidc',
+ title: 'Your Identity Provider',
+ icon: () => null,
+ },
+ environment: configApi.getOptionalString('auth.environment'),
+});
+
+// auth0AuthApiRef
+OAuth2.create({
+ discoveryApi,
+ oauthRequestApi,
+ provider: {
+ id: 'auth0',
+ title: 'Auth0',
+ icon: () => null,
+ },
+ defaultScopes: ['openid', 'email', 'profile'],
+ environment: configApi.getOptionalString('auth.environment'),
+});
+```
+
+Finally, for the provider to show up in your settings menu, you also need to
+update the settings route in `packages/app/src/App.tsx` to pass the
+`acmeAuthApiRef` to the `UserSettingsPage`. This replaces all existing provider
+items, so you might want to add back any of the default ones that you are using
+from the
+[DefaultProviderSettings](https://github.com/backstage/backstage/blob/a3ec122170e0205fd3f9c307b98b1c5e4f55bf5f/plugins/user-settings/src/components/AuthProviders/DefaultProviderSettings.tsx#L35).
+
+```tsx
+
+ }
+ />
+ }
+/>
+```
diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md
index 14920513af..9ebcfc0945 100644
--- a/docs/api/utility-apis.md
+++ b/docs/api/utility-apis.md
@@ -128,8 +128,8 @@ the core APIs. The core APIs are the ones exported by
[configApiRef](../reference/core-plugin-api.configapiref.md).
The core APIs are loaded for any app created with
-[createApp](../reference/core-app-api.createapp.md) from
-[@backstage/core-plugin-api](../reference/core-plugin-api.md), which means that
+[createApp](../reference/app-defaults.createapp.md) from
+[@backstage/core-plugin-api](../reference/app-defaults.md), which means that
there is no step that needs to be taken to include these APIs in an app.
### Plugin APIs
@@ -168,7 +168,7 @@ Lastly, the app itself is the final point where APIs can be added, and what has
the final say in what APIs will be loaded at runtime. The app may override the
factories for any of the core or plugin APIs, with the exception of the config,
app theme, and identity APIs. These are static APIs that are tied into the
-[createApp](../reference/core-app-api.createapp.md) implementation, and
+[createApp](../reference/app-defaults.createapp.md) implementation, and
therefore not possible to override.
Overriding APIs is useful for apps that want to switch out behavior to tailor it
@@ -313,7 +313,7 @@ The common development environment for plugins is included in
[createDevApp](../reference/dev-utils.createdevapp.md) function creates an
application with implementations for all core APIs already present. Contrary to
the method for wiring up Utility API implementations in an app created with
-[createApp](../reference/core-app-api.createapp.md),
+[createApp](../reference/app-defaults.createapp.md),
[createDevApp](../reference/dev-utils.createdevapp.md) uses automatic dependency
injection. This is to make it possible to replace any API implementation, and
having that be reflected in dependents of that API.
diff --git a/docs/architecture-decisions/adr000-template.md b/docs/architecture-decisions/adr000-template.md
index 5e6edd8dba..f16ae7d39f 100644
--- a/docs/architecture-decisions/adr000-template.md
+++ b/docs/architecture-decisions/adr000-template.md
@@ -1,6 +1,7 @@
---
id: adrs-adr000
-title: ADR000: [TITLE]
+title: 'ADR000: [TITLE]'
+# prettier-ignore
description: Architecture Decision Record (ADR) for [TITLE] [DESCRIPTION]
---
diff --git a/docs/architecture-decisions/adr001-add-adr-log.md b/docs/architecture-decisions/adr001-add-adr-log.md
index 872b3311a5..d1bf0f83f5 100644
--- a/docs/architecture-decisions/adr001-add-adr-log.md
+++ b/docs/architecture-decisions/adr001-add-adr-log.md
@@ -1,6 +1,7 @@
---
id: adrs-adr001
-title: ADR001: Architecture Decision Record (ADR) log
+title: 'ADR001: Architecture Decision Record (ADR) log'
+# prettier-ignore
description: Architecture Decision Record (ADR) logs as a reference point for the team
---
diff --git a/docs/architecture-decisions/adr002-default-catalog-file-format.md b/docs/architecture-decisions/adr002-default-catalog-file-format.md
index 013e2cbf39..d0a657fe46 100644
--- a/docs/architecture-decisions/adr002-default-catalog-file-format.md
+++ b/docs/architecture-decisions/adr002-default-catalog-file-format.md
@@ -1,6 +1,7 @@
---
id: adrs-adr002
-title: ADR002: Default Software Catalog File Format
+title: 'ADR002: Default Software Catalog File Format'
+# prettier-ignore
description: Architecture Decision Record (ADR) log on Default Software Catalog File Format
---
diff --git a/docs/architecture-decisions/adr003-avoid-default-exports.md b/docs/architecture-decisions/adr003-avoid-default-exports.md
index 878d0d701c..34e5cd5411 100644
--- a/docs/architecture-decisions/adr003-avoid-default-exports.md
+++ b/docs/architecture-decisions/adr003-avoid-default-exports.md
@@ -1,6 +1,7 @@
---
id: adrs-adr003
-title: ADR003: Avoid Default Exports and Prefer Named Exports
+title: 'ADR003: Avoid Default Exports and Prefer Named Exports'
+# prettier-ignore
description: Architecture Decision Record (ADR) log on Avoid Default Exports and Prefer Named Exports
---
diff --git a/docs/architecture-decisions/adr004-module-export-structure.md b/docs/architecture-decisions/adr004-module-export-structure.md
index e6e378da9d..95d0f27811 100644
--- a/docs/architecture-decisions/adr004-module-export-structure.md
+++ b/docs/architecture-decisions/adr004-module-export-structure.md
@@ -1,6 +1,7 @@
---
id: adrs-adr004
-title: ADR004: Module Export Structure
+title: 'ADR004: Module Export Structure'
+# prettier-ignore
description: Architecture Decision Record (ADR) log on Module Export Structure
---
diff --git a/docs/architecture-decisions/adr005-catalog-core-entities.md b/docs/architecture-decisions/adr005-catalog-core-entities.md
index 83196c12e2..1206d9e83d 100644
--- a/docs/architecture-decisions/adr005-catalog-core-entities.md
+++ b/docs/architecture-decisions/adr005-catalog-core-entities.md
@@ -1,6 +1,7 @@
---
id: adrs-adr005
-title: ADR005: Catalog Core Entities
+title: 'ADR005: Catalog Core Entities'
+# prettier-ignore
description: Architecture Decision Record (ADR) log on Catalog Core Entities
---
diff --git a/docs/architecture-decisions/adr006-avoid-react-fc.md b/docs/architecture-decisions/adr006-avoid-react-fc.md
index eecb4ad87a..ea43cb215d 100644
--- a/docs/architecture-decisions/adr006-avoid-react-fc.md
+++ b/docs/architecture-decisions/adr006-avoid-react-fc.md
@@ -1,6 +1,7 @@
---
id: adrs-adr006
-title: ADR006: Avoid React.FC and React.SFC
+title: 'ADR006: Avoid React.FC and React.SFC'
+# prettier-ignore
description: Architecture Decision Record (ADR) log on Avoid React.FC and React.SFC
---
diff --git a/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md b/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md
index 68394de17c..a61bb78787 100644
--- a/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md
+++ b/docs/architecture-decisions/adr007-use-msw-to-mock-service-requests.md
@@ -1,6 +1,7 @@
---
id: adrs-adr007
-title: ADR007: Use MSW to mock http requests
+title: 'ADR007: Use MSW to mock http requests'
+# prettier-ignore
description: Architecture Decision Record (ADR) log on Use MSW to mock http requests
---
diff --git a/docs/architecture-decisions/adr008-default-catalog-file-name.md b/docs/architecture-decisions/adr008-default-catalog-file-name.md
index 6794dc87cc..fca65c8355 100644
--- a/docs/architecture-decisions/adr008-default-catalog-file-name.md
+++ b/docs/architecture-decisions/adr008-default-catalog-file-name.md
@@ -1,6 +1,7 @@
---
id: adrs-adr008
-title: ADR008: Default Catalog File Name
+title: 'ADR008: Default Catalog File Name'
+# prettier-ignore
description: Architecture Decision Record (ADR) log on Default Catalog File Name
---
diff --git a/docs/architecture-decisions/adr009-entity-references.md b/docs/architecture-decisions/adr009-entity-references.md
index a6fe3ee583..254ae7ac1d 100644
--- a/docs/architecture-decisions/adr009-entity-references.md
+++ b/docs/architecture-decisions/adr009-entity-references.md
@@ -1,6 +1,7 @@
---
id: adrs-adr009
-title: ADR009: Entity References
+title: 'ADR009: Entity References'
+# prettier-ignore
description: Architecture Decision Record (ADR) log on Entity References
---
diff --git a/docs/architecture-decisions/adr010-luxon-date-library.md b/docs/architecture-decisions/adr010-luxon-date-library.md
index 43f18f0f69..65cabd256c 100644
--- a/docs/architecture-decisions/adr010-luxon-date-library.md
+++ b/docs/architecture-decisions/adr010-luxon-date-library.md
@@ -1,6 +1,7 @@
---
id: adrs-adr010
-title: ADR010: Use the Luxon Date Library
+title: 'ADR010: Use the Luxon Date Library'
+# prettier-ignore
description: Architecture Decision Record (ADR) for Luxon Date Library
---
diff --git a/docs/architecture-decisions/adr011-plugin-package-structure.md b/docs/architecture-decisions/adr011-plugin-package-structure.md
index 374a3ed546..363cae01a5 100644
--- a/docs/architecture-decisions/adr011-plugin-package-structure.md
+++ b/docs/architecture-decisions/adr011-plugin-package-structure.md
@@ -1,6 +1,7 @@
---
id: adrs-adr011
-title: ADR011: Plugin Package Structure
+title: 'ADR011: Plugin Package Structure'
+# prettier-ignore
description: Architecture Decision Record (ADR) for Plugin Package Structure
---
diff --git a/docs/architecture-decisions/adr012-use-luxon-locale-and-date-presets.md b/docs/architecture-decisions/adr012-use-luxon-locale-and-date-presets.md
index 0893c105f4..25bb6c1833 100644
--- a/docs/architecture-decisions/adr012-use-luxon-locale-and-date-presets.md
+++ b/docs/architecture-decisions/adr012-use-luxon-locale-and-date-presets.md
@@ -1,6 +1,7 @@
---
id: adrs-adr012
-title: ADR012: Use Luxon.toLocaleString and date/time presets
+title: 'ADR012: Use Luxon.toLocaleString and date/time presets'
+# prettier-ignore
description: Architecture Decision Record (ADR) for using Luxon's toLocaleString method and date/time presets for displaying dates and times
---
diff --git a/docs/architecture-decisions/index.md b/docs/architecture-decisions/index.md
index f852170b1e..15d1b0e9d5 100644
--- a/docs/architecture-decisions/index.md
+++ b/docs/architecture-decisions/index.md
@@ -2,6 +2,7 @@
id: adrs-overview
title: Architecture Decision Records (ADR)
sidebar_label: Overview
+# prettier-ignore
description: Overview of Architecture Decision Records (ADR)
---
diff --git a/docs/auth/microsoft/provider.md b/docs/auth/microsoft/provider.md
index 1e24235f1a..ef79127223 100644
--- a/docs/auth/microsoft/provider.md
+++ b/docs/auth/microsoft/provider.md
@@ -48,6 +48,6 @@ The Microsoft provider is a structure with three configuration keys:
## Adding the provider to the Backstage frontend
-To add the provider to the frontend, add the `microsoftAuthApi` reference and
+To add the provider to the frontend, add the `microsoftAuthApiRef` reference and
`SignInPage` component as shown in
[Adding the provider to the sign-in page](../index.md#adding-the-provider-to-the-sign-in-page).
diff --git a/docs/conf/writing.md b/docs/conf/writing.md
index 7945d6c980..a4da1e3df0 100644
--- a/docs/conf/writing.md
+++ b/docs/conf/writing.md
@@ -67,7 +67,7 @@ production build.
## Configuration Files
-It is possible to have multiple configuration files (bundled and/or remote),
+It is possible to have multiple configuration files (bundled and/or remote\*),
both to support different environments, but also to define configuration that is
local to specific packages. The configuration files to load are selected using a
`--config ` flag, and it is possible to load any number of
@@ -77,6 +77,9 @@ root when running the backend, you would use `--config ../../my-config.yaml`,
and for config file on a config server you would use
`--config https://some.domain.io/app-config.yaml`
+**Note**: In case URLs are passed, it is also needed to set the remote option in
+the loadBackendConfig call.
+
If no `config` flags are specified, the default behavior is to load
`app-config.yaml` and, if it exists, `app-config.local.yaml` from the repo root.
In the provided project setup, `app-config.local.yaml` is `.gitignore`'d, making
diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md
index 07a12f54a0..32ab830322 100644
--- a/docs/features/kubernetes/configuration.md
+++ b/docs/features/kubernetes/configuration.md
@@ -264,6 +264,12 @@ following objects:
- horizontalpodautoscalers
- ingresses
+The following RBAC permissions are required on the batch API group for the
+following objects:
+
+- jobs
+- cronjobs
+
## Surfacing your Kubernetes components as part of an entity
There are two ways to surface your Kubernetes components as part of an entity.
diff --git a/docs/features/search/search-engines.md b/docs/features/search/search-engines.md
index 1331c73b72..8342c74096 100644
--- a/docs/features/search/search-engines.md
+++ b/docs/features/search/search-engines.md
@@ -90,6 +90,18 @@ within your instance. The configuration options are documented in the
The underlying functionality is using official ElasticSearch client version 7.x,
meaning that ElasticSearch version 7 is the only one confirmed to be supported.
+Should you need to create your own bespoke search experiences that require more
+than just a query translator (such as faceted search or Relay pagination), you
+can access the configuration of the search engine in order to create new elastic
+search clients. The version of the client need not be the same as one used
+internally by the elastic search engine plugin. For example:
+
+```typescript
+import { Client } from '@elastic/elastic-search';
+
+const client = searchEngine.newClient(options => new Client(options));
+```
+
## Example configurations
### AWS
diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md
index ebb7af4398..fab2e1e2cb 100644
--- a/docs/features/software-templates/writing-custom-field-extensions.md
+++ b/docs/features/software-templates/writing-custom-field-extensions.md
@@ -26,7 +26,7 @@ You can create your own Field Extension by using the
`API` like below:
```tsx
-//packages/app/scaffolder/MyCustomExtension/MyCustomExtension.tsx
+//packages/app/src/scaffolder/MyCustomExtension/MyCustomExtension.tsx
import React from 'react';
import { FieldProps, FieldValidation } from '@rjsf/core';
import FormControl from '@material-ui/core/FormControl';
@@ -68,7 +68,7 @@ export const myCustomValidation = (
```
```tsx
-// packages/app/scaffolder/MyCustomExtension/extensions.ts
+// packages/app/src/scaffolder/MyCustomExtension/extensions.ts
/*
This is where the magic happens and creates the custom field extension.
@@ -94,7 +94,7 @@ export const MyCustomFieldExtension = plugin.provide(
```
```tsx
-// packages/app/scaffolder/MyCustomExtension/index.ts
+// packages/app/src/scaffolder/MyCustomExtension/index.ts
export { MyCustomFieldExtension } from './extension';
```
@@ -102,7 +102,7 @@ export { MyCustomFieldExtension } from './extension';
Once all these files are in place, you then need to provide your custom
extension to the `scaffolder` plugin.
-You do this in `packages/app/App.tsx`. You need to provide the
+You do this in `packages/app/src/App.tsx`. You need to provide the
`customFieldExtensions` as children to the `ScaffolderPage`.
```tsx
@@ -118,7 +118,7 @@ const routes = (
Should look something like this instead:
```tsx
-import { MyCustomFieldExtension } from './scafffolder/MyCustomExtension';
+import { MyCustomFieldExtension } from './scaffolder/MyCustomExtension';
const routes = (
...
diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md
index 5548adf7f0..30cb8314a0 100644
--- a/docs/features/techdocs/cli.md
+++ b/docs/features/techdocs/cli.md
@@ -1,4 +1,9 @@
-# TechDocs CLI
+---
+id: cli
+title: TechDocs CLI
+# prettier-ignore
+description: TechDocs CLI - a utility command line interface for managing TechDocs sites in Backstage.
+---
Utility command line interface for managing TechDocs sites in
[Backstage](https://github.com/backstage/backstage).
diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md
index 241ae7ef03..a4ca0771df 100644
--- a/docs/getting-started/app-custom-theme.md
+++ b/docs/getting-started/app-custom-theme.md
@@ -54,7 +54,7 @@ For example, adding the theme that we created in the previous section can be
done like this:
```ts
-import { createApp } from '@backstage/core-app-api';
+import { createApp } from '@backstage/app-defaults';
const app = createApp({
apis: ...,
diff --git a/docs/plugins/composability.md b/docs/plugins/composability.md
index 33568ac561..05af512f92 100644
--- a/docs/plugins/composability.md
+++ b/docs/plugins/composability.md
@@ -368,7 +368,7 @@ The following is an example of creation and usage of a parameterized route:
```tsx
// Creation of a parameterized route
const myRouteRef = createRouteRef({
- title: 'My Named Route',
+ id: 'myroute',
params: ['name']
})
diff --git a/docs/tutorials/journey.md b/docs/tutorials/journey.md
index 249507ddf0..126bb5ae0d 100644
--- a/docs/tutorials/journey.md
+++ b/docs/tutorials/journey.md
@@ -48,7 +48,6 @@ that implements the `OAuthApi` type, it's now working in the frontend too.
```ts
const spotifyAuthApiRef = createApiRef({
id: 'core.auth.spotify',
- description: 'Provides authentication towards Spotify APIs',
});
```
diff --git a/docs/tutorials/using-backstage-proxy-within-plugin.md b/docs/tutorials/using-backstage-proxy-within-plugin.md
index b0c6bef8ef..771aa7a652 100644
--- a/docs/tutorials/using-backstage-proxy-within-plugin.md
+++ b/docs/tutorials/using-backstage-proxy-within-plugin.md
@@ -99,7 +99,6 @@ export interface MyAwesomeApi {
export const myAwesomeApiRef = createApiRef({
id: 'plugin.my-awesome-api.service',
- description: 'Example API definition',
});
```
diff --git a/microsite/blog/2021-12-20-backstage-wrapped-2021.md b/microsite/blog/2021-12-20-backstage-wrapped-2021.md
new file mode 100644
index 0000000000..2a6234a9f6
--- /dev/null
+++ b/microsite/blog/2021-12-20-backstage-wrapped-2021.md
@@ -0,0 +1,145 @@
+---
+title: Backstage Wrapped 2021
+author: Lee Mills, Spotify
+authorURL: https://github.com/leemills83
+authorImageURL: https://avatars1.githubusercontent.com/u/1236238?s=460&v=4
+---
+
+
+
+What a year 2021 has been for the Backstage project and community. As the year comes to a close, we can look back at some of the happenings, the successes, and welcome all the newcomers to our growing community.
+
+In a year that has had its fair share of ups and downs, this community has been something that has brought me so much in the support you provide each other, your eagerness, passion, and willingness to share. Thank you to all of you for creating such a great community, and welcome to everyone joining us. I can’t wait to meet you all and see what great things we can create together in the new year.
+
+In 2021 we’ve seen the project and community grow in every dimension, reaching over 75 public adopters, over 500 contributors from around the world all bringing new ideas and perspectives. We’ve had so many of you sharing and demoing your hard work in our monthly community sessions, added new plugins, built new features and really started to see Backstage have an impact for engineering teams across so many of our users.
+
+Now let’s hear from the maintainers and look at the stats!
+
+
+
+## The community grew…and grew
+
+This year, Netflix, [Expedia][metrics], Wayfair, Peloton, [Box][10], [DAZN][dazn], TELUS, and Unity all officially joined the community — along with more than 50 other public [adopters][md]. Contributors grew by leaps and bounds, too.
+
+Patrik Oldsberg ([@Rugvip][rugvip]):
+
+> It’s been awesome to see the engagement by the community grow even more in 2021, especially the way people within the community support each other. Thanks to our growing number of stable, experienced, and knowledgeable contributors it’s now possible to get help in places like Discord anytime, day or night.
+
+### More adopters, more contributors
+
+
+
+In January, Backstage had 17 public adopters. By September, [we had 50][50]. And the curve only got steeper from there.
+
+- **75+ public adopters**
+- **500+ contributors**
+- **4,000+ Discord members**
+- **9,000+ PRs & issues**
+
+### A very busy repo
+
+Along with the growth in contributors, PRs, and issues, our active, thriving community has crossed (or is just about to cross) other big milestones on GitHub, too.
+
+- **Nearly 2,000 forks**
+- **20,000+ commits**
+- **14,000+ GitHub stars**
+
+### Where in the world is the Backstage community?
+
+Watch the visualization below to see where in the world all those contributions came from this past year.
+
+
+
+### What does 9,000 contributions look like?
+
+
+
+Can you spot your name in the video above?
+
+Picking up from the end of 2020, the video shows every single contribution to the Backstage source code over this past year. The visualization was built using [Gource.io][gource]:
+
+> Software projects are displayed by Gource as an animated tree with the root directory of the project at its centre. Directories appear as branches with files as leaves. Developers can be seen working on the tree at the times they contributed to the project.
+
+And if your name isn’t in there, it’s never too late to get started [contributing][contributing] — so that you too can become a bright, sparkling, laser-beam-shooting spider-node thing in 2022!
+
+## Other 2021 milestones
+
+As the community grew, so did the product — with the addition of new features as well as important performance and stability improvements to the core.
+
+Ben Lambert ([@benjdlambert][blam]):
+
+> We’ve had a lot of priorities this year. Our first priority was rebuilding the catalog to make it more efficient. Then, toward the later part of the year, we’ve been stabilizing the core with deprecations and API changes ahead of our 1.0 release of the Core Framework next year.
+>
+> It’s been a super productive year for us all as Core Maintainers, and the product has come on leaps and bounds with the help of everyone involved in the project!
+
+### New features
+
+These new features were built with the community, and made with customization and extensibility in mind.
+
+- **Kubernetes Service Monitoring ([RFC][k8s])**
+- **Search Platform ([Alpha][search])**
+- **Composable Homepage ([README][home])**
+- **Authorization Framework ([RFC][auth])**
+
+### Major stability updates
+
+As we get the ball rolling for [CNCF Incubation][cncf], we’ve been busy updating, refining, documenting, deprecating, and stabilizing the core.
+
+- **Software Templates ([Beta][scaffolder])**
+- **TechDocs ([Beta][techdocs])**
+- **API Reference ([docs][api])**
+
+### 50+ open source plugins
+
+We reached [50+ plugins][plugins] built by the community this year, including official plugins (**Snyk**, **FireHydrant**), highly requested plugins (**PagerDuty**, **Prometheus**), and plugins based on new ideas and functionality (**XCMetrics**, **[Tech Insights][techrfc]**) — all tailor-made for Backstage.
+
+### In the spotlight
+
+This year Backstage was center stage at both community-organized events and at major conferences.
+
+- **New (and [expanded][09]) Community Sessions**
+- **Backstage Users Unconference + [Open Mic][openmic]**
+- **KubeCon + CloudNativeCon [Europe][europe] & [North America][na]**
+- **GitHub Universe 2021 [Keynote][day2]**
+
+Find more talks, podcasts, blog posts, and Backstage in the news [here][news].
+
+## Goodbye, 2021. Hello, 2022!
+
+Lastly, thanks to everyone in the global Backstage community who contributed to the open source project in 2021.
+
+This was the first year Spotify hosted [Backstage Community Sessions][meetups], and it’s been a great source of inspiration, motivation, and joy getting to know all the people behind the contributions. We look forward to seeing all your faces again in 2022 — and hopefully many new ones as well.
+
+Until then, [grab this Backstage Zoom background][zoom] to use at the next Community Session.
+
+
+
+See you then! Happy holidays from everyone on the Backstage team at Spotify!
+
+[metrics]: https://backstage.spotify.com/blog/measuring-backstage-proof-of-value-at-expedia/
+[10]: https://backstage.spotify.com/blog/community-session/10-box-and-spotify-paid-plugins/
+[dazn]: https://backstage.spotify.com/blog/adopter-spotlight/dazn/
+[md]: https://github.com/backstage/backstage/blob/master/ADOPTERS.md
+[rugvip]: https://github.com/Rugvip
+[50]: https://backstage.io/blog/2021/09/30/50-public-adopters
+[gource]: https://gource.io
+[contributing]: https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md
+[blam]: https://github.com/benjdlambert
+[k8s]: https://github.com/backstage/backstage/issues/2857
+[search]: https://backstage.io/blog/2021/06/24/announcing-backstage-search-platform
+[home]: https://github.com/backstage/backstage/blob/master/plugins/home/README.md
+[auth]: https://backstage.spotify.com/blog/authorization-framework-rfc/
+[scaffolder]: https://backstage.io/blog/2021/07/26/software-templates-are-now-in-beta
+[techdocs]: https://backstage.io/blog/2021/09/16/the-techdocs-beta-has-landed
+[api]: https://backstage.io/docs/reference/
+[cncf]: https://github.com/cncf/toc/pull/717
+[plugins]: https://backstage.io/plugins
+[techrfc]: https://github.com/backstage/backstage/issues/7576
+[09]: https://backstage.spotify.com/blog/community-session/09-prometheus-homepage-templates/
+[openmic]: https://backstage-openmic.com
+[europe]: https://www.youtube.com/watch?v=aIURaocR5D8
+[na]: https://backstage.spotify.com/blog/kubecon-na-2021/
+[day2]: https://www.youtube.com/watch?v=7yQJlYvpoYs&t=269s
+[news]: https://github.com/backstage/community#newsletters
+[meetups]: https://github.com/backstage/community#meetups
+[zoom]: https://drive.google.com/file/d/14wOPXEBZ5Qmi29ms4Sqp1m56dM0AIHbB/view?usp=sharing
diff --git a/microsite/blog/assets/21-12-20/Backstage-background-02-2021.png b/microsite/blog/assets/21-12-20/Backstage-background-02-2021.png
new file mode 100644
index 0000000000..bf3c72ba70
Binary files /dev/null and b/microsite/blog/assets/21-12-20/Backstage-background-02-2021.png differ
diff --git a/microsite/blog/assets/21-12-20/backstage-wrapped-2021.png b/microsite/blog/assets/21-12-20/backstage-wrapped-2021.png
new file mode 100644
index 0000000000..6ac5d5f67c
Binary files /dev/null and b/microsite/blog/assets/21-12-20/backstage-wrapped-2021.png differ
diff --git a/microsite/blog/assets/21-12-20/public-backstage-adopters-2021.png b/microsite/blog/assets/21-12-20/public-backstage-adopters-2021.png
new file mode 100644
index 0000000000..aac981d5f9
Binary files /dev/null and b/microsite/blog/assets/21-12-20/public-backstage-adopters-2021.png differ
diff --git a/package.json b/package.json
index 2d63f92d3a..57cf603090 100644
--- a/package.json
+++ b/package.json
@@ -55,9 +55,9 @@
},
"version": "1.0.0",
"dependencies": {
- "@microsoft/api-documenter": "^7.13.68",
- "@microsoft/api-extractor": "^7.18.7",
- "@microsoft/api-extractor-model": "^7.13.5",
+ "@microsoft/api-documenter": "^7.13.77",
+ "@microsoft/api-extractor": "^7.19.2",
+ "@microsoft/api-extractor-model": "^7.15.1",
"@microsoft/tsdoc": "^0.13.2"
},
"devDependencies": {
@@ -78,11 +78,12 @@
"prettier": "^2.2.1",
"shx": "^0.3.2",
"ts-node": "^10.4.0",
+ "typescript": "~4.3.5",
"yarn-lock-check": "^1.0.5"
},
"prettier": "@spotify/prettier-config",
"lint-staged": {
- "*.{js,jsx,ts,tsx}": [
+ "*.{js,jsx,ts,tsx,mjs,cjs}": [
"eslint --fix",
"prettier --write"
],
diff --git a/packages/app-defaults/src/defaults/apis.ts b/packages/app-defaults/src/defaults/apis.ts
index d3c76d52bd..29aa62bdab 100644
--- a/packages/app-defaults/src/defaults/apis.ts
+++ b/packages/app-defaults/src/defaults/apis.ts
@@ -34,6 +34,8 @@ import {
OneLoginAuth,
UnhandledErrorForwarder,
AtlassianAuth,
+ createFetchApi,
+ FetchMiddlewares,
} from '@backstage/core-app-api';
import {
@@ -42,6 +44,8 @@ import {
analyticsApiRef,
errorApiRef,
discoveryApiRef,
+ fetchApiRef,
+ identityApiRef,
oauthRequestApiRef,
googleAuthApiRef,
githubAuthApiRef,
@@ -92,6 +96,27 @@ export const apis = [
deps: { errorApi: errorApiRef },
factory: ({ errorApi }) => WebStorage.create({ errorApi }),
}),
+ createApiFactory({
+ api: fetchApiRef,
+ deps: {
+ configApi: configApiRef,
+ identityApi: identityApiRef,
+ discoveryApi: discoveryApiRef,
+ },
+ factory: ({ configApi, identityApi, discoveryApi }) => {
+ return createFetchApi({
+ middleware: [
+ FetchMiddlewares.resolvePluginProtocol({
+ discoveryApi,
+ }),
+ FetchMiddlewares.injectIdentityAuth({
+ identityApi,
+ config: configApi,
+ }),
+ ],
+ });
+ },
+ }),
createApiFactory({
api: oauthRequestApiRef,
deps: {},
diff --git a/packages/app-defaults/src/defaults/components.tsx b/packages/app-defaults/src/defaults/components.tsx
index 139bea4862..93a5d9cd50 100644
--- a/packages/app-defaults/src/defaults/components.tsx
+++ b/packages/app-defaults/src/defaults/components.tsx
@@ -73,7 +73,7 @@ const DefaultErrorBoundaryFallback = ({
};
/**
- * Creates a set of default components to pass along to {@link @backstage/core-app-api#createApp}.
+ * Creates a set of default components to pass along to {@link @backstage/core-app-api#createSpecializedApp}.
*
* @public
*/
diff --git a/packages/app-defaults/src/defaults/themes.tsx b/packages/app-defaults/src/defaults/themes.tsx
index 90a4a0f47f..397c160168 100644
--- a/packages/app-defaults/src/defaults/themes.tsx
+++ b/packages/app-defaults/src/defaults/themes.tsx
@@ -28,7 +28,6 @@ export const themes: AppTheme[] = [
title: 'Light Theme',
variant: 'light',
icon: ,
- theme: lightTheme,
Provider: ({ children }) => (
{children}
@@ -40,7 +39,6 @@ export const themes: AppTheme[] = [
title: 'Dark Theme',
variant: 'dark',
icon: ,
- theme: darkTheme,
Provider: ({ children }) => (
{children}
diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md
index 3f3ac75853..d1055a54bf 100644
--- a/packages/app/CHANGELOG.md
+++ b/packages/app/CHANGELOG.md
@@ -1,5 +1,23 @@
# example-app
+## 0.2.57
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/plugin-github-actions@0.4.27
+ - @backstage/core-app-api@0.2.1
+ - @backstage/plugin-kubernetes@0.5.1
+ - @backstage/core-plugin-api@0.3.1
+ - @backstage/core-components@0.8.1
+ - @backstage/plugin-org@0.3.31
+ - @backstage/plugin-azure-devops@0.1.7
+ - @backstage/cli@0.10.2
+ - @backstage/catalog-model@0.9.8
+ - @backstage/plugin-techdocs@0.12.10
+ - @backstage/plugin-catalog-react@0.6.7
+ - @backstage/plugin-apache-airflow@0.1.0
+
## 0.2.56
### Patch Changes
diff --git a/packages/app/package.json b/packages/app/package.json
index 99b90952e3..1f609858f6 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -1,39 +1,40 @@
{
"name": "example-app",
- "version": "0.2.56",
+ "version": "0.2.57",
"private": true,
"bundled": true,
"dependencies": {
"@backstage/app-defaults": "^0.1.2",
- "@backstage/catalog-model": "^0.9.7",
- "@backstage/cli": "^0.10.1",
- "@backstage/core-app-api": "^0.2.0",
- "@backstage/core-components": "^0.8.0",
- "@backstage/core-plugin-api": "^0.3.0",
+ "@backstage/catalog-model": "^0.9.8",
+ "@backstage/cli": "^0.10.2",
+ "@backstage/core-app-api": "^0.2.1",
+ "@backstage/core-components": "^0.8.1",
+ "@backstage/core-plugin-api": "^0.3.1",
"@backstage/integration-react": "^0.1.15",
"@backstage/plugin-api-docs": "^0.6.18",
- "@backstage/plugin-azure-devops": "^0.1.6",
- "@backstage/plugin-apache-airflow": "^0.0.0",
+ "@backstage/plugin-azure-devops": "^0.1.7",
+ "@backstage/plugin-apache-airflow": "^0.1.0",
"@backstage/plugin-badges": "^0.2.16",
"@backstage/plugin-catalog": "^0.7.4",
"@backstage/plugin-catalog-graph": "^0.2.3",
"@backstage/plugin-catalog-import": "^0.7.5",
- "@backstage/plugin-catalog-react": "^0.6.5",
+ "@backstage/plugin-catalog-react": "^0.6.7",
"@backstage/plugin-circleci": "^0.2.31",
"@backstage/plugin-cloudbuild": "^0.2.29",
"@backstage/plugin-code-coverage": "^0.1.19",
"@backstage/plugin-cost-insights": "^0.11.13",
"@backstage/plugin-explore": "^0.3.22",
"@backstage/plugin-gcp-projects": "^0.3.10",
- "@backstage/plugin-github-actions": "^0.4.26",
+ "@backstage/plugin-github-actions": "^0.4.27",
"@backstage/plugin-graphiql": "^0.2.24",
"@backstage/plugin-home": "^0.4.7",
"@backstage/plugin-jenkins": "^0.5.14",
"@backstage/plugin-kafka": "^0.2.22",
- "@backstage/plugin-kubernetes": "^0.5.0",
+ "@backstage/plugin-kubernetes": "^0.5.1",
"@backstage/plugin-lighthouse": "^0.2.31",
"@backstage/plugin-newrelic": "^0.3.10",
- "@backstage/plugin-org": "^0.3.30",
+ "@backstage/plugin-newrelic-dashboard": "^0.1.0",
+ "@backstage/plugin-org": "^0.3.31",
"@backstage/plugin-pagerduty": "0.3.19",
"@backstage/plugin-rollbar": "^0.3.20",
"@backstage/plugin-scaffolder": "^0.11.14",
@@ -41,7 +42,7 @@
"@backstage/plugin-sentry": "^0.3.30",
"@backstage/plugin-shortcuts": "^0.1.15",
"@backstage/plugin-tech-radar": "^0.4.13",
- "@backstage/plugin-techdocs": "^0.12.9",
+ "@backstage/plugin-techdocs": "^0.12.10",
"@backstage/plugin-todo": "^0.1.16",
"@backstage/plugin-user-settings": "^0.3.13",
"@backstage/search-common": "^0.2.0",
diff --git a/packages/app/public/index.html b/packages/app/public/index.html
index d86f09f0b0..8273576b01 100644
--- a/packages/app/public/index.html
+++ b/packages/app/public/index.html
@@ -47,13 +47,12 @@
min-height: 100%;
}
- <%= app.title %>
+ <%= config.getString('app.title') %>
- <% if (app.googleAnalyticsTrackingId && typeof
- app.googleAnalyticsTrackingId==='string' ) { %>
+ <% if (config.has('app.googleAnalyticsTrackingId')) { %>
- <% } %> <% if (app.datadogRum.clientToken && app.datadogRum.applicationId )
- { %>
+ <% } %> <% if (config.has('app.datadogRum')) { %>
<% } %>
diff --git a/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx b/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx
index 43eaa6218c..9229bc61dc 100644
--- a/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx
+++ b/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx
@@ -16,7 +16,6 @@
import { AppThemeApi, appThemeApiRef } from '@backstage/core-plugin-api';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
-import { BackstageTheme } from '@backstage/theme';
import userEvent from '@testing-library/user-event';
import React from 'react';
import ObservableImpl from 'zen-observable';
@@ -41,13 +40,13 @@ describe('SidebarThemeSwitcher', () => {
id: 'dark',
title: 'Dark Theme',
variant: 'dark',
- theme: {} as unknown as BackstageTheme,
+ Provider: jest.fn(),
},
{
id: 'light',
title: 'Light Theme',
variant: 'light',
- theme: {} as unknown as BackstageTheme,
+ Provider: jest.fn(),
},
]);
});
diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx
index adea9e14f2..36d6abcfd1 100644
--- a/packages/dev-utils/src/devApp/render.tsx
+++ b/packages/dev-utils/src/devApp/render.tsx
@@ -159,7 +159,7 @@ export class DevAppBuilder {
* Build a DevApp component using the resources registered so far
*/
build(): ComponentType<{}> {
- const dummyRouteRef = createRouteRef({ title: 'Page of another plugin' });
+ const dummyRouteRef = createRouteRef({ id: 'dummy' });
const DummyPage = () => Page belonging to another plugin.;
attachComponentData(DummyPage, 'core.mountPoint', dummyRouteRef);
diff --git a/packages/embedded-techdocs-app/public/index.html b/packages/embedded-techdocs-app/public/index.html
index a6102f010d..fb874df173 100644
--- a/packages/embedded-techdocs-app/public/index.html
+++ b/packages/embedded-techdocs-app/public/index.html
@@ -47,7 +47,7 @@
min-height: 100%;
}
- <%= app.title %>
+ <%= config.getString('app.title') %>
diff --git a/packages/integration-react/src/api/ScmIntegrationsApi.ts b/packages/integration-react/src/api/ScmIntegrationsApi.ts
index 06f28a6c4d..ec2185701b 100644
--- a/packages/integration-react/src/api/ScmIntegrationsApi.ts
+++ b/packages/integration-react/src/api/ScmIntegrationsApi.ts
@@ -45,5 +45,4 @@ export class ScmIntegrationsApi {
export const scmIntegrationsApiRef: ApiRef =
createApiRef({
id: 'integration.scmintegrations',
- description: 'All of the registered SCM integrations of your config',
});
diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js
index 72ff42311d..f5852b0164 100644
--- a/packages/storybook/.storybook/main.js
+++ b/packages/storybook/.storybook/main.js
@@ -59,7 +59,7 @@ module.exports = ({ args }) => {
},
},
{
- test: /\.(jsx?|mjs)$/,
+ test: /\.(jsx?|mjs|cjs)$/,
exclude: /node_modules/,
loader: require.resolve('@sucrase/webpack-loader'),
options: {
diff --git a/packages/test-utils/api-report.md b/packages/test-utils/api-report.md
index a01f306fab..a75a6d958e 100644
--- a/packages/test-utils/api-report.md
+++ b/packages/test-utils/api-report.md
@@ -12,13 +12,14 @@ import { ErrorApi } from '@backstage/core-plugin-api';
import { ErrorApiError } from '@backstage/core-plugin-api';
import { ErrorApiErrorContext } from '@backstage/core-plugin-api';
import { ExternalRouteRef } from '@backstage/core-plugin-api';
+import { JsonValue } from '@backstage/types';
import { Observable } from '@backstage/types';
import { ReactElement } from 'react';
import { ReactNode } from 'react';
import { RenderResult } from '@testing-library/react';
import { RouteRef } from '@backstage/core-plugin-api';
import { StorageApi } from '@backstage/core-plugin-api';
-import { StorageValueChange } from '@backstage/core-plugin-api';
+import { StorageValueSnapshot } from '@backstage/core-plugin-api';
// @public
export type AsyncLogCollector = () => Promise;
@@ -34,52 +35,6 @@ export type ErrorWithContext = {
context?: ErrorApiErrorContext;
};
-// @public @deprecated (undocumented)
-export class Keyboard {
- constructor(
- target: any,
- {
- debug,
- }?: {
- debug?: boolean | undefined;
- },
- );
- // (undocumented)
- click(): Promise;
- // (undocumented)
- debug: boolean;
- // (undocumented)
- document: any;
- // (undocumented)
- enter(value: any): Promise;
- // (undocumented)
- escape(): Promise;
- // (undocumented)
- get focused(): any;
- // (undocumented)
- static fromReadableInput(input: any): any;
- // (undocumented)
- _log(message: any, ...args: any[]): void;
- // (undocumented)
- _pretty(element: any): string;
- // (undocumented)
- send(chars: any): Promise;
- // (undocumented)
- _sendKey(key: any, charCode: any, action: any): Promise;
- // (undocumented)
- tab(): Promise;
- // (undocumented)
- static toReadableInput(chars: any): any;
- // (undocumented)
- toString(): string;
- // (undocumented)
- static type(target: any, input: any): Promise;
- // (undocumented)
- type(input: any): Promise;
- // (undocumented)
- static typeDebug(target: any, input: any): Promise;
-}
-
// @public
export type LogCollector = AsyncLogCollector | SyncLogCollector;
@@ -127,11 +82,13 @@ export class MockStorageApi implements StorageApi {
// (undocumented)
get(key: string): T | undefined;
// (undocumented)
- observe$(key: string): Observable>;
+ observe$(key: string): Observable>;
// (undocumented)
remove(key: string): Promise;
// (undocumented)
set(key: string, data: T): Promise;
+ // (undocumented)
+ snapshot(key: string): StorageValueSnapshot;
}
// @public
@@ -139,15 +96,6 @@ export type MockStorageBucket = {
[key: string]: any;
};
-// @public @deprecated (undocumented)
-export const msw: {
- setupDefaultHandlers: (worker: {
- listen: (t: any) => void;
- close: () => void;
- resetHandlers: () => void;
- }) => void;
-};
-
// @public
export function renderInTestApp(
Component: ComponentType | ReactNode,
diff --git a/packages/test-utils/src/testUtils/Keyboard.js b/packages/test-utils/src/testUtils/Keyboard.js
deleted file mode 100644
index 2a7928f8cd..0000000000
--- a/packages/test-utils/src/testUtils/Keyboard.js
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { act, fireEvent } from '@testing-library/react';
-
-const codes = {
- Tab: 9,
- Enter: 10,
- Click: 17 /* This keyboard can click, deal with it */,
- Esc: 27,
-};
-
-/**
- * @public
- * @deprecated superseded by {@link @testing-library/user-event#userEvent}
- */
-export class Keyboard {
- static async type(target, input) {
- await new Keyboard(target).type(input);
- }
-
- static async typeDebug(target, input) {
- await new Keyboard(target, { debug: true }).type(input);
- }
-
- static toReadableInput(chars) {
- return chars.split('').map(char => {
- switch (char.charCodeAt(0)) {
- case codes.Tab:
- return '';
- case codes.Enter:
- return '';
- case codes.Click:
- return '';
- case codes.Esc:
- return '';
- default:
- return char;
- }
- });
- }
-
- static fromReadableInput(input) {
- return input.trim().replace(/\s*<([a-zA-Z]+)>\s*/g, (match, name) => {
- if (name in codes) {
- return String.fromCharCode(codes[name]);
- }
- throw new Error(`Unknown char name: '${name}'`);
- });
- }
-
- constructor(target, { debug = false } = {}) {
- this.debug = debug;
-
- if (target.ownerDocument) {
- this.document = target.ownerDocument;
- } else if (target.baseElement) {
- this.document = target.baseElement.ownerDocument;
- } else {
- throw new TypeError(
- 'Keyboard(target): target must be DOM node or react-testing-library render() output',
- );
- }
- }
-
- toString() {
- return `Keyboard{document=${this.document}, debug=${this.debug}}`;
- }
-
- _log(message, ...args) {
- if (this.debug) {
- // eslint-disable-next-line no-console
- console.log(`[Keyboard] ${message}`, ...args);
- }
- }
-
- _pretty(element) {
- const attrs = [...element.attributes]
- .map(attr => `${attr.name}="${attr.value}"`)
- .join(' ');
- return `<${element.nodeName.toLocaleLowerCase('en-US')} ${attrs}>`;
- }
-
- get focused() {
- return this.document.activeElement;
- }
-
- async type(input) {
- this._log(
- `sending sequence '${input}' with initial focus ${this._pretty(
- this.focused,
- )}`,
- );
- await this.send(Keyboard.fromReadableInput(input));
- }
-
- async send(chars) {
- for (const key of chars.split('')) {
- const charCode = key.charCodeAt(0);
-
- if (charCode === codes.Tab) {
- await this.tab();
- continue;
- }
-
- const focused = this.focused;
- if (!focused || focused === this.document.body) {
- throw Error(
- `No element focused in document while trying to type '${Keyboard.toReadableInput(
- chars,
- )}'`,
- );
- }
- const nextValue = (focused.value || '') + key;
-
- if (charCode >= 32) {
- await this._sendKey(key, charCode, () => {
- this._log(
- `sending +${key} = '${nextValue}' to ${this._pretty(focused)}`,
- );
- fireEvent.change(focused, {
- target: { value: nextValue },
- bubbles: true,
- cancelable: true,
- });
- });
- } else if (charCode === codes.Enter) {
- await this.enter(focused.value || '');
- } else if (charCode === codes.Esc) {
- await this.escape();
- } else if (charCode === codes.Click) {
- await this.click();
- } else {
- throw new Error(`Unsupported char code, ${charCode}`);
- }
- }
- }
-
- async click() {
- this._log(`clicking ${this._pretty(this.focused)}`);
- await act(async () => fireEvent.click(this.focused));
- }
-
- async tab() {
- await this._sendKey('Tab', codes.Tab, () => {
- const focusable = this.document.querySelectorAll(
- [
- 'a[href]',
- 'area[href]',
- 'input:not([disabled])',
- 'select:not([disabled])',
- 'textarea:not([disabled])',
- 'button:not([disabled])',
- 'iframe',
- 'object',
- 'embed',
- '*[tabindex]',
- '*[contenteditable]',
- ].join(','),
- );
-
- const tabbable = [...focusable].filter(el => {
- return el.tabIndex >= 0;
- });
-
- const focused = this.document.activeElement;
- const focusedIndex = tabbable.indexOf(focused);
- const nextFocus = tabbable[focusedIndex + (1 % tabbable.length)];
-
- this._log(
- `tabbing to ${this._pretty(nextFocus)} ${this.focused.textContent}`,
- );
- nextFocus.focus();
- });
- }
-
- async enter(value) {
- this._log(`submitting '${value}' via ${this._pretty(this.focused)}`);
- await act(() =>
- this._sendKey('Enter', codes.Enter, () => {
- if (this.focused.type === 'button') {
- fireEvent.click(this.focused, { target: { value } });
- } else {
- fireEvent.submit(this.focused, {
- target: { value },
- bubbles: true,
- cancelable: true,
- });
- }
- }),
- );
- }
-
- async escape() {
- this._log(`escape from ${this._pretty(this.focused)}`);
- await act(async () => this._sendKey('Escape', codes.Esc));
- }
-
- async _sendKey(key, charCode, action) {
- const event = { key, charCode, keyCode: charCode, which: charCode };
- const focused = this.focused;
-
- if (fireEvent.keyDown(focused, event)) {
- if (fireEvent.keyPress(focused, event)) {
- if (action) {
- action();
- }
- }
- }
- fireEvent.keyUp(focused, event);
- }
-}
diff --git a/packages/test-utils/src/testUtils/Keyboard.test.js b/packages/test-utils/src/testUtils/Keyboard.test.js
deleted file mode 100644
index 41ee3a12d6..0000000000
--- a/packages/test-utils/src/testUtils/Keyboard.test.js
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2020 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import React from 'react';
-import { Keyboard } from './Keyboard';
-import { render } from '@testing-library/react';
-
-describe('testUtils.Keyboard', () => {
- it('types into some inputs with focus and submits a form', async () => {
- const typed1 = [];
- const typed2 = [];
- const typed3 = [];
-
- let submitted = false;
- const handleSubmit = event => {
- event.preventDefault();
- submitted = true;
- };
-
- const rendered = render(
- ,
- );
-
- const keyboard = new Keyboard(rendered);
- await keyboard.send('xy');
- await keyboard.tab();
- await keyboard.send('abc');
- await keyboard.enter();
-
- expect(typed1).toEqual([]);
- expect(typed2).toEqual(['x', 'xy']);
- expect(typed3).toEqual(['a', 'ab', 'abc']);
- expect(submitted).toBe(true);
- });
-
- it('can use Keyboard.type to send readable input', async () => {
- const typed1 = [];
- const typed2 = [];
- const typed3 = [];
-
- let submitted = false;
- const handleSubmit = event => {
- event.preventDefault();
- submitted = true;
- };
-
- const rendered = render(
- ,
- );
-
- await Keyboard.type(rendered, ' a b c ');
-
- expect(typed1).toEqual(['1a']);
- expect(typed2).toEqual(['2b']);
- expect(typed3).toEqual(['3c']);
- expect(submitted).toBe(true);
- });
-
- it('should be able to navigate a radio input with click', async () => {
- const selections = [];
-
- const rendered = render(
-
selections.push(value)}>
-
-
-
-
,
- );
-
- await Keyboard.type(rendered, '');
-
- expect(selections).toEqual(['a', 'c']);
- });
-});
diff --git a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.test.ts b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.test.ts
index a3028f8b57..a8187cd849 100644
--- a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.test.ts
+++ b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.test.ts
@@ -25,6 +25,12 @@ describe('WebStorage Storage API', () => {
const storage = createMockStorage();
expect(storage.get('myfakekey')).toBeUndefined();
+ expect(storage.snapshot('myfakekey')).toEqual({
+ key: 'myfakekey',
+ presence: 'absent',
+ value: undefined,
+ newValue: undefined,
+ });
});
it('should allow the setting and getting of the simple data structures', async () => {
@@ -36,6 +42,24 @@ describe('WebStorage Storage API', () => {
expect(storage.get('myfakekey')).toBe('helloimastring');
expect(storage.get('mysecondfakekey')).toBe(1234);
expect(storage.get('mythirdfakekey')).toBe(true);
+ expect(storage.snapshot('myfakekey')).toEqual({
+ key: 'myfakekey',
+ presence: 'present',
+ value: 'helloimastring',
+ newValue: 'helloimastring',
+ });
+ expect(storage.snapshot('mysecondfakekey')).toEqual({
+ key: 'mysecondfakekey',
+ presence: 'present',
+ value: 1234,
+ newValue: 1234,
+ });
+ expect(storage.snapshot('mythirdfakekey')).toEqual({
+ key: 'mythirdfakekey',
+ presence: 'present',
+ value: true,
+ newValue: true,
+ });
});
it('should allow setting of complex datastructures', async () => {
@@ -49,6 +73,12 @@ describe('WebStorage Storage API', () => {
await storage.set('myfakekey', mockData);
expect(storage.get('myfakekey')).toEqual(mockData);
+ expect(storage.snapshot('myfakekey')).toEqual({
+ key: 'myfakekey',
+ presence: 'present',
+ value: mockData,
+ newValue: mockData,
+ });
});
it('should subscribe to key changes when setting a new value', async () => {
@@ -59,7 +89,7 @@ describe('WebStorage Storage API', () => {
const mockData = { hello: 'im a great new value' };
await new Promise(resolve => {
- storage.observe$('correctKey').subscribe({
+ storage.observe$('correctKey').subscribe({
next: (...args) => {
selectedKeyNextHandler(...args);
resolve();
@@ -75,6 +105,8 @@ describe('WebStorage Storage API', () => {
expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1);
expect(selectedKeyNextHandler).toHaveBeenCalledWith({
key: 'correctKey',
+ presence: 'present',
+ value: mockData,
newValue: mockData,
});
});
@@ -105,6 +137,8 @@ describe('WebStorage Storage API', () => {
expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1);
expect(selectedKeyNextHandler).toHaveBeenCalledWith({
key: 'correctKey',
+ presence: 'absent',
+ value: undefined,
newValue: undefined,
});
});
@@ -122,6 +156,21 @@ describe('WebStorage Storage API', () => {
expect(firstStorage.get(keyName)).not.toBe(secondStorage.get(keyName));
expect(firstStorage.get(keyName)).toBe('boop');
expect(secondStorage.get(keyName)).toBe('deerp');
+ expect(firstStorage.snapshot(keyName)).not.toEqual(
+ secondStorage.snapshot(keyName),
+ );
+ expect(firstStorage.snapshot(keyName)).toEqual({
+ key: keyName,
+ presence: 'present',
+ value: 'boop',
+ newValue: 'boop',
+ });
+ expect(secondStorage.snapshot(keyName)).toEqual({
+ key: keyName,
+ presence: 'present',
+ value: 'deerp',
+ newValue: 'deerp',
+ });
});
it('should not clash with other namespaces when creating buckets', async () => {
@@ -138,6 +187,9 @@ describe('WebStorage Storage API', () => {
await firstStorage.set('test2', { error: true });
expect(secondStorage.get('deep/test2')).toBe(undefined);
+ expect(secondStorage.snapshot('deep/test2')).toMatchObject({
+ presence: 'absent',
+ });
});
it('should not reuse storage instances between different rootStorages', async () => {
@@ -151,5 +203,17 @@ describe('WebStorage Storage API', () => {
expect(firstStorage.get('test2')).toBe(true);
expect(secondStorage.get('test2')).toBe(undefined);
+ expect(firstStorage.snapshot('test2')).toEqual({
+ key: 'test2',
+ presence: 'present',
+ value: true,
+ newValue: true,
+ });
+ expect(secondStorage.snapshot('test2')).toEqual({
+ key: 'test2',
+ presence: 'absent',
+ value: undefined,
+ newValue: undefined,
+ });
});
});
diff --git a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts
index 6378a77474..f71d2602b5 100644
--- a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts
+++ b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import { StorageApi, StorageValueChange } from '@backstage/core-plugin-api';
-import { Observable } from '@backstage/types';
+import { StorageApi, StorageValueSnapshot } from '@backstage/core-plugin-api';
+import { JsonValue, Observable } from '@backstage/types';
import ObservableImpl from 'zen-observable';
/**
@@ -62,20 +62,48 @@ export class MockStorageApi implements StorageApi {
}
get(key: string): T | undefined {
- return this.data[this.getKeyName(key)];
+ return this.snapshot(key).value as T | undefined;
+ }
+
+ snapshot(key: string): StorageValueSnapshot {
+ if (this.data.hasOwnProperty(this.getKeyName(key))) {
+ const data = this.data[this.getKeyName(key)];
+ return {
+ key,
+ presence: 'present',
+ value: data,
+ newValue: data,
+ };
+ }
+ return {
+ key,
+ presence: 'absent',
+ value: undefined,
+ newValue: undefined,
+ };
}
async set(key: string, data: T): Promise {
this.data[this.getKeyName(key)] = data;
- this.notifyChanges({ key, newValue: data });
+ this.notifyChanges({
+ key,
+ presence: 'present',
+ value: data,
+ newValue: data,
+ });
}
async remove(key: string): Promise {
delete this.data[this.getKeyName(key)];
- this.notifyChanges({ key, newValue: undefined });
+ this.notifyChanges({
+ key,
+ presence: 'absent',
+ value: undefined,
+ newValue: undefined,
+ });
}
- observe$(key: string): Observable> {
+ observe$(key: string): Observable> {
return this.observable.filter(({ key: messageKey }) => messageKey === key);
}
@@ -83,22 +111,22 @@ export class MockStorageApi implements StorageApi {
return `${this.namespace}/${encodeURIComponent(key)}`;
}
- private notifyChanges(message: StorageValueChange) {
+ private notifyChanges(message: StorageValueSnapshot) {
for (const subscription of this.subscribers) {
subscription.next(message);
}
}
private subscribers = new Set<
- ZenObservable.SubscriptionObserver
+ ZenObservable.SubscriptionObserver>
>();
- private readonly observable = new ObservableImpl(
- subscriber => {
- this.subscribers.add(subscriber);
- return () => {
- this.subscribers.delete(subscriber);
- };
- },
- );
+ private readonly observable = new ObservableImpl<
+ StorageValueSnapshot
+ >(subscriber => {
+ this.subscribers.add(subscriber);
+ return () => {
+ this.subscribers.delete(subscriber);
+ };
+ });
}
diff --git a/packages/test-utils/src/testUtils/index.tsx b/packages/test-utils/src/testUtils/index.tsx
index c778c5c837..a7850fc158 100644
--- a/packages/test-utils/src/testUtils/index.tsx
+++ b/packages/test-utils/src/testUtils/index.tsx
@@ -19,7 +19,6 @@ export { default as mockBreakpoint } from './mockBreakpoint';
export { wrapInTestApp, renderInTestApp } from './appWrappers';
export type { TestAppOptions } from './appWrappers';
export * from './msw';
-export * from './Keyboard';
export * from './logCollector';
export * from './testingLibrary';
export { TestApiProvider, TestApiRegistry } from './TestApiProvider';
diff --git a/packages/test-utils/src/testUtils/logCollector.ts b/packages/test-utils/src/testUtils/logCollector.ts
index 25629f2c13..b9fa6220ed 100644
--- a/packages/test-utils/src/testUtils/logCollector.ts
+++ b/packages/test-utils/src/testUtils/logCollector.ts
@@ -73,7 +73,6 @@ export function withLogCollector(
/**
* Log collector that collect logs either from a sync or async collector.
* @public
- * @deprecated import from test-utils instead
* */
export function withLogCollector(
logsToCollect: LogFuncs[] | LogCollector,
diff --git a/packages/test-utils/src/testUtils/msw/index.ts b/packages/test-utils/src/testUtils/msw/index.ts
index 40bc47e8f0..625ef0e0fc 100644
--- a/packages/test-utils/src/testUtils/msw/index.ts
+++ b/packages/test-utils/src/testUtils/msw/index.ts
@@ -14,20 +14,6 @@
* limitations under the License.
*/
-/**
- * @deprecated use {@link setupRequestMockHandlers} instead which can be called directly with the worker.
- * @public
- */
-export const msw = {
- setupDefaultHandlers: (worker: {
- listen: (t: any) => void;
- close: () => void;
- resetHandlers: () => void;
- }) => {
- setupRequestMockHandlers(worker);
- },
-};
-
/**
* Sets up handlers for request mocking
* @public
diff --git a/plugins/allure/package.json b/plugins/allure/package.json
index 8b3ee3b882..261c5e1879 100644
--- a/plugins/allure/package.json
+++ b/plugins/allure/package.json
@@ -23,8 +23,8 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.7",
- "@backstage/core-components": "^0.8.0",
- "@backstage/core-plugin-api": "^0.3.0",
+ "@backstage/core-components": "^0.8.1",
+ "@backstage/core-plugin-api": "^0.3.1",
"@backstage/plugin-catalog-react": "^0.6.5",
"@backstage/theme": "^0.2.14",
"@material-ui/core": "^4.12.2",
@@ -37,8 +37,8 @@
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1",
- "@backstage/core-app-api": "^0.2.0",
+ "@backstage/cli": "^0.10.2",
+ "@backstage/core-app-api": "^0.2.1",
"@backstage/dev-utils": "^0.2.14",
"@backstage/test-utils": "^0.1.24",
"@testing-library/jest-dom": "^5.10.1",
diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json
index 15055b2a4f..5f48d4404e 100644
--- a/plugins/analytics-module-ga/package.json
+++ b/plugins/analytics-module-ga/package.json
@@ -22,8 +22,8 @@
},
"dependencies": {
"@backstage/config": "^0.1.5",
- "@backstage/core-components": "^0.8.0",
- "@backstage/core-plugin-api": "^0.3.0",
+ "@backstage/core-components": "^0.8.1",
+ "@backstage/core-plugin-api": "^0.3.1",
"@backstage/theme": "^0.2.14",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -35,8 +35,8 @@
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1",
- "@backstage/core-app-api": "^0.2.0",
+ "@backstage/cli": "^0.10.2",
+ "@backstage/core-app-api": "^0.2.1",
"@backstage/dev-utils": "^0.2.14",
"@backstage/test-utils": "^0.1.24",
"@testing-library/jest-dom": "^5.10.1",
diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md
new file mode 100644
index 0000000000..64abdb929a
--- /dev/null
+++ b/plugins/apache-airflow/CHANGELOG.md
@@ -0,0 +1,22 @@
+# @backstage/plugin-apache-airflow
+
+## 0.1.0
+
+### Minor Changes
+
+- 9aea335911: Introduces a new plugin for the Apache Airflow workflow management platform.
+ This implementation has been tested with the Apache Airflow v2 API,
+ authenticating with basic authentication through the Backstage proxy plugin.
+
+ Supported functionality includes:
+
+ - Information card of version information of the Airflow instance
+ - Information card of instance health for the meta-database and scheduler
+ - Table of DAGs with meta information and status, along with a link to view
+ details in the Airflow UI
+
+### Patch Changes
+
+- Updated dependencies
+ - @backstage/core-plugin-api@0.3.1
+ - @backstage/core-components@0.8.1
diff --git a/plugins/apache-airflow/README.md b/plugins/apache-airflow/README.md
index c57ab9da43..bdbb1b981f 100644
--- a/plugins/apache-airflow/README.md
+++ b/plugins/apache-airflow/README.md
@@ -2,6 +2,9 @@
Welcome to the apache-airflow plugin!
+This plugin serves as frontend to the REST API exposed by Apache Airflow.
+Note only [Airflow v2 (and later)](https://airflow.apache.org/docs/apache-airflow/stable/deprecated-rest-api-ref.html) integrate with the plugin.
+
## Feature Requests & Ideas
- [ ] Add support for running multiple instances of Airflow for monitoring
diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json
index a361961d12..58c5dceb77 100644
--- a/plugins/apache-airflow/package.json
+++ b/plugins/apache-airflow/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-apache-airflow",
- "version": "0.0.0",
+ "version": "0.1.0",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,8 +20,8 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/core-components": "^0.8.0",
- "@backstage/core-plugin-api": "^0.3.0",
+ "@backstage/core-components": "^0.8.1",
+ "@backstage/core-plugin-api": "^0.3.1",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
@@ -33,8 +33,8 @@
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1",
- "@backstage/core-app-api": "^0.2.0",
+ "@backstage/cli": "^0.10.2",
+ "@backstage/core-app-api": "^0.2.1",
"@backstage/dev-utils": "^0.2.14",
"@backstage/test-utils": "^0.1.24",
"@testing-library/jest-dom": "^5.10.1",
diff --git a/plugins/apache-airflow/src/api/ApacheAirflowApi.ts b/plugins/apache-airflow/src/api/ApacheAirflowApi.ts
index b0a0229df4..dde8c57802 100644
--- a/plugins/apache-airflow/src/api/ApacheAirflowApi.ts
+++ b/plugins/apache-airflow/src/api/ApacheAirflowApi.ts
@@ -19,7 +19,6 @@ import { Dag, InstanceStatus, InstanceVersion } from './types';
export const apacheAirflowApiRef = createApiRef({
id: 'plugin.apacheairflow.service',
- description: 'Used by the Apache Airflow plugin to make requests',
});
export type ApacheAirflowApi = {
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index 93df7e68aa..fd057cf358 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -32,8 +32,8 @@
"dependencies": {
"@asyncapi/react-component": "^1.0.0-next.25",
"@backstage/catalog-model": "^0.9.7",
- "@backstage/core-components": "^0.8.0",
- "@backstage/core-plugin-api": "^0.3.0",
+ "@backstage/core-components": "^0.8.1",
+ "@backstage/core-plugin-api": "^0.3.1",
"@backstage/plugin-catalog": "^0.7.4",
"@backstage/plugin-catalog-react": "^0.6.5",
"@backstage/theme": "^0.2.14",
@@ -54,8 +54,8 @@
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1",
- "@backstage/core-app-api": "^0.2.0",
+ "@backstage/cli": "^0.10.2",
+ "@backstage/core-app-api": "^0.2.1",
"@backstage/dev-utils": "^0.2.14",
"@backstage/test-utils": "^0.1.24",
"@testing-library/jest-dom": "^5.10.1",
diff --git a/plugins/api-docs/src/config.ts b/plugins/api-docs/src/config.ts
index e035bcfbd1..979340164b 100644
--- a/plugins/api-docs/src/config.ts
+++ b/plugins/api-docs/src/config.ts
@@ -20,7 +20,6 @@ import { createApiRef } from '@backstage/core-plugin-api';
export const apiDocsConfigRef = createApiRef({
id: 'plugin.api-docs.config',
- description: 'Used to configure api-docs widgets',
});
export interface ApiDocsConfig {
diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md
index dc45804449..cfdae9f4ef 100644
--- a/plugins/auth-backend/CHANGELOG.md
+++ b/plugins/auth-backend/CHANGELOG.md
@@ -1,5 +1,14 @@
# @backstage/plugin-auth-backend
+## 0.5.1
+
+### Patch Changes
+
+- 699c2e9ddc: export minimal typescript types for OIDC provider
+- Updated dependencies
+ - @backstage/backend-common@0.9.14
+ - @backstage/catalog-model@0.9.8
+
## 0.5.0
### Minor Changes
diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md
index a637118130..9ed523c678 100644
--- a/plugins/auth-backend/api-report.md
+++ b/plugins/auth-backend/api-report.md
@@ -47,6 +47,16 @@ export type AtlassianProviderOptions = {
};
};
+// @public
+export type AuthHandler = (
+ input: AuthResult,
+) => Promise;
+
+// @public
+export type AuthHandlerResult = {
+ profile: ProfileInfo;
+};
+
// Warning: (ae-missing-release-tag) "AuthProviderFactory" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -258,7 +268,6 @@ export const createOAuth2Provider: (
options?: OAuth2ProviderOptions | undefined,
) => AuthProviderFactory;
-// Warning: (ae-forgotten-export) The symbol "OidcProviderOptions" needs to be exported by the entry point index.d.ts
// Warning: (ae-missing-release-tag) "createOidcProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -534,6 +543,20 @@ export type OAuthState = {
origin?: string;
};
+// @public
+export type OidcAuthResult = {
+ tokenset: TokenSet;
+ userinfo: UserinfoResponse;
+};
+
+// @public
+export type OidcProviderOptions = {
+ authHandler?: AuthHandler;
+ signIn?: {
+ resolver?: SignInResolver;
+ };
+};
+
// Warning: (ae-missing-release-tag) "oktaEmailSignInResolver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -606,6 +629,22 @@ export type SamlProviderOptions = {
};
};
+// @public
+export type SignInInfo = {
+ profile: ProfileInfo;
+ result: AuthResult;
+};
+
+// @public
+export type SignInResolver = (
+ info: SignInInfo,
+ context: {
+ tokenIssuer: TokenIssuer;
+ catalogIdentityClient: CatalogIdentityClient;
+ logger: Logger_2;
+ },
+) => Promise;
+
// Warning: (ae-missing-release-tag) "TokenIssuer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public
@@ -637,8 +676,6 @@ export type WebMessageResponse =
// Warnings were encountered during analysis:
//
// src/identity/types.d.ts:31:9 - (ae-forgotten-export) The symbol "AnyJWK" needs to be exported by the entry point index.d.ts
-// src/providers/atlassian/provider.d.ts:37:5 - (ae-forgotten-export) The symbol "AuthHandler" needs to be exported by the entry point index.d.ts
-// src/providers/atlassian/provider.d.ts:42:9 - (ae-forgotten-export) The symbol "SignInResolver" needs to be exported by the entry point index.d.ts
// src/providers/aws-alb/provider.d.ts:77:5 - (ae-forgotten-export) The symbol "AwsAlbResult" needs to be exported by the entry point index.d.ts
// src/providers/github/provider.d.ts:71:58 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag
// src/providers/github/provider.d.ts:71:90 - (tsdoc-escape-greater-than) The ">" character should be escaped using a backslash to avoid confusion with an HTML tag
diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json
index b2822c4f33..c7d208eef2 100644
--- a/plugins/auth-backend/package.json
+++ b/plugins/auth-backend/package.json
@@ -1,7 +1,7 @@
{
"name": "@backstage/plugin-auth-backend",
"description": "A Backstage backend plugin that handles authentication",
- "version": "0.5.0",
+ "version": "0.5.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -30,9 +30,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.13",
+ "@backstage/backend-common": "^0.9.14",
"@backstage/catalog-client": "^0.5.2",
- "@backstage/catalog-model": "^0.9.7",
+ "@backstage/catalog-model": "^0.9.8",
"@backstage/config": "^0.1.11",
"@backstage/errors": "^0.1.5",
"@backstage/test-utils": "^0.1.24",
@@ -73,7 +73,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1",
+ "@backstage/cli": "^0.10.2",
"@types/body-parser": "^1.19.0",
"@types/cookie-parser": "^1.4.2",
"@types/express-session": "^1.17.2",
diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts
index 92c76b04b7..a3fc77bc02 100644
--- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts
+++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts
@@ -17,7 +17,7 @@
import express from 'express';
import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter';
import { encodeState } from './helpers';
-import { OAuthHandlers } from './types';
+import { OAuthHandlers, OAuthResponse } from './types';
const mockResponseData = {
providerInfo: {
@@ -36,6 +36,12 @@ const mockResponseData = {
},
};
+function mkTokenBody(payload: unknown): string {
+ return Buffer.from(JSON.stringify(payload), 'utf8')
+ .toString('base64')
+ .replace(/=/g, '');
+}
+
describe('OAuthAdapter', () => {
class MyAuthProvider implements OAuthHandlers {
async start() {
@@ -249,4 +255,86 @@ describe('OAuthAdapter', () => {
'Refresh token is not supported for provider test-provider',
);
});
+
+ it('correctly populates incomplete identities', async () => {
+ const mockRefresh = jest.fn, [express.Request]>();
+
+ const oauthProvider = new OAuthAdapter(
+ {
+ refresh: mockRefresh,
+ start: jest.fn(),
+ handler: jest.fn(),
+ } as OAuthHandlers,
+ {
+ ...oAuthProviderOptions,
+ tokenIssuer: {
+ issueToken: async ({ claims }) => `a.${mkTokenBody(claims)}.a`,
+ listPublicKeys: async () => ({ keys: [] }),
+ },
+ disableRefresh: false,
+ isOriginAllowed: () => false,
+ },
+ );
+
+ const mockRequest = {
+ header: () => 'XMLHttpRequest',
+ cookies: {
+ 'test-provider-refresh-token': 'token',
+ },
+ query: {},
+ } as unknown as express.Request;
+
+ const mockResponse = {
+ json: jest.fn().mockReturnThis(),
+ status: jest.fn().mockReturnThis(),
+ } as unknown as express.Response;
+
+ // Without a token
+ mockRefresh.mockResolvedValueOnce({
+ ...mockResponseData,
+ backstageIdentity: {
+ id: 'foo',
+ token: '',
+ },
+ });
+ await oauthProvider.refresh(mockRequest, mockResponse);
+ expect(mockResponse.json).toHaveBeenCalledTimes(1);
+ expect(mockResponse.json).toHaveBeenLastCalledWith({
+ ...mockResponseData,
+ backstageIdentity: {
+ id: 'foo',
+ token: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`,
+ idToken: `a.${mkTokenBody({ sub: 'user:default/foo' })}.a`,
+ identity: {
+ type: 'user',
+ userEntityRef: 'user:default/foo',
+ ownershipEntityRefs: [],
+ },
+ },
+ });
+
+ // With a token
+ mockRefresh.mockResolvedValueOnce({
+ ...mockResponseData,
+ backstageIdentity: {
+ id: 'foo',
+ token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`,
+ },
+ });
+ await oauthProvider.refresh(mockRequest, mockResponse);
+ expect(mockResponse.json).toHaveBeenCalledTimes(2);
+ expect(mockResponse.json).toHaveBeenLastCalledWith({
+ ...mockResponseData,
+ backstageIdentity: {
+ id: 'foo',
+ token: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`,
+ idToken: `z.${mkTokenBody({ sub: 'user:my-ns/foo' })}.z`,
+ identity: {
+ type: 'user',
+ userEntityRef: 'user:my-ns/foo',
+ ownershipEntityRefs: [],
+ },
+ },
+ });
+ });
});
diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts
index eb3f7efa42..4d5d507aa1 100644
--- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts
+++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts
@@ -17,6 +17,11 @@
import express from 'express';
import crypto from 'crypto';
import { URL } from 'url';
+import {
+ ENTITY_DEFAULT_NAMESPACE,
+ parseEntityRef,
+ stringifyEntityRef,
+} from '@backstage/catalog-model';
import {
AuthProviderRouteHandlers,
AuthProviderConfig,
@@ -243,8 +248,14 @@ export class OAuthAdapter implements AuthProviderRouteHandlers {
return prepareBackstageIdentityResponse(identity);
}
+ const userEntityRef = stringifyEntityRef(
+ parseEntityRef(identity.id, {
+ defaultKind: 'user',
+ defaultNamespace: ENTITY_DEFAULT_NAMESPACE,
+ }),
+ );
const token = await this.options.tokenIssuer.issueToken({
- claims: { sub: identity.id },
+ claims: { sub: userEntityRef },
});
return prepareBackstageIdentityResponse({ ...identity, token });
diff --git a/plugins/auth-backend/src/providers/index.ts b/plugins/auth-backend/src/providers/index.ts
index 4ecbb98fd2..9589e97265 100644
--- a/plugins/auth-backend/src/providers/index.ts
+++ b/plugins/auth-backend/src/providers/index.ts
@@ -34,6 +34,10 @@ export type {
AuthProviderRouteHandlers,
AuthProviderFactoryOptions,
AuthProviderFactory,
+ AuthHandler,
+ AuthHandlerResult,
+ SignInResolver,
+ SignInInfo,
} from './types';
// These types are needed for a postMessage from the login pop-up
diff --git a/plugins/auth-backend/src/providers/oidc/index.ts b/plugins/auth-backend/src/providers/oidc/index.ts
index 20d014a783..39bd02928d 100644
--- a/plugins/auth-backend/src/providers/oidc/index.ts
+++ b/plugins/auth-backend/src/providers/oidc/index.ts
@@ -13,5 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
+export type { OidcAuthResult, OidcProviderOptions } from './provider';
export { createOidcProvider } from './provider';
diff --git a/plugins/auth-backend/src/providers/oidc/provider.ts b/plugins/auth-backend/src/providers/oidc/provider.ts
index 158af6f830..fe4c042500 100644
--- a/plugins/auth-backend/src/providers/oidc/provider.ts
+++ b/plugins/auth-backend/src/providers/oidc/provider.ts
@@ -56,7 +56,11 @@ type OidcImpl = {
client: Client;
};
-type AuthResult = {
+/**
+ * authentication result for the OIDC which includes the token set and user information (a profile response sent by OIDC server)
+ * @public
+ */
+export type OidcAuthResult = {
tokenset: TokenSet;
userinfo: UserinfoResponse;
};
@@ -66,8 +70,8 @@ export type Options = OAuthProviderOptions & {
scope?: string;
prompt?: string;
tokenSignedResponseAlg?: string;
- signInResolver?: SignInResolver;
- authHandler: AuthHandler;
+ signInResolver?: SignInResolver;
+ authHandler: AuthHandler;
tokenIssuer: TokenIssuer;
catalogIdentityClient: CatalogIdentityClient;
logger: Logger;
@@ -78,8 +82,8 @@ export class OidcAuthProvider implements OAuthHandlers {
private readonly scope?: string;
private readonly prompt?: string;
- private readonly signInResolver?: SignInResolver;
- private readonly authHandler: AuthHandler;
+ private readonly signInResolver?: SignInResolver;
+ private readonly authHandler: AuthHandler;
private readonly tokenIssuer: TokenIssuer;
private readonly catalogIdentityClient: CatalogIdentityClient;
private readonly logger: Logger;
@@ -113,7 +117,7 @@ export class OidcAuthProvider implements OAuthHandlers {
): Promise<{ response: OAuthResponse; refreshToken?: string }> {
const { strategy } = await this.implementation;
const strategyResponse = await executeFrameHandlerStrategy<
- AuthResult,
+ OidcAuthResult,
PrivateInfo
>(req, strategy);
const {
@@ -158,7 +162,7 @@ export class OidcAuthProvider implements OAuthHandlers {
(
tokenset: TokenSet,
userinfo: UserinfoResponse,
- done: PassportDoneCallback,
+ done: PassportDoneCallback,
) => {
if (typeof done !== 'function') {
throw new Error(
@@ -180,7 +184,7 @@ export class OidcAuthProvider implements OAuthHandlers {
// Use this function to grab the user profile info from the token
// Then populate the profile with it
- private async handleResult(result: AuthResult): Promise {
+ private async handleResult(result: OidcAuthResult): Promise {
const { profile } = await this.authHandler(result);
const response: OAuthResponse = {
providerInfo: {
@@ -210,27 +214,37 @@ export class OidcAuthProvider implements OAuthHandlers {
}
}
-export const oAuth2DefaultSignInResolver: SignInResolver = async (
- info,
- ctx,
-) => {
- const { profile } = info;
+export const oAuth2DefaultSignInResolver: SignInResolver =
+ async (info, ctx) => {
+ const { profile } = info;
- if (!profile.email) {
- throw new Error('Profile contained no email');
- }
- const userId = profile.email.split('@')[0];
- const token = await ctx.tokenIssuer.issueToken({
- claims: { sub: userId, ent: [`user:default/${userId}`] },
- });
- return { id: userId, token };
-};
+ if (!profile.email) {
+ throw new Error('Profile contained no email');
+ }
+ const userId = profile.email.split('@')[0];
+ const token = await ctx.tokenIssuer.issueToken({
+ claims: { sub: userId, ent: [`user:default/${userId}`] },
+ });
+ return { id: userId, token };
+ };
+/**
+ * OIDC provider callback options. An auth handler and a sign in resolver
+ * can be passed while creating a OIDC provider.
+ *
+ * authHandler : called after sign in was successful, a new object must be returned which includes a profile
+ * signInResolver: called after sign in was successful, expects to return a new {@link BackstageSignInResult}
+ *
+ * Both options are optional. There is fallback for authHandler where the default handler expect an e-mail explicitly
+ * otherwise it throws an error
+ *
+ * @public
+ */
export type OidcProviderOptions = {
- authHandler?: AuthHandler;
+ authHandler?: AuthHandler;
signIn?: {
- resolver?: SignInResolver;
+ resolver?: SignInResolver;
};
};
@@ -260,7 +274,7 @@ export const createOidcProvider = (
tokenIssuer,
});
- const authHandler: AuthHandler = options?.authHandler
+ const authHandler: AuthHandler = options?.authHandler
? options.authHandler
: async ({ userinfo }) => ({
profile: {
@@ -271,7 +285,7 @@ export const createOidcProvider = (
});
const signInResolverFn =
options?.signIn?.resolver ?? oAuth2DefaultSignInResolver;
- const signInResolver: SignInResolver = info =>
+ const signInResolver: SignInResolver = info =>
signInResolverFn(info, {
catalogIdentityClient,
tokenIssuer,
diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts
index dafa52163c..ecb466c768 100644
--- a/plugins/auth-backend/src/providers/types.ts
+++ b/plugins/auth-backend/src/providers/types.ts
@@ -240,6 +240,10 @@ export type ProfileInfo = {
picture?: string;
};
+/**
+ * type of sign in information context, includes the profile information and authentication result which contains auth. related information
+ * @public
+ */
export type SignInInfo = {
/**
* The simple profile passed down for use in the frontend.
@@ -252,6 +256,11 @@ export type SignInInfo = {
result: AuthResult;
};
+/**
+ * Sign in resolver type describes the function which handles the result of a successful authentication
+ * and it must return a valid {@link BackstageSignInResult}
+ * @public
+ */
export type SignInResolver = (
info: SignInInfo,
context: {
@@ -261,6 +270,10 @@ export type SignInResolver = (
},
) => Promise;
+/**
+ * The return type of authentication handler which must contain a valid profile information
+ * @public
+ */
export type AuthHandlerResult = { profile: ProfileInfo };
/**
@@ -270,6 +283,8 @@ export type AuthHandlerResult = { profile: ProfileInfo };
*
* Throwing an error in the function will cause the authentication to fail, making it
* possible to use this function as a way to limit access to a certain group of users.
+ *
+ * @public
*/
export type AuthHandler = (
input: AuthResult,
diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md
index e0e74a9bc6..ff802a30e1 100644
--- a/plugins/azure-devops-backend/CHANGELOG.md
+++ b/plugins/azure-devops-backend/CHANGELOG.md
@@ -1,5 +1,66 @@
# @backstage/plugin-azure-devops-backend
+## 0.2.5
+
+### Patch Changes
+
+- daf32e2c9b: Created some initial filters that can be used to create pull request columns:
+
+ - All
+ - AssignedToUser
+ - AssignedToCurrentUser
+ - AssignedToTeam
+ - AssignedToTeams
+ - AssignedToCurrentUsersTeams
+ - CreatedByUser
+ - CreatedByCurrentUser
+ - CreatedByTeam
+ - CreatedByTeams
+ - CreatedByCurrentUsersTeams
+
+ Example custom column creation:
+
+ ```tsx
+ const COLUMN_CONFIGS: PullRequestColumnConfig[] = [
+ {
+ title: 'Created by me',
+ filters: [{ type: FilterType.CreatedByCurrentUser }],
+ },
+ {
+ title: 'Created by Backstage Core',
+ filters: [
+ {
+ type: FilterType.CreatedByTeam,
+ teamName: 'Backstage Core',
+ },
+ ],
+ },
+ {
+ title: 'Assigned to my teams',
+ filters: [{ type: FilterType.AssignedToCurrentUsersTeams }],
+ },
+ {
+ title: 'Other PRs',
+ filters: [{ type: FilterType.All }],
+ simplified: true,
+ },
+ ];
+
+
+ }
+ />;
+ ```
+
+- Updated dependencies
+ - @backstage/backend-common@0.9.14
+ - @backstage/plugin-azure-devops-common@0.1.3
+
## 0.2.4
### Patch Changes
diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json
index 9741380fb1..a7aec3f2d6 100644
--- a/plugins/azure-devops-backend/package.json
+++ b/plugins/azure-devops-backend/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops-backend",
- "version": "0.2.4",
+ "version": "0.2.5",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -20,9 +20,9 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/backend-common": "^0.9.13",
+ "@backstage/backend-common": "^0.9.14",
"@backstage/config": "^0.1.11",
- "@backstage/plugin-azure-devops-common": "^0.1.2",
+ "@backstage/plugin-azure-devops-common": "^0.1.3",
"@types/express": "^4.17.6",
"azure-devops-node-api": "^11.0.1",
"express": "^4.17.1",
@@ -31,7 +31,7 @@
"yn": "^4.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1",
+ "@backstage/cli": "^0.10.2",
"@types/supertest": "^2.0.8",
"supertest": "^4.0.2",
"msw": "^0.35.0"
diff --git a/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts b/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts
index aa843e9c17..7a0d0a279e 100644
--- a/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts
+++ b/plugins/azure-devops-backend/src/utils/azure-devops-utils.ts
@@ -215,6 +215,7 @@ function convertReviewer(
return {
id: identityRef.id,
displayName: identityRef.displayName,
+ uniqueName: identityRef.uniqueName,
imageUrl: getAvatarUrl(identityRef),
isRequired: identityRef.isRequired,
isContainer: identityRef.isContainer,
diff --git a/plugins/azure-devops-common/CHANGELOG.md b/plugins/azure-devops-common/CHANGELOG.md
index b2c8645e94..84c2cae9a5 100644
--- a/plugins/azure-devops-common/CHANGELOG.md
+++ b/plugins/azure-devops-common/CHANGELOG.md
@@ -1,5 +1,62 @@
# @backstage/plugin-azure-devops-common
+## 0.1.3
+
+### Patch Changes
+
+- daf32e2c9b: Created some initial filters that can be used to create pull request columns:
+
+ - All
+ - AssignedToUser
+ - AssignedToCurrentUser
+ - AssignedToTeam
+ - AssignedToTeams
+ - AssignedToCurrentUsersTeams
+ - CreatedByUser
+ - CreatedByCurrentUser
+ - CreatedByTeam
+ - CreatedByTeams
+ - CreatedByCurrentUsersTeams
+
+ Example custom column creation:
+
+ ```tsx
+ const COLUMN_CONFIGS: PullRequestColumnConfig[] = [
+ {
+ title: 'Created by me',
+ filters: [{ type: FilterType.CreatedByCurrentUser }],
+ },
+ {
+ title: 'Created by Backstage Core',
+ filters: [
+ {
+ type: FilterType.CreatedByTeam,
+ teamName: 'Backstage Core',
+ },
+ ],
+ },
+ {
+ title: 'Assigned to my teams',
+ filters: [{ type: FilterType.AssignedToCurrentUsersTeams }],
+ },
+ {
+ title: 'Other PRs',
+ filters: [{ type: FilterType.All }],
+ simplified: true,
+ },
+ ];
+
+
+ }
+ />;
+ ```
+
## 0.1.2
### Patch Changes
diff --git a/plugins/azure-devops-common/api-report.md b/plugins/azure-devops-common/api-report.md
index bb458f169a..85a9e54847 100644
--- a/plugins/azure-devops-common/api-report.md
+++ b/plugins/azure-devops-common/api-report.md
@@ -61,6 +61,10 @@ export interface CreatedBy {
// (undocumented)
imageUrl?: string;
// (undocumented)
+ teamIds?: string[];
+ // (undocumented)
+ teamNames?: string[];
+ // (undocumented)
uniqueName?: string;
}
@@ -254,6 +258,8 @@ export interface Reviewer {
// (undocumented)
isRequired?: boolean;
// (undocumented)
+ uniqueName?: string;
+ // (undocumented)
voteStatus: PullRequestVoteStatus;
}
diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json
index 7b82296c40..f8a9622bff 100644
--- a/plugins/azure-devops-common/package.json
+++ b/plugins/azure-devops-common/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops-common",
- "version": "0.1.2",
+ "version": "0.1.3",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -29,7 +29,7 @@
"clean": "backstage-cli clean"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1"
+ "@backstage/cli": "^0.10.2"
},
"files": [
"dist"
diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts
index e6bd174079..0befcf0b60 100644
--- a/plugins/azure-devops-common/src/types.ts
+++ b/plugins/azure-devops-common/src/types.ts
@@ -145,6 +145,7 @@ export interface DashboardPullRequest {
export interface Reviewer {
id?: string;
displayName?: string;
+ uniqueName?: string;
imageUrl?: string;
isRequired?: boolean;
isContainer?: boolean;
@@ -164,6 +165,8 @@ export interface CreatedBy {
displayName?: string;
uniqueName?: string;
imageUrl?: string;
+ teamIds?: string[];
+ teamNames?: string[];
}
export interface Repository {
diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md
index 57f80f508b..c79c3abdbc 100644
--- a/plugins/azure-devops/CHANGELOG.md
+++ b/plugins/azure-devops/CHANGELOG.md
@@ -1,5 +1,69 @@
# @backstage/plugin-azure-devops
+## 0.1.7
+
+### Patch Changes
+
+- daf32e2c9b: Created some initial filters that can be used to create pull request columns:
+
+ - All
+ - AssignedToUser
+ - AssignedToCurrentUser
+ - AssignedToTeam
+ - AssignedToTeams
+ - AssignedToCurrentUsersTeams
+ - CreatedByUser
+ - CreatedByCurrentUser
+ - CreatedByTeam
+ - CreatedByTeams
+ - CreatedByCurrentUsersTeams
+
+ Example custom column creation:
+
+ ```tsx
+ const COLUMN_CONFIGS: PullRequestColumnConfig[] = [
+ {
+ title: 'Created by me',
+ filters: [{ type: FilterType.CreatedByCurrentUser }],
+ },
+ {
+ title: 'Created by Backstage Core',
+ filters: [
+ {
+ type: FilterType.CreatedByTeam,
+ teamName: 'Backstage Core',
+ },
+ ],
+ },
+ {
+ title: 'Assigned to my teams',
+ filters: [{ type: FilterType.AssignedToCurrentUsersTeams }],
+ },
+ {
+ title: 'Other PRs',
+ filters: [{ type: FilterType.All }],
+ simplified: true,
+ },
+ ];
+
+
+ }
+ />;
+ ```
+
+- Updated dependencies
+ - @backstage/core-plugin-api@0.3.1
+ - @backstage/core-components@0.8.1
+ - @backstage/plugin-azure-devops-common@0.1.3
+ - @backstage/catalog-model@0.9.8
+ - @backstage/plugin-catalog-react@0.6.7
+
## 0.1.6
### Patch Changes
diff --git a/plugins/azure-devops/api-report.md b/plugins/azure-devops/api-report.md
index 0f3e3310ef..11e238e6f5 100644
--- a/plugins/azure-devops/api-report.md
+++ b/plugins/azure-devops/api-report.md
@@ -6,9 +6,55 @@
///
import { BackstagePlugin } from '@backstage/core-plugin-api';
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
import { Entity } from '@backstage/catalog-model';
import { SvgIconProps } from '@material-ui/core';
+// Warning: (ae-missing-release-tag) "AllFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type AllFilter = BaseFilter & {
+ type: FilterType.All;
+};
+
+// Warning: (ae-missing-release-tag) "AssignedToTeamFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type AssignedToTeamFilter = BaseFilter & {
+ type: FilterType.AssignedToTeam;
+ teamId: string;
+};
+
+// Warning: (ae-missing-release-tag) "AssignedToTeamsFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type AssignedToTeamsFilter = BaseFilter &
+ (
+ | {
+ type: FilterType.AssignedToTeams;
+ teamIds: string[];
+ }
+ | {
+ type: FilterType.AssignedToCurrentUsersTeams;
+ teamIds?: string[];
+ }
+ );
+
+// Warning: (ae-missing-release-tag) "AssignedToUserFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type AssignedToUserFilter = BaseFilter &
+ (
+ | {
+ type: FilterType.AssignedToUser;
+ email: string;
+ }
+ | {
+ type: FilterType.AssignedToCurrentUser;
+ email?: string;
+ }
+ );
+
// Warning: (ae-missing-release-tag) "azureDevOpsPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -25,11 +71,71 @@ export const AzurePullRequestsIcon: (props: SvgIconProps) => JSX.Element;
export const AzurePullRequestsPage: ({
projectName,
pollingInterval,
+ defaultColumnConfigs,
}: {
projectName?: string | undefined;
pollingInterval?: number | undefined;
+ defaultColumnConfigs?: PullRequestColumnConfig[] | undefined;
}) => JSX.Element;
+// Warning: (ae-missing-release-tag) "BaseFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type BaseFilter = {
+ type: FilterType;
+};
+
+// Warning: (ae-missing-release-tag) "CreatedByTeamFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type CreatedByTeamFilter = BaseFilter &
+ ({
+ type: FilterType.CreatedByTeam;
+ } & (
+ | {
+ teamId: string;
+ }
+ | {
+ teamName: string;
+ }
+ ));
+
+// Warning: (ae-missing-release-tag) "CreatedByTeamsFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type CreatedByTeamsFilter = BaseFilter &
+ (
+ | ({
+ type: FilterType.CreatedByTeams;
+ } & (
+ | {
+ teamIds: string[];
+ }
+ | {
+ teamNames: string[];
+ }
+ ))
+ | {
+ type: FilterType.CreatedByCurrentUsersTeams;
+ teamIds?: string[];
+ }
+ );
+
+// Warning: (ae-missing-release-tag) "CreatedByUserFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type CreatedByUserFilter = BaseFilter &
+ (
+ | {
+ type: FilterType.CreatedByUser;
+ email: string;
+ }
+ | {
+ type: FilterType.CreatedByCurrentUser;
+ email?: string;
+ }
+ );
+
// Warning: (ae-missing-release-tag) "EntityAzurePipelinesContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
@@ -48,10 +154,67 @@ export const EntityAzurePullRequestsContent: ({
defaultLimit?: number | undefined;
}) => JSX.Element;
+// Warning: (ae-missing-release-tag) "Filter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type Filter =
+ | AssignedToUserFilter
+ | CreatedByUserFilter
+ | AssignedToTeamFilter
+ | CreatedByTeamFilter
+ | AssignedToTeamsFilter
+ | CreatedByTeamsFilter
+ | AllFilter;
+
+// Warning: (ae-missing-release-tag) "FilterType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export enum FilterType {
+ // (undocumented)
+ All = 'All',
+ // (undocumented)
+ AssignedToCurrentUser = 'AssignedToCurrentUser',
+ // (undocumented)
+ AssignedToCurrentUsersTeams = 'AssignedToCurrentUsersTeams',
+ // (undocumented)
+ AssignedToTeam = 'AssignedToTeam',
+ // (undocumented)
+ AssignedToTeams = 'AssignedToTeams',
+ // (undocumented)
+ AssignedToUser = 'AssignedToUser',
+ // (undocumented)
+ CreatedByCurrentUser = 'CreatedByCurrentUser',
+ // (undocumented)
+ CreatedByCurrentUsersTeams = 'CreatedByCurrentUsersTeams',
+ // (undocumented)
+ CreatedByTeam = 'CreatedByTeam',
+ // (undocumented)
+ CreatedByTeams = 'CreatedByTeams',
+ // (undocumented)
+ CreatedByUser = 'CreatedByUser',
+}
+
// Warning: (ae-missing-release-tag) "isAzureDevOpsAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export const isAzureDevOpsAvailable: (entity: Entity) => boolean;
+// Warning: (ae-missing-release-tag) "PullRequestColumnConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export interface PullRequestColumnConfig {
+ // (undocumented)
+ filters: Filter[];
+ // (undocumented)
+ simplified?: boolean;
+ // (undocumented)
+ title: string;
+}
+
+// Warning: (ae-missing-release-tag) "PullRequestFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type PullRequestFilter = (pullRequest: DashboardPullRequest) => boolean;
+
// (No @packageDocumentation comment for this package)
```
diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json
index a37259739f..5d048c6471 100644
--- a/plugins/azure-devops/package.json
+++ b/plugins/azure-devops/package.json
@@ -1,6 +1,6 @@
{
"name": "@backstage/plugin-azure-devops",
- "version": "0.1.6",
+ "version": "0.1.7",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
@@ -27,12 +27,12 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/catalog-model": "^0.9.7",
- "@backstage/core-components": "^0.8.0",
- "@backstage/core-plugin-api": "^0.3.0",
+ "@backstage/catalog-model": "^0.9.8",
+ "@backstage/core-components": "^0.8.1",
+ "@backstage/core-plugin-api": "^0.3.1",
"@backstage/errors": "^0.1.4",
- "@backstage/plugin-azure-devops-common": "^0.1.2",
- "@backstage/plugin-catalog-react": "^0.6.5",
+ "@backstage/plugin-azure-devops-common": "^0.1.3",
+ "@backstage/plugin-catalog-react": "^0.6.7",
"@backstage/theme": "^0.2.14",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -46,8 +46,8 @@
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1",
- "@backstage/core-app-api": "^0.2.0",
+ "@backstage/cli": "^0.10.2",
+ "@backstage/core-app-api": "^0.2.1",
"@backstage/dev-utils": "^0.2.14",
"@backstage/test-utils": "^0.1.24",
"@testing-library/jest-dom": "^5.10.1",
diff --git a/plugins/azure-devops/src/api/AzureDevOpsApi.ts b/plugins/azure-devops/src/api/AzureDevOpsApi.ts
index 80d36f0adf..d6e926b9a5 100644
--- a/plugins/azure-devops/src/api/AzureDevOpsApi.ts
+++ b/plugins/azure-devops/src/api/AzureDevOpsApi.ts
@@ -27,8 +27,6 @@ import { createApiRef } from '@backstage/core-plugin-api';
export const azureDevOpsApiRef = createApiRef({
id: 'plugin.azure-devops.service',
- description:
- 'Used by the Azure DevOps plugin to make requests to accompanying backend',
});
export interface AzureDevOpsApi {
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx b/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx
index 9f21c4b81b..158c4bca5b 100644
--- a/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx
+++ b/plugins/azure-devops/src/components/PullRequestsPage/PullRequestsPage.tsx
@@ -21,56 +21,17 @@ import {
Progress,
ResponseErrorPanel,
} from '@backstage/core-components';
-import { PullRequestGroup, PullRequestGroupConfig } from './lib/types';
-import React, { useEffect, useState } from 'react';
-import { getCreatedByUserFilter, getPullRequestGroups } from './lib/utils';
-import { useDashboardPullRequests, useUserEmail } from '../../hooks';
+import { PullRequestColumnConfig, PullRequestGroup } from './lib/types';
+import React, { useState } from 'react';
+import { getPullRequestGroupConfigs, getPullRequestGroups } from './lib/utils';
-import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+import { FilterType } from './lib/filters';
import { PullRequestGrid } from './lib/PullRequestGrid';
-
-function usePullRequestGroupConfigs(
- userEmail: string | undefined,
-): PullRequestGroupConfig[] {
- const [pullRequestGroupConfigs, setPullRequestGroupConfigs] = useState<
- PullRequestGroupConfig[]
- >([]);
-
- useEffect(() => {
- const prGroupConfigs: PullRequestGroupConfig[] = [
- { title: 'Created by me', filter: getCreatedByUserFilter(userEmail) },
- { title: 'Other PRs', filter: _ => true, simplified: false },
- ];
-
- setPullRequestGroupConfigs(prGroupConfigs);
- }, [userEmail]);
-
- return pullRequestGroupConfigs;
-}
-
-function usePullRequestGroups(
- pullRequests: DashboardPullRequest[] | undefined,
- pullRequestGroupConfigs: PullRequestGroupConfig[],
-): PullRequestGroup[] {
- const [pullRequestGroups, setPullRequestGroups] = useState<
- PullRequestGroup[]
- >([]);
-
- useEffect(() => {
- if (pullRequests) {
- const groups = getPullRequestGroups(
- pullRequests,
- pullRequestGroupConfigs,
- );
- setPullRequestGroups(groups);
- }
- }, [pullRequests, pullRequestGroupConfigs]);
-
- return pullRequestGroups;
-}
+import { useDashboardPullRequests } from '../../hooks';
+import { useFilterProcessor } from './lib/hooks';
type PullRequestsPageContentProps = {
- pullRequestGroups: PullRequestGroup[];
+ pullRequestGroups: PullRequestGroup[] | undefined;
loading: boolean;
error?: Error;
};
@@ -80,7 +41,7 @@ const PullRequestsPageContent = ({
loading,
error,
}: PullRequestsPageContentProps) => {
- if (loading && pullRequestGroups.length <= 0) {
+ if (loading && (!pullRequestGroups || pullRequestGroups.length <= 0)) {
return ;
}
@@ -88,25 +49,50 @@ const PullRequestsPageContent = ({
return ;
}
- return ;
+ return ;
};
+const DEFAULT_COLUMN_CONFIGS: PullRequestColumnConfig[] = [
+ {
+ title: 'Created by me',
+ filters: [{ type: FilterType.CreatedByCurrentUser }],
+ simplified: false,
+ },
+ {
+ title: 'Other PRs',
+ filters: [{ type: FilterType.All }],
+ simplified: true,
+ },
+];
+
type PullRequestsPageProps = {
projectName?: string;
pollingInterval?: number;
+ defaultColumnConfigs?: PullRequestColumnConfig[];
};
export const PullRequestsPage = ({
projectName,
pollingInterval,
+ defaultColumnConfigs,
}: PullRequestsPageProps) => {
const { pullRequests, loading, error } = useDashboardPullRequests(
projectName,
pollingInterval,
);
- const userEmail = useUserEmail();
- const pullRequestGroupConfigs = usePullRequestGroupConfigs(userEmail);
- const pullRequestGroups = usePullRequestGroups(
+
+ const [columnConfigs] = useState(
+ defaultColumnConfigs ?? DEFAULT_COLUMN_CONFIGS,
+ );
+
+ const filterProcessor = useFilterProcessor();
+
+ const pullRequestGroupConfigs = getPullRequestGroupConfigs(
+ columnConfigs,
+ filterProcessor,
+ );
+
+ const pullRequestGroups = getPullRequestGroups(
pullRequests,
pullRequestGroupConfigs,
);
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/index.ts b/plugins/azure-devops/src/components/PullRequestsPage/index.ts
index e8b6cfa6bb..4061c72a8e 100644
--- a/plugins/azure-devops/src/components/PullRequestsPage/index.ts
+++ b/plugins/azure-devops/src/components/PullRequestsPage/index.ts
@@ -15,3 +15,17 @@
*/
export { PullRequestsPage } from './PullRequestsPage';
+export type { PullRequestColumnConfig } from './lib/types';
+export { FilterType } from './lib/filters';
+export type {
+ BaseFilter,
+ Filter,
+ PullRequestFilter,
+ AssignedToUserFilter,
+ CreatedByUserFilter,
+ AssignedToTeamFilter,
+ CreatedByTeamFilter,
+ AssignedToTeamsFilter,
+ CreatedByTeamsFilter,
+ AllFilter,
+} from './lib/filters';
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/allFilter.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/allFilter.ts
new file mode 100644
index 0000000000..90ec822ef6
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/allFilter.ts
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BaseFilter, FilterType, PullRequestFilter } from './types';
+
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+
+export type AllFilter = BaseFilter & {
+ type: FilterType.All;
+};
+
+export function createAllFilter(): PullRequestFilter {
+ return (_pullRequest: DashboardPullRequest): boolean => true;
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/assignedToTeamFilter.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/assignedToTeamFilter.ts
new file mode 100644
index 0000000000..8e816db2c3
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/assignedToTeamFilter.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BaseFilter, FilterType, PullRequestFilter } from './types';
+
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+import { stringArrayHas } from '../../../../utils';
+
+export type AssignedToTeamFilter = BaseFilter & {
+ type: FilterType.AssignedToTeam;
+ teamId: string;
+};
+
+export function createAssignedToTeamFilter(
+ filter: AssignedToTeamFilter,
+): PullRequestFilter {
+ return (pullRequest: DashboardPullRequest): boolean => {
+ const reviewerIds = pullRequest.reviewers?.map(reviewer => reviewer.id);
+
+ if (!reviewerIds) {
+ return false;
+ }
+
+ return stringArrayHas(reviewerIds, filter.teamId, true);
+ };
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/assignedToTeamsFilter.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/assignedToTeamsFilter.ts
new file mode 100644
index 0000000000..ce5e5bcc11
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/assignedToTeamsFilter.ts
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BaseFilter, FilterType, PullRequestFilter } from './types';
+
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+import { createAssignedToTeamFilter } from './assignedToTeamFilter';
+
+export type AssignedToTeamsFilter = BaseFilter &
+ (
+ | {
+ type: FilterType.AssignedToTeams;
+ teamIds: string[];
+ }
+ | {
+ type: FilterType.AssignedToCurrentUsersTeams;
+ teamIds?: string[];
+ }
+ );
+
+export function createAssignedToTeamsFilter(
+ filter: AssignedToTeamsFilter,
+): PullRequestFilter {
+ const teamIds = filter.teamIds;
+
+ return (pullRequest: DashboardPullRequest): boolean => {
+ if (!teamIds) {
+ return false;
+ }
+
+ return teamIds.some(teamId => {
+ return createAssignedToTeamFilter({
+ type: FilterType.AssignedToTeam,
+ teamId,
+ })(pullRequest);
+ });
+ };
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/assignedToUserFilter.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/assignedToUserFilter.ts
new file mode 100644
index 0000000000..95ee3d5a59
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/assignedToUserFilter.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BaseFilter, FilterType, PullRequestFilter } from './types';
+
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+import { stringArrayHas } from '../../../../utils';
+
+export type AssignedToUserFilter = BaseFilter &
+ (
+ | {
+ type: FilterType.AssignedToUser;
+ email: string;
+ }
+ | {
+ type: FilterType.AssignedToCurrentUser;
+ email?: string;
+ }
+ );
+
+export function createAssignedToUserFilter(
+ filter: AssignedToUserFilter,
+): PullRequestFilter {
+ const email = filter.email;
+
+ return (pullRequest: DashboardPullRequest): boolean => {
+ const uniqueNames = pullRequest.reviewers?.map(
+ reviewer => reviewer.uniqueName,
+ );
+
+ if (!email || !uniqueNames) {
+ return false;
+ }
+
+ return stringArrayHas(uniqueNames, email, true);
+ };
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createFilter.test.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createFilter.test.ts
new file mode 100644
index 0000000000..b0ac4d402b
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createFilter.test.ts
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Filter, FilterType } from './types';
+
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+import { createFilter } from './createFilter';
+
+describe('createFilter', () => {
+ const pullRequest = {
+ createdBy: {
+ uniqueName: 'user1@backstage.com',
+ teamIds: ['team1Id', 'team2Id'],
+ },
+ reviewers: [
+ { uniqueName: 'user2@backstage.com' },
+ { id: 'team2Id' },
+ { id: 'team3Id' },
+ ],
+ } as DashboardPullRequest;
+
+ const testCases: Array<{ filter: Filter; result: boolean }> = [
+ {
+ filter: {
+ type: FilterType.AssignedToUser,
+ email: 'user2@backstage.com',
+ },
+ result: true,
+ },
+ {
+ filter: {
+ type: FilterType.AssignedToUser,
+ email: 'random-user@backstage.com',
+ },
+ result: false,
+ },
+ {
+ filter: {
+ type: FilterType.CreatedByUser,
+ email: 'user1@backstage.com',
+ },
+ result: true,
+ },
+ {
+ filter: {
+ type: FilterType.CreatedByUser,
+ email: 'random-user@backstage.com',
+ },
+ result: false,
+ },
+ {
+ filter: {
+ type: FilterType.AssignedToTeam,
+ teamId: 'team2Id',
+ },
+ result: true,
+ },
+ {
+ filter: {
+ type: FilterType.AssignedToTeam,
+ teamId: 'randomTeamId',
+ },
+ result: false,
+ },
+ {
+ filter: {
+ type: FilterType.CreatedByTeam,
+ teamId: 'team1Id',
+ },
+ result: true,
+ },
+ {
+ filter: {
+ type: FilterType.CreatedByTeam,
+ teamId: 'randomTeamId',
+ },
+ result: false,
+ },
+ {
+ filter: {
+ type: FilterType.AssignedToTeams,
+ teamIds: ['team2Id', 'randomTeamId'],
+ },
+ result: true,
+ },
+ {
+ filter: {
+ type: FilterType.AssignedToTeams,
+ teamIds: ['team2Id', 'team3Id'],
+ },
+ result: true,
+ },
+ {
+ filter: {
+ type: FilterType.AssignedToTeams,
+ teamIds: ['randomTeam1Id', 'randomTeam2Id'],
+ },
+ result: false,
+ },
+ {
+ filter: {
+ type: FilterType.CreatedByTeams,
+ teamIds: ['team1Id', 'randomTeamId'],
+ },
+ result: true,
+ },
+ {
+ filter: {
+ type: FilterType.CreatedByTeams,
+ teamIds: ['team1Id', 'team2Id'],
+ },
+ result: true,
+ },
+ {
+ filter: {
+ type: FilterType.CreatedByTeams,
+ teamIds: ['randomTeam1Id', 'randomTeam2Id'],
+ },
+ result: false,
+ },
+ {
+ filter: {
+ type: FilterType.All,
+ },
+ result: true,
+ },
+ ];
+
+ testCases.forEach(({ filter, result }) => {
+ it(`should return ${String(result)} when pull request ${
+ result ? 'is' : 'is not'
+ } ${filter.type}`, () => {
+ const pullRequestFilter = createFilter(filter);
+ expect(pullRequestFilter(pullRequest)).toBe(result);
+ });
+ });
+});
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createFilter.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createFilter.ts
new file mode 100644
index 0000000000..492d0539c5
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createFilter.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Filter, FilterType, PullRequestFilter } from './types';
+
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+import { createAllFilter } from './allFilter';
+import { createAssignedToTeamFilter } from './assignedToTeamFilter';
+import { createAssignedToTeamsFilter } from './assignedToTeamsFilter';
+import { createAssignedToUserFilter } from './assignedToUserFilter';
+import { createCreatedByTeamFilter } from './createdByTeamFilter';
+import { createCreatedByTeamsFilter } from './createdByTeamsFilter';
+import { createCreatedByUserFilter } from './createdByUserFilter';
+
+export function createFilter(filters: Filter | Filter[]): PullRequestFilter {
+ const mapFilter = (filter: Filter): PullRequestFilter => {
+ switch (filter.type) {
+ case FilterType.AssignedToUser:
+ case FilterType.AssignedToCurrentUser:
+ return createAssignedToUserFilter(filter);
+
+ case FilterType.CreatedByUser:
+ case FilterType.CreatedByCurrentUser:
+ return createCreatedByUserFilter(filter);
+
+ case FilterType.AssignedToTeam:
+ return createAssignedToTeamFilter(filter);
+
+ case FilterType.CreatedByTeam:
+ return createCreatedByTeamFilter(filter);
+
+ case FilterType.AssignedToTeams:
+ case FilterType.AssignedToCurrentUsersTeams:
+ return createAssignedToTeamsFilter(filter);
+
+ case FilterType.CreatedByTeams:
+ case FilterType.CreatedByCurrentUsersTeams:
+ return createCreatedByTeamsFilter(filter);
+
+ case FilterType.All:
+ return createAllFilter();
+
+ default:
+ return _ => false;
+ }
+ };
+
+ if (Array.isArray(filters)) {
+ if (filters.length === 1) {
+ return mapFilter(filters[0]);
+ }
+
+ return (pullRequest: DashboardPullRequest): boolean =>
+ filters.every(filter => mapFilter(filter)(pullRequest));
+ }
+
+ return mapFilter(filters);
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createdByTeamFilter.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createdByTeamFilter.ts
new file mode 100644
index 0000000000..70a388b540
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createdByTeamFilter.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BaseFilter, FilterType, PullRequestFilter } from './types';
+
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+import { stringArrayHas } from '../../../../utils';
+
+export type CreatedByTeamFilter = BaseFilter &
+ ({
+ type: FilterType.CreatedByTeam;
+ } & ({ teamId: string } | { teamName: string }));
+
+export function createCreatedByTeamFilter(
+ filter: CreatedByTeamFilter,
+): PullRequestFilter {
+ return (pullRequest: DashboardPullRequest): boolean => {
+ const [createdByTeams, team] =
+ 'teamId' in filter
+ ? [pullRequest.createdBy?.teamIds, filter.teamId]
+ : [pullRequest.createdBy?.teamNames, filter.teamName];
+
+ if (!createdByTeams) {
+ return false;
+ }
+
+ return stringArrayHas(createdByTeams, team, true);
+ };
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createdByTeamsFilter.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createdByTeamsFilter.ts
new file mode 100644
index 0000000000..34ed81db5d
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createdByTeamsFilter.ts
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BaseFilter, FilterType, PullRequestFilter } from './types';
+
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+import { createCreatedByTeamFilter } from './createdByTeamFilter';
+
+export type CreatedByTeamsFilter = BaseFilter &
+ (
+ | ({
+ type: FilterType.CreatedByTeams;
+ } & ({ teamIds: string[] } | { teamNames: string[] }))
+ | {
+ type: FilterType.CreatedByCurrentUsersTeams;
+ teamIds?: string[];
+ }
+ );
+
+export function createCreatedByTeamsFilter(
+ filter: CreatedByTeamsFilter,
+): PullRequestFilter {
+ return (pullRequest: DashboardPullRequest): boolean => {
+ if ('teamNames' in filter) {
+ const teamNames = filter.teamNames;
+
+ if (!teamNames) {
+ return false;
+ }
+
+ return teamNames.some(teamName => {
+ return createCreatedByTeamFilter({
+ type: FilterType.CreatedByTeam,
+ teamName,
+ })(pullRequest);
+ });
+ }
+
+ const teamIds = filter.teamIds;
+
+ if (!teamIds) {
+ return false;
+ }
+
+ return teamIds.some(teamId => {
+ return createCreatedByTeamFilter({
+ type: FilterType.CreatedByTeam,
+ teamId,
+ })(pullRequest);
+ });
+ };
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createdByUserFilter.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createdByUserFilter.ts
new file mode 100644
index 0000000000..69d4f23db5
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/createdByUserFilter.ts
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { BaseFilter, FilterType, PullRequestFilter } from './types';
+
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+import { equalsIgnoreCase } from '../../../../utils';
+
+export type CreatedByUserFilter = BaseFilter &
+ (
+ | {
+ type: FilterType.CreatedByUser;
+ email: string;
+ }
+ | {
+ type: FilterType.CreatedByCurrentUser;
+ email?: string;
+ }
+ );
+
+export function createCreatedByUserFilter(
+ filter: CreatedByUserFilter,
+): PullRequestFilter {
+ const email = filter.email;
+
+ return (pullRequest: DashboardPullRequest): boolean => {
+ const uniqueName = pullRequest.createdBy?.uniqueName;
+
+ if (!email || !uniqueName) {
+ return false;
+ }
+
+ return equalsIgnoreCase(email, uniqueName);
+ };
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/index.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/index.ts
new file mode 100644
index 0000000000..df165dc9ca
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/index.ts
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { createFilter } from './createFilter';
+export { FilterTypes, FilterType } from './types';
+export type {
+ BaseFilter,
+ Filter,
+ PullRequestFilter,
+ AssignedToUserFilter,
+ CreatedByUserFilter,
+ AssignedToTeamFilter,
+ CreatedByTeamFilter,
+ AssignedToTeamsFilter,
+ CreatedByTeamsFilter,
+ AllFilter,
+} from './types';
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/types.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/types.ts
new file mode 100644
index 0000000000..bc4643d51b
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/filters/types.ts
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { AllFilter } from './allFilter';
+import { AssignedToTeamFilter } from './assignedToTeamFilter';
+import { AssignedToTeamsFilter } from './assignedToTeamsFilter';
+import { AssignedToUserFilter } from './assignedToUserFilter';
+import { CreatedByTeamFilter } from './createdByTeamFilter';
+import { CreatedByTeamsFilter } from './createdByTeamsFilter';
+import { CreatedByUserFilter } from './createdByUserFilter';
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+
+export enum FilterType {
+ All = 'All',
+
+ // Assigned To
+ AssignedToUser = 'AssignedToUser',
+ AssignedToCurrentUser = 'AssignedToCurrentUser',
+ AssignedToTeam = 'AssignedToTeam',
+ AssignedToTeams = 'AssignedToTeams',
+ AssignedToCurrentUsersTeams = 'AssignedToCurrentUsersTeams',
+
+ // Created By
+ CreatedByUser = 'CreatedByUser',
+ CreatedByCurrentUser = 'CreatedByCurrentUser',
+ CreatedByTeam = 'CreatedByTeam',
+ CreatedByTeams = 'CreatedByTeams',
+ CreatedByCurrentUsersTeams = 'CreatedByCurrentUsersTeams',
+}
+
+export const FilterTypes = [
+ FilterType.All,
+
+ FilterType.AssignedToUser,
+ FilterType.AssignedToCurrentUser,
+ FilterType.AssignedToTeam,
+ FilterType.AssignedToTeams,
+ FilterType.AssignedToCurrentUsersTeams,
+
+ FilterType.CreatedByUser,
+ FilterType.CreatedByCurrentUser,
+ FilterType.CreatedByTeam,
+ FilterType.CreatedByTeams,
+ FilterType.CreatedByCurrentUsersTeams,
+] as const;
+
+export type BaseFilter = {
+ type: FilterType;
+};
+
+export type Filter =
+ | AssignedToUserFilter
+ | CreatedByUserFilter
+ | AssignedToTeamFilter
+ | CreatedByTeamFilter
+ | AssignedToTeamsFilter
+ | CreatedByTeamsFilter
+ | AllFilter;
+
+export type {
+ AssignedToUserFilter,
+ CreatedByUserFilter,
+ AssignedToTeamFilter,
+ CreatedByTeamFilter,
+ AssignedToTeamsFilter,
+ CreatedByTeamsFilter,
+ AllFilter,
+};
+
+export type PullRequestFilter = (pullRequest: DashboardPullRequest) => boolean;
diff --git a/plugins/bazaar/src/components/AlertBanner/index.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/hooks/index.ts
similarity index 93%
rename from plugins/bazaar/src/components/AlertBanner/index.ts
rename to plugins/azure-devops/src/components/PullRequestsPage/lib/hooks/index.ts
index 1728cab9fc..2e87dfea0c 100644
--- a/plugins/bazaar/src/components/AlertBanner/index.ts
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/hooks/index.ts
@@ -14,4 +14,4 @@
* limitations under the License.
*/
-export { AlertBanner } from './AlertBanner';
+export * from './useFilterProcessor';
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/hooks/useFilterProcessor.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/hooks/useFilterProcessor.ts
new file mode 100644
index 0000000000..546b23430d
--- /dev/null
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/hooks/useFilterProcessor.ts
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Filter, FilterType } from '../filters';
+
+import { useUserEmail } from '../../../../hooks';
+
+export function useFilterProcessor(): (filters: Filter[]) => Filter[] {
+ const userEmail = useUserEmail();
+
+ return (filters: Filter[]): Filter[] => {
+ for (const filter of filters) {
+ switch (filter.type) {
+ case FilterType.AssignedToCurrentUser:
+ case FilterType.CreatedByCurrentUser:
+ filter.email = userEmail;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return filters;
+ };
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts
index 2d936f7b64..74c8ebe605 100644
--- a/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/types.ts
@@ -14,23 +14,24 @@
* limitations under the License.
*/
-import {
- DashboardPullRequest,
- Team,
-} from '@backstage/plugin-azure-devops-common';
+import { Filter, PullRequestFilter } from './filters';
-export interface PullRequestGroup {
+import { DashboardPullRequest } from '@backstage/plugin-azure-devops-common';
+
+export interface PullRequestColumnConfig {
title: string;
- pullRequests: DashboardPullRequest[];
+ filters: Filter[];
simplified?: boolean;
}
-export type PullRequestFilter = (pullRequest: DashboardPullRequest) => boolean;
-
-export type TeamFilter = (team: Team) => boolean;
-
export interface PullRequestGroupConfig {
title: string;
filter: PullRequestFilter;
simplified?: boolean;
}
+
+export interface PullRequestGroup {
+ title: string;
+ pullRequests: DashboardPullRequest[];
+ simplified?: boolean;
+}
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.test.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.test.ts
index da27b2e06e..d128cbd1db 100644
--- a/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.test.ts
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.test.ts
@@ -19,33 +19,7 @@ import {
PullRequestVoteStatus,
Reviewer,
} from '@backstage/plugin-azure-devops-common';
-import {
- arrayExtract,
- getCreatedByUserFilter,
- getPullRequestGroups,
- reviewerFilter,
-} from './utils';
-
-describe('getCreatedByUserFilter', () => {
- it('should filter if pull request is created by user', () => {
- const userEmail = 'user1@backstage.com';
- const pr = {
- createdBy: { uniqueName: userEmail },
- } as DashboardPullRequest;
- const result = getCreatedByUserFilter(userEmail)(pr);
- expect(result).toBe(true);
- });
-
- it('should not filter if pull request is not created by user', () => {
- const userEmail1 = 'user1@backstage.com';
- const userEmail2 = 'user2@backstage.com';
- const pr = {
- createdBy: { uniqueName: userEmail1 },
- } as DashboardPullRequest;
- const result = getCreatedByUserFilter(userEmail2)(pr);
- expect(result).toBe(false);
- });
-});
+import { arrayExtract, getPullRequestGroups, reviewerFilter } from './utils';
describe('reviewerFilter', () => {
it('should return false if reviewer has no vote and is not required', () => {
@@ -128,11 +102,15 @@ describe('getPullRequestGroups', () => {
];
const configs = [
- { title: 'Created by me', filter: getCreatedByUserFilter(userEmail) },
+ {
+ title: 'Created by me',
+ filter: (pullRequest: DashboardPullRequest): boolean =>
+ pullRequest.createdBy?.uniqueName === userEmail,
+ },
{ title: 'Other PRs', filter: (_: unknown) => true, simplified: true },
];
- const result = getPullRequestGroups(pullRequests, configs);
+ const result = getPullRequestGroups(pullRequests, configs) ?? [];
expect(result.length).toBe(2);
diff --git a/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.ts b/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.ts
index c9cf866010..c4fcdc9f7e 100644
--- a/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.ts
+++ b/plugins/azure-devops/src/components/PullRequestsPage/lib/utils.ts
@@ -19,25 +19,13 @@ import {
PullRequestVoteStatus,
Reviewer,
} from '@backstage/plugin-azure-devops-common';
+import { Filter, createFilter } from './filters';
import {
- PullRequestFilter,
+ PullRequestColumnConfig,
PullRequestGroup,
PullRequestGroupConfig,
} from './types';
-/**
- * Creates a filter that matches pull requests created by `userEmail`.
- * @param userEmail an email to filter by.
- * @returns a filter for pull requests created by `userEmail`.
- */
-export function getCreatedByUserFilter(
- userEmail: string | undefined,
-): PullRequestFilter {
- return (pullRequest: DashboardPullRequest): boolean =>
- pullRequest.createdBy?.uniqueName?.toLocaleLowerCase() ===
- userEmail?.toLocaleLowerCase();
-}
-
/**
* Filters a reviewer based on vote status and if the reviewer is required.
* @param reviewer a reviewer to filter.
@@ -97,9 +85,13 @@ export function arrayExtract(arr: T[], filter: (value: T) => unknown): T[] {
* @returns a list of pull request groups.
*/
export function getPullRequestGroups(
- pullRequests: DashboardPullRequest[],
+ pullRequests: DashboardPullRequest[] | undefined,
configs: PullRequestGroupConfig[],
-): PullRequestGroup[] {
+): PullRequestGroup[] | undefined {
+ if (!pullRequests) {
+ return undefined;
+ }
+
const remainingPullRequests: DashboardPullRequest[] = [...pullRequests];
const pullRequestGroups: PullRequestGroup[] = [];
@@ -115,3 +107,17 @@ export function getPullRequestGroups(
return pullRequestGroups;
}
+
+export function getPullRequestGroupConfigs(
+ columnConfigs: PullRequestColumnConfig[],
+ filterProcessor: (filters: Filter[]) => Filter[],
+): PullRequestGroupConfig[] {
+ return columnConfigs.map(columnConfig => {
+ const filters = filterProcessor(columnConfig.filters);
+ return {
+ title: columnConfig.title,
+ filter: createFilter(filters),
+ simplified: columnConfig.simplified,
+ };
+ });
+}
diff --git a/plugins/azure-devops/src/index.ts b/plugins/azure-devops/src/index.ts
index c9b804bc8d..8289a93a5c 100644
--- a/plugins/azure-devops/src/index.ts
+++ b/plugins/azure-devops/src/index.ts
@@ -23,3 +23,18 @@ export {
} from './plugin';
export { AzurePullRequestsIcon } from './components/AzurePullRequestsIcon';
+
+export { FilterType } from './components/PullRequestsPage';
+export type {
+ PullRequestColumnConfig,
+ BaseFilter,
+ Filter,
+ PullRequestFilter,
+ AssignedToUserFilter,
+ CreatedByUserFilter,
+ AssignedToTeamFilter,
+ CreatedByTeamFilter,
+ AssignedToTeamsFilter,
+ CreatedByTeamsFilter,
+ AllFilter,
+} from './components/PullRequestsPage';
diff --git a/plugins/azure-devops/src/utils/arrayHas.ts b/plugins/azure-devops/src/utils/arrayHas.ts
new file mode 100644
index 0000000000..8dac8b8ec9
--- /dev/null
+++ b/plugins/azure-devops/src/utils/arrayHas.ts
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export function arrayHas(arr: T[], value: T): boolean {
+ return new Set(arr).has(value);
+}
+
+export function stringArrayHas(
+ arr: Array,
+ value: string | undefined,
+ ignoreCase: boolean = false,
+): boolean {
+ if (ignoreCase) {
+ return arrayHas(
+ arr.map(a => a?.toLocaleLowerCase('en-US')),
+ value?.toLocaleLowerCase('en-US'),
+ );
+ }
+
+ return arrayHas(arr, value);
+}
diff --git a/plugins/azure-devops/src/utils/equalsIgnoreCase.ts b/plugins/azure-devops/src/utils/equalsIgnoreCase.ts
new file mode 100644
index 0000000000..12b058e6ae
--- /dev/null
+++ b/plugins/azure-devops/src/utils/equalsIgnoreCase.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export function equalsIgnoreCase(str1: string, str2: string): boolean {
+ return str1.toLocaleLowerCase('en-US') === str2.toLocaleLowerCase('en-US');
+}
diff --git a/plugins/azure-devops/src/utils/index.ts b/plugins/azure-devops/src/utils/index.ts
new file mode 100644
index 0000000000..f2214e8106
--- /dev/null
+++ b/plugins/azure-devops/src/utils/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export * from './arrayHas';
+export * from './equalsIgnoreCase';
+export * from './getDurationFromDates';
diff --git a/plugins/badges/package.json b/plugins/badges/package.json
index 441f72c550..9d35a3b9e2 100644
--- a/plugins/badges/package.json
+++ b/plugins/badges/package.json
@@ -28,8 +28,8 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.7",
- "@backstage/core-components": "^0.8.0",
- "@backstage/core-plugin-api": "^0.3.0",
+ "@backstage/core-components": "^0.8.1",
+ "@backstage/core-plugin-api": "^0.3.1",
"@backstage/errors": "^0.1.5",
"@backstage/plugin-catalog-react": "^0.6.5",
"@backstage/theme": "^0.2.14",
@@ -43,8 +43,8 @@
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1",
- "@backstage/core-app-api": "^0.2.0",
+ "@backstage/cli": "^0.10.2",
+ "@backstage/core-app-api": "^0.2.1",
"@backstage/dev-utils": "^0.2.14",
"@backstage/test-utils": "^0.1.24",
"@testing-library/jest-dom": "^5.10.1",
diff --git a/plugins/badges/src/api/types.ts b/plugins/badges/src/api/types.ts
index 499c144fca..884daa08ea 100644
--- a/plugins/badges/src/api/types.ts
+++ b/plugins/badges/src/api/types.ts
@@ -19,7 +19,6 @@ import { createApiRef } from '@backstage/core-plugin-api';
export const badgesApiRef = createApiRef({
id: 'plugin.badges.client',
- description: 'Used to make requests to the badges backend',
});
export type BadgeStyle =
diff --git a/plugins/bazaar-backend/migrations/20211117092217_optional_entity_ref.js b/plugins/bazaar-backend/migrations/20211117092217_optional_entity_ref.js
new file mode 100644
index 0000000000..03ed0a8d05
--- /dev/null
+++ b/plugins/bazaar-backend/migrations/20211117092217_optional_entity_ref.js
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+exports.up = async function up(knex) {
+ if (knex.client.config.client === 'sqlite3') {
+ await knex.schema.dropTable('metadata');
+ await knex.schema.createTable('metadata', table => {
+ table.increments('id').comment('Automatically generated unique ID');
+ table.text('entity_ref').unique().comment('The ref of the entity');
+ table.text('name').notNullable().comment('The name of the entity');
+ table
+ .text('community')
+ .comment('Link to where the community can discuss ideas');
+ table
+ .text('description')
+ .notNullable()
+ .comment('The description of the Bazaar project');
+ table
+ .text('status')
+ .defaultTo('proposed')
+ .notNullable()
+ .comment('The status of the Bazaar project');
+ table
+ .text('updated_at')
+ .notNullable()
+ .comment('Timestamp on ISO 8601 format when entity was last updated');
+ table
+ .text('size')
+ .defaultTo('medium')
+ .notNullable()
+ .comment('The estimated magnitude of the project');
+ table
+ .text('start_date')
+ .comment('Optional start date of the project (ISO 8601 format)');
+ table
+ .text('end_date')
+ .comment('Optional end date of the project (ISO 8601 format)');
+ table
+ .text('responsible')
+ .notNullable()
+ .comment('Contact person of the project');
+ });
+
+ await knex.schema.dropTable('members');
+ await knex.schema.createTable('members', table => {
+ table
+ .integer('item_id')
+ .references('metadata.id')
+ .onDelete('CASCADE')
+ .comment('Id of the associated item');
+ table
+ .text('entity_ref')
+ .references('metadata.entity_ref')
+ .onDelete('CASCADE')
+ .comment('The ref of the entity');
+ table.text('user_id').notNullable().comment('The user id of the member');
+ table
+ .dateTime('join_date')
+ .defaultTo(knex.fn.now())
+ .notNullable()
+ .comment('The timestamp when this member joined');
+ table.text('picture').comment('Link to profile picture');
+ });
+ } else {
+ await knex.schema.alterTable('metadata', table => {
+ table.renameColumn('announcement', 'description');
+ table.increments('id').comment('Automatically generated unique ID');
+ table.string('entity_ref').nullable().alter();
+ });
+
+ await knex.schema.alterTable('members', table => {
+ table
+ .integer('item_id')
+ .references('metadata.id')
+ .onDelete('CASCADE')
+ .comment('Id of the associated item');
+ table.dropColumn('entity_ref');
+ });
+ }
+};
+
+exports.down = async function down(knex) {
+ if (knex.client.config.client === 'sqlite3') {
+ await knex.schema.dropTable('metadata');
+ await knex.schema.createTable('metadata', table => {
+ table
+ .text('entity_ref')
+ .notNullable()
+ .unique()
+ .comment('The ref of the entity');
+ table.text('name').notNullable().comment('The name of the entity');
+ table
+ .text('community')
+ .comment('Link to where the community can discuss ideas');
+ table
+ .text('announcement')
+ .notNullable()
+ .comment('The announcement of the Bazaar project');
+ table
+ .text('status')
+ .defaultTo('proposed')
+ .notNullable()
+ .comment('The status of the Bazaar project');
+ table
+ .text('updated_at')
+ .notNullable()
+ .comment('Timestamp on ISO 8601 format when entity was last updated');
+ table
+ .text('size')
+ .defaultTo('medium')
+ .notNullable()
+ .comment('The estimated magnitude of the project');
+ table
+ .text('start_date')
+ .comment('Optional start date of the project (ISO 8601 format)');
+ table
+ .text('end_date')
+ .comment('Optional end date of the project (ISO 8601 format)');
+ table
+ .text('responsible')
+ .notNullable()
+ .comment('Contact person of the project');
+ });
+
+ await knex.schema.dropTable('members');
+ await knex.schema.createTable('members', table => {
+ table
+ .text('entity_ref')
+ .notNullable()
+ .references('metadata.entity_ref')
+ .onDelete('CASCADE')
+ .comment('The ref of the entity');
+ table.text('user_id').notNullable().comment('The user id of the member');
+ table
+ .dateTime('join_date')
+ .defaultTo(knex.fn.now())
+ .notNullable()
+ .comment('The timestamp when this member joined');
+ table.text('picture').comment('Link to profile picture');
+ });
+ } else {
+ await knex.schema.alterTable('metadata', table => {
+ table.renameColumn('description', 'announcement');
+ table.string('entity_ref').notNullable().alter();
+ table.dropColumn('id');
+ });
+
+ await knex.schema.alterTable('members', table => {
+ table.dropColumn('item_id');
+ table
+ .text('entity_ref')
+ .notNullable()
+ .references('metadata.entity_ref')
+ .onDelete('CASCADE')
+ .comment('The ref of the entity');
+ });
+ }
+};
diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts
index 3afde44d5e..3099e5bbb2 100644
--- a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts
+++ b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts
@@ -22,13 +22,14 @@ const bazaarProject: any = {
entityRef: 'ref1',
community: '',
status: 'proposed',
- announcement: 'a',
+ description: 'a',
membersCount: 0,
startDate: '2021-11-07T13:27:00.000Z',
endDate: null,
size: 'small',
responsible: 'r',
};
+
describe('DatabaseHandler', () => {
const databases = TestDatabases.create({
ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'],
@@ -50,7 +51,7 @@ describe('DatabaseHandler', () => {
await knex('metadata').insert({
entity_ref: bazaarProject.entityRef,
name: bazaarProject.name,
- announcement: bazaarProject.announcement,
+ description: bazaarProject.description,
community: bazaarProject.community,
status: bazaarProject.status,
updated_at: new Date().toISOString(),
@@ -60,10 +61,10 @@ describe('DatabaseHandler', () => {
responsible: bazaarProject.responsible,
});
- const res = await dbHandler.getMetadata('ref1');
+ const res = await dbHandler.getMetadataByRef('ref1');
expect(res).toHaveLength(1);
- expect(res[0].announcement).toEqual('a');
+ expect(res[0].description).toEqual('a');
expect(res[0].community).toEqual('');
expect(res[0].status).toEqual('proposed');
expect(res[0].start_date).toEqual('2021-11-07T13:27:00.000Z');
diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.ts
index 60f579af2d..7da06a54b8 100644
--- a/plugins/bazaar-backend/src/service/DatabaseHandler.ts
+++ b/plugins/bazaar-backend/src/service/DatabaseHandler.ts
@@ -44,10 +44,10 @@ export class DatabaseHandler {
}
private columns = [
- 'members.entity_ref',
+ 'metadata.id',
'metadata.entity_ref',
'metadata.name',
- 'metadata.announcement',
+ 'metadata.description',
'metadata.status',
'metadata.updated_at',
'metadata.community',
@@ -57,40 +57,52 @@ export class DatabaseHandler {
'metadata.responsible',
];
- async getMembers(entityRef: string) {
+ async getMembers(id: string) {
return await this.database
.select('*')
.from('members')
- .where({ entity_ref: entityRef });
+ .where({ item_id: id });
}
- async addMember(userId: string, entityRef: string, picture?: string) {
+ async addMember(id: number, userId: string, picture?: string) {
await this.database
.insert({
- entity_ref: entityRef,
+ item_id: id,
user_id: userId,
picture: picture,
})
.into('members');
}
- async deleteMember(userId: string, entityRef: string) {
+ async deleteMember(id: number, userId: string) {
return await this.database('members')
- .where({ entity_ref: decodeURIComponent(entityRef) })
+ .where({ item_id: id })
.andWhere('user_id', userId)
.del();
}
- async getMetadata(entityRef: string) {
+ async getMetadataById(id: number) {
const coalesce = this.database.raw(
- 'coalesce(count(members.entity_ref), 0) as members_count',
+ 'coalesce(count(members.item_id), 0) as members_count',
+ );
+
+ return await this.database('metadata')
+ .select([...this.columns, coalesce])
+ .where({ 'metadata.id': id })
+ .groupBy(this.columns)
+ .leftJoin('members', 'metadata.id', '=', 'members.item_id');
+ }
+
+ async getMetadataByRef(entityRef: string) {
+ const coalesce = this.database.raw(
+ 'coalesce(count(members.item_id), 0) as members_count',
);
return await this.database('metadata')
.select([...this.columns, coalesce])
.where({ 'metadata.entity_ref': entityRef })
.groupBy(this.columns)
- .leftJoin('members', 'metadata.entity_ref', '=', 'members.entity_ref');
+ .leftJoin('members', 'metadata.id', '=', 'members.item_id');
}
async insertMetadata(bazaarProject: any) {
@@ -98,7 +110,7 @@ export class DatabaseHandler {
name,
entityRef,
community,
- announcement,
+ description,
status,
size,
startDate,
@@ -108,11 +120,11 @@ export class DatabaseHandler {
await this.database
.insert({
- name: name,
+ name,
entity_ref: entityRef,
- community: community,
- announcement: announcement,
- status: status,
+ community,
+ description,
+ status,
updated_at: new Date().toISOString(),
size,
start_date: startDate,
@@ -124,9 +136,11 @@ export class DatabaseHandler {
async updateMetadata(bazaarProject: any) {
const {
+ name,
+ id,
entityRef,
community,
- announcement,
+ description,
status,
size,
startDate,
@@ -134,34 +148,32 @@ export class DatabaseHandler {
responsible,
} = bazaarProject;
- return await this.database('metadata')
- .where({ entity_ref: entityRef })
- .update({
- announcement: announcement,
- community: community,
- status: status,
- updated_at: new Date().toISOString(),
- size,
- start_date: startDate,
- end_date: endDate,
- responsible,
- });
+ return await this.database('metadata').where({ id: id }).update({
+ name,
+ entity_ref: entityRef,
+ description,
+ community,
+ status,
+ updated_at: new Date().toISOString(),
+ size,
+ start_date: startDate,
+ end_date: endDate,
+ responsible,
+ });
}
- async deleteMetadata(entityRef: string) {
- return await this.database('metadata')
- .where({ entity_ref: entityRef })
- .del();
+ async deleteMetadata(id: number) {
+ return await this.database('metadata').where({ id: id }).del();
}
- async getEntities() {
+ async getProjects() {
const coalesce = this.database.raw(
- 'coalesce(count(members.entity_ref), 0) as members_count',
+ 'coalesce(count(members.item_id), 0) as members_count',
);
return await this.database('metadata')
.select([...this.columns, coalesce])
.groupBy(this.columns)
- .leftJoin('members', 'metadata.entity_ref', '=', 'members.entity_ref');
+ .leftJoin('members', 'metadata.id', '=', 'members.item_id');
}
}
diff --git a/plugins/bazaar-backend/src/service/router.ts b/plugins/bazaar-backend/src/service/router.ts
index f0bc02c65b..730d958fcd 100644
--- a/plugins/bazaar-backend/src/service/router.ts
+++ b/plugins/bazaar-backend/src/service/router.ts
@@ -40,28 +40,27 @@ export async function createRouter(
const router = Router();
router.use(express.json());
- router.get('/members/:ref', async (request, response) => {
- const entity_ref = decodeURIComponent(request.params.ref);
- const data = await dbHandler.getMembers(entity_ref);
+ router.get('/projects/:id/members', async (request, response) => {
+ const members = await dbHandler.getMembers(request.params.id);
- if (data?.length) {
- response.json({ status: 'ok', data: data });
+ if (members?.length) {
+ response.json({ status: 'ok', data: members });
} else {
response.json({ status: 'ok', data: [] });
}
});
- router.put('/member', async (request, response) => {
- const { user_id, entity_ref, picture } = request.body;
- await dbHandler.addMember(user_id, entity_ref, picture);
+ router.put('/projects/:id/member/:userId', async (request, response) => {
+ const { id, userId } = request.params;
+ await dbHandler.addMember(parseInt(id, 10), userId, request.body?.picture);
response.json({ status: 'ok' });
});
- router.delete('/member/:ref/:id', async (request, response) => {
- const { ref, id } = request.params;
+ router.delete('/projects/:id/member/:userId', async (request, response) => {
+ const { id, userId } = request.params;
- const count = await dbHandler.deleteMember(id, ref);
+ const count = await dbHandler.deleteMember(parseInt(id, 10), userId);
if (count) {
response.json({ status: 'ok' });
@@ -70,36 +69,48 @@ export async function createRouter(
}
});
- router.get('/metadata/:ref', async (request, response) => {
+ router.get('/projects/id/:id', async (request, response) => {
+ const id = decodeURIComponent(request.params.id);
+
+ const data = await dbHandler.getMetadataById(parseInt(id, 10));
+
+ response.json({ status: 'ok', data: data });
+ });
+
+ router.get('/projects/ref/:ref', async (request, response) => {
const ref = decodeURIComponent(request.params.ref);
- const data = await dbHandler.getMetadata(ref);
+ const data = await dbHandler.getMetadataByRef(ref);
response.json({ status: 'ok', data: data });
});
- router.get('/entities', async (_, response) => {
- const data = await dbHandler.getEntities();
+ router.get('/projects', async (_, response) => {
+ const data = await dbHandler.getProjects();
response.json({ status: 'ok', data: data });
});
- router.put('/metadata', async (request, response) => {
+ router.put('/projects', async (request, response) => {
const bazaarProject = request.body;
const count = await dbHandler.updateMetadata(bazaarProject);
if (count) {
response.json({ status: 'ok' });
- } else {
- await dbHandler.insertMetadata(bazaarProject);
- response.json({ status: 'ok' });
}
});
- router.delete('/metadata/:ref', async (request, response) => {
- const ref = decodeURIComponent(request.params.ref);
+ router.post('/projects', async (request, response) => {
+ const bazaarProject = request.body;
- const count = await dbHandler.deleteMetadata(ref);
+ await dbHandler.insertMetadata(bazaarProject);
+ response.json({ status: 'ok' });
+ });
+
+ router.delete('/projects/:id', async (request, response) => {
+ const id = decodeURIComponent(request.params.id);
+
+ const count = await dbHandler.deleteMetadata(parseInt(id, 10));
if (count) {
response.json({ status: 'ok' });
diff --git a/plugins/bazaar/README.md b/plugins/bazaar/README.md
index f07e751984..dea25727b0 100644
--- a/plugins/bazaar/README.md
+++ b/plugins/bazaar/README.md
@@ -69,61 +69,42 @@ const overviewContent = (
### Layout
-The latest modified Bazaar projects are displayed in the Bazaar landing page, located at the Bazaar icon in the sidebar. Each project is represented as a card containing its most relevant data to give an overview of the project. The list of project is paginated.
+The latest modified Bazaar projects are displayed in the Bazaar landing page, located at the Bazaar icon in the sidebar. Each project is represented as a card containing its most relevant data to give an overview of the project. It is also possible to sort in alphabetical order or on the number of members. Here you can also search or add project to the Bazaar.
-
+
### Workflow
-To add a project to the Bazaar, you need to create a project with one of the templates in Backstage. Click the add project-button, choose the project and fill in the form.
+To add a project to the bazaar, simply click on the `add-project` button and fill in the form.
The following fields are mandatory:
-- announcement - present your idea and what skills you are looking for
+- name - name of the project on URL safe format
+- description - present your idea and what skills you are looking for
- status - whether or not the project has started
- size - small, medium or large
- responsible - main contact person of the project
The other fields are:
+- project - link Bazaar project to existing entity in the catalog
+- community link - link to where the project members can communicate, e.g. Teams or Discord link
- start date
- end date
-- community link - link where the project members can chat, e.g. Teams or Discord link
-When the project is added, you will see the Bazaar information in the Bazaar card on the entity page. There you can join a project, edit or delete it.
+When clicking on a Bazaar project a card containing the Bazaar information will show up. If the Bazaar project is linked to an entity, the card is also visible on that entity's EntityPage. From that card it is possible to either link or unlink an entity to a project, edit or delete the project and join the project if it seems interesting to you. Once you have joined a project, you will get access to the community link if it exists.
-### Database
-
-The metadata related to the Bazaar is stored in a database. Right now there are two tables, one for storing the metadata and one for storing the members of a Bazaar project.
-
-**metadata**:
-
-- name - name of the entity
-- entity_ref - namespace/kind/name of the entity
-- announcement - announcement of the project and its current need of skills/team member
-- status - status of the project, 'proposed' or 'ongoing'
-- updated_at - date when the Bazaar information was last modified (ISO 8601 format)
-- size - small, medium or large
-- start_date - date when the project is estimated to start (ISO 8601 format)
-- end_date - date when the project is estimated to end (ISO 8601 format)
-- responsible - main contact person of the project
-
-**members**:
-
-- entity_ref - namespace/kind/name of the entity
-- user_name
-- join_date - date when the user joined the project (ISO 8601 format)
+
## Future work and ideas
- Workflow
- - Make it possible to add a Bazaar project without linking it to a Backstage entity, this would make it easier to just add an idea to the Bazaar.
+ - Make it possible for multiple Bazaar project to link to the same catalog entity
- Bazaar landing page
- Add a tab 'My page', where your personal data is displayed. For example: your projects and its latest activities etc.
- - Make it possible to sort the project based on e.g. the number of members
- Bazaar tab on the EntityPage
@@ -133,6 +114,3 @@ The metadata related to the Bazaar is stored in a database. Right now there are
- Dialogues
- Extend the dialogue for adding a project with more fields, e.g. the possibility to add images
-
-- Testing
- - Add tests to all the components
diff --git a/plugins/bazaar/media/bazaar_pr_fullscreen.png b/plugins/bazaar/media/bazaar_pr_fullscreen.png
deleted file mode 100644
index 6c5cf1277b..0000000000
Binary files a/plugins/bazaar/media/bazaar_pr_fullscreen.png and /dev/null differ
diff --git a/plugins/bazaar/media/demo.gif b/plugins/bazaar/media/demo.gif
new file mode 100644
index 0000000000..6369ce7551
Binary files /dev/null and b/plugins/bazaar/media/demo.gif differ
diff --git a/plugins/bazaar/media/layout.png b/plugins/bazaar/media/layout.png
new file mode 100644
index 0000000000..cc02a374a3
Binary files /dev/null and b/plugins/bazaar/media/layout.png differ
diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json
index 3db79b9cdf..6ccda73f93 100644
--- a/plugins/bazaar/package.json
+++ b/plugins/bazaar/package.json
@@ -23,8 +23,8 @@
"dependencies": {
"@backstage/catalog-model": "^0.9.7",
"@backstage/cli": "^0.10.1",
- "@backstage/core-components": "^0.8.0",
- "@backstage/core-plugin-api": "^0.3.0",
+ "@backstage/core-components": "^0.8.1",
+ "@backstage/core-plugin-api": "^0.3.1",
"@backstage/plugin-catalog": "^0.7.4",
"@backstage/plugin-catalog-react": "^0.6.5",
"@date-io/luxon": "1.x",
@@ -34,6 +34,7 @@
"@material-ui/pickers": "^3.3.10",
"@testing-library/jest-dom": "^5.10.1",
"luxon": "^2.0.2",
+ "material-ui-search-bar": "^1.0.0",
"react-hook-form": "^7.13.0",
"react-router-dom": "6.0.0-beta.0",
"react-use": "^17.2.4"
@@ -42,7 +43,7 @@
"react": "^16.13.1 || ^17.0.0"
},
"devDependencies": {
- "@backstage/cli": "^0.10.1",
+ "@backstage/cli": "^0.10.2",
"@backstage/dev-utils": "^0.2.14",
"@testing-library/jest-dom": "^5.10.1",
"cross-fetch": "^3.0.6"
diff --git a/plugins/bazaar/plugin-bazaar.api.md b/plugins/bazaar/plugin-bazaar.api.md
new file mode 100644
index 0000000000..33a674ffc4
--- /dev/null
+++ b/plugins/bazaar/plugin-bazaar.api.md
@@ -0,0 +1,25 @@
+## API Report File for "@backstage/plugin-bazaar"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+///
+
+import * as _backstage_core_plugin_api from '@backstage/core-plugin-api';
+
+// @public (undocumented)
+export const BazaarPage: () => JSX.Element;
+
+// @public (undocumented)
+export const bazaarPlugin: _backstage_core_plugin_api.BackstagePlugin<
+ {
+ root: _backstage_core_plugin_api.RouteRef;
+ },
+ {}
+>;
+
+// @public (undocumented)
+export const EntityBazaarInfoCard: () => JSX.Element;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/plugins/bazaar/src/api.ts b/plugins/bazaar/src/api.ts
index db20e5e0cb..9f5010ec43 100644
--- a/plugins/bazaar/src/api.ts
+++ b/plugins/bazaar/src/api.ts
@@ -14,33 +14,34 @@
* limitations under the License.
*/
-import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import {
createApiRef,
DiscoveryApi,
IdentityApi,
} from '@backstage/core-plugin-api';
-import { BazaarProject } from './types';
export const bazaarApiRef = createApiRef({
id: 'bazaar',
- description: 'Used to make requests towards the bazaar backend',
});
export interface BazaarApi {
- updateMetadata(bazaarProject: BazaarProject): Promise;
+ updateProject(bazaarProject: any): Promise;
- getMetadata(entity: Entity): Promise;
+ addProject(bazaarProject: any): Promise;
- getMembers(entity: Entity): Promise;
+ getProjectById(id: number): Promise;
- deleteMember(entity: Entity): Promise;
+ getProjectByRef(entityRef: string): Promise;
- addMember(entity: Entity): Promise;
+ getMembers(id: number): Promise;
- getEntities(): Promise;
+ deleteMember(id: number, userId: string): Promise;
- deleteEntity(bazaarProject: BazaarProject): Promise;
+ addMember(id: number, userId: string): Promise;
+
+ getProjects(): Promise;
+
+ deleteProject(id: number): Promise;
}
export class BazaarClient implements BazaarApi {
@@ -55,10 +56,10 @@ export class BazaarClient implements BazaarApi {
this.discoveryApi = options.discoveryApi;
}
- async updateMetadata(bazaarProject: BazaarProject): Promise {
+ async updateProject(bazaarProject: any): Promise {
const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
- return await fetch(`${baseUrl}/metadata`, {
+ return await fetch(`${baseUrl}/projects`, {
method: 'PUT',
headers: {
Accept: 'application/json',
@@ -68,11 +69,34 @@ export class BazaarClient implements BazaarApi {
}).then(resp => resp.json());
}
- async getMetadata(entity: Entity): Promise {
+ async addProject(bazaarProject: any): Promise {
+ const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
+
+ return await fetch(`${baseUrl}/projects`, {
+ method: 'POST',
+ headers: {
+ Accept: 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(bazaarProject),
+ }).then(resp => resp.json());
+ }
+
+ async getProjectById(id: number): Promise {
+ const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
+
+ const response = await fetch(`${baseUrl}/projects/id/${id}`, {
+ method: 'GET',
+ });
+
+ return response.ok ? response : null;
+ }
+
+ async getProjectByRef(entityRef: string): Promise {
const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
const response = await fetch(
- `${baseUrl}/metadata/${encodeURIComponent(stringifyEntityRef(entity))}`,
+ `${baseUrl}/projects/ref/${encodeURIComponent(entityRef)}`,
{
method: 'GET',
},
@@ -81,60 +105,49 @@ export class BazaarClient implements BazaarApi {
return response.ok ? response : null;
}
- async getMembers(entity: Entity): Promise {
+ async getMembers(id: number): Promise {
const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
- return await fetch(
- `${baseUrl}/members/${encodeURIComponent(stringifyEntityRef(entity))}`,
- {
- method: 'GET',
- },
- ).then(resp => resp.json());
+ return await fetch(`${baseUrl}/projects/${id}/members`, {
+ method: 'GET',
+ }).then(resp => resp.json());
}
- async addMember(entity: Entity): Promise {
+ async addMember(id: number, userId: string): Promise {
const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
- await fetch(`${baseUrl}/member`, {
+ await fetch(`${baseUrl}/projects/${id}/member/${userId}`, {
method: 'PUT',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
- entity_ref: stringifyEntityRef(entity),
- user_id: this.identityApi.getUserId(),
picture: this.identityApi.getProfile()?.picture,
}),
});
}
- async deleteMember(entity: Entity): Promise {
+ async deleteMember(id: number, userId: string): Promise {
const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
- await fetch(
- `${baseUrl}/member/${encodeURIComponent(
- stringifyEntityRef(entity),
- )}/${this.identityApi.getUserId()}`,
- {
- method: 'DELETE',
- },
- );
+ await fetch(`${baseUrl}/projects/${id}/member/${userId}`, {
+ method: 'DELETE',
+ });
}
- async getEntities(): Promise {
+ async getProjects(): Promise {
const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
- return await fetch(`${baseUrl}/entities`, {
+ return await fetch(`${baseUrl}/projects`, {
method: 'GET',
}).then(resp => resp.json());
}
- async deleteEntity(bazaarProject: BazaarProject): Promise {
+ async deleteProject(id: number): Promise {
const baseUrl = await this.discoveryApi.getBaseUrl('bazaar');
- const entityRef = bazaarProject.entityRef as string;
- await fetch(`${baseUrl}/metadata/${encodeURIComponent(entityRef)}`, {
+ await fetch(`${baseUrl}/projects/${id}`, {
method: 'DELETE',
});
}
diff --git a/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx b/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx
index 310ffe051d..4333ff11fd 100644
--- a/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx
+++ b/plugins/bazaar/src/components/AddProjectDialog/AddProjectDialog.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { useState, useEffect } from 'react';
+import React, { useState } from 'react';
import { Entity, stringifyEntityRef } from '@backstage/catalog-model';
import { UseFormReset, UseFormGetValues } from 'react-hook-form';
import { useApi } from '@backstage/core-plugin-api';
@@ -39,18 +39,13 @@ export const AddProjectDialog = ({
fetchCatalogEntities,
}: Props) => {
const bazaarApi = useApi(bazaarApiRef);
- const [selectedEntity, setSelectedEntity] = useState(
- catalogEntities ? catalogEntities[0] : null,
- );
-
- useEffect(() => {
- setSelectedEntity(catalogEntities ? catalogEntities[0] : null);
- }, [catalogEntities]);
+ const [selectedEntity, setSelectedEntity] = useState(null);
const defaultValues = {
+ name: '',
title: 'Add project',
community: '',
- announcement: '',
+ description: '',
status: 'proposed' as Status,
size: 'medium' as Size,
responsible: '',
@@ -58,59 +53,48 @@ export const AddProjectDialog = ({
endDate: null,
};
- const handleListItemClick = (entity: Entity) => {
+ const handleEntityClick = (entity: Entity) => {
setSelectedEntity(entity);
};
- const handleCloseDialog = () => {
- setSelectedEntity(catalogEntities ? catalogEntities[0] : null);
- handleClose();
- };
-
- const handleSave: any = async (
+ const handleSubmit: any = async (
getValues: UseFormGetValues,
reset: UseFormReset,
) => {
const formValues = getValues();
+ const response = await bazaarApi.addProject({
+ ...formValues,
+ entityRef: selectedEntity ? stringifyEntityRef(selectedEntity) : null,
+ startDate: formValues.startDate ?? null,
+ endDate: formValues.endDate ?? null,
+ } as BazaarProject);
- if (selectedEntity) {
- await bazaarApi.updateMetadata({
- name: selectedEntity.metadata.name,
- entityRef: stringifyEntityRef(selectedEntity),
- announcement: formValues.announcement,
- status: formValues.status,
- community: formValues.community,
- membersCount: 0,
- size: formValues.size,
- startDate: formValues.startDate ?? null,
- endDate: formValues.endDate ?? null,
- responsible: formValues.responsible,
- } as BazaarProject);
-
+ if (response.status === 'ok') {
fetchBazaarProjects();
fetchCatalogEntities();
-
- handleClose();
- reset(defaultValues);
}
+
+ handleClose();
+ reset(defaultValues);
};
return (
}
- handleClose={handleCloseDialog}
+ handleClose={handleClose}
/>
);
};
diff --git a/plugins/bazaar/src/components/AlertBanner/AlertBanner.tsx b/plugins/bazaar/src/components/AlertBanner/AlertBanner.tsx
deleted file mode 100644
index 1b6c1331c3..0000000000
--- a/plugins/bazaar/src/components/AlertBanner/AlertBanner.tsx
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2021 The Backstage Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import React from 'react';
-import { Snackbar, IconButton } from '@material-ui/core';
-import CloseIcon from '@material-ui/icons/Close';
-import { Alert } from '@material-ui/lab';
-
-type Props = {
- open: boolean;
- message: JSX.Element;
- handleClose: () => void;
-};
-
-export const AlertBanner = ({ open, message, handleClose }: Props) => {
- return (
-
-
-
-
- }
- >
- {message}
-
-
- );
-};
diff --git a/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx b/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx
new file mode 100644
index 0000000000..5145dc54b8
--- /dev/null
+++ b/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx
@@ -0,0 +1,168 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import {
+ Grid,
+ makeStyles,
+ Card,
+ CardContent,
+ Typography,
+ Link,
+ GridSize,
+} from '@material-ui/core';
+import { Avatar } from '@backstage/core-components';
+import { AboutField } from '@backstage/plugin-catalog';
+import { StatusTag } from '../StatusTag';
+import { Member, BazaarProject } from '../../types';
+
+const useStyles = makeStyles({
+ break: {
+ wordBreak: 'break-word',
+ },
+});
+
+type Props = {
+ bazaarProject: BazaarProject;
+ members: Member[];
+ descriptionSize: GridSize;
+ membersSize: GridSize;
+};
+
+export const CardContentFields = ({
+ bazaarProject,
+ members,
+ descriptionSize,
+ membersSize,
+}: Props) => {
+ const classes = useStyles();
+
+ return (
+
+ );
+ }
+ return null;
+};
diff --git a/plugins/bazaar/src/components/EntityBazaarInfoContent/index.ts b/plugins/bazaar/src/components/EntityBazaarInfoContent/index.ts
new file mode 100644
index 0000000000..6be5ad7dbf
--- /dev/null
+++ b/plugins/bazaar/src/components/EntityBazaarInfoContent/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { EntityBazaarInfoContent } from './EntityBazaarInfoContent';
diff --git a/plugins/bazaar/src/components/HomePage/HomePage.tsx b/plugins/bazaar/src/components/HomePage/HomePage.tsx
index 32a16291fc..b34b7ac949 100644
--- a/plugins/bazaar/src/components/HomePage/HomePage.tsx
+++ b/plugins/bazaar/src/components/HomePage/HomePage.tsx
@@ -36,11 +36,7 @@ export const HomePage = () => {
return (
+ );
+};
diff --git a/plugins/bazaar/src/components/HomePageBazaarInfoCard/index.ts b/plugins/bazaar/src/components/HomePageBazaarInfoCard/index.ts
new file mode 100644
index 0000000000..0edfba0652
--- /dev/null
+++ b/plugins/bazaar/src/components/HomePageBazaarInfoCard/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export { HomePageBazaarInfoCard } from './HomePageBazaarInfoCard';
diff --git a/plugins/bazaar/src/components/InputField/InputField.tsx b/plugins/bazaar/src/components/InputField/InputField.tsx
index 9932e5b1fc..76eb28da6c 100644
--- a/plugins/bazaar/src/components/InputField/InputField.tsx
+++ b/plugins/bazaar/src/components/InputField/InputField.tsx
@@ -25,7 +25,7 @@ type Rules = {
};
type Props = {
- inputType: 'announcement' | 'community' | 'responsible';
+ inputType: 'description' | 'community' | 'responsible' | 'name';
error?: FieldError | undefined;
control: Control;
helperText?: string;
diff --git a/plugins/bazaar/src/components/InputSelector/InputSelector.tsx b/plugins/bazaar/src/components/InputSelector/InputSelector.tsx
index 25ed459963..b7e9ecd96a 100644
--- a/plugins/bazaar/src/components/InputSelector/InputSelector.tsx
+++ b/plugins/bazaar/src/components/InputSelector/InputSelector.tsx
@@ -60,12 +60,7 @@ export const InputSelector = ({ name, options, control, error }: Props) => {
>
{options.map(option => {
return (
-