diff --git a/.changeset/angry-bottles-mix.md b/.changeset/angry-bottles-mix.md deleted file mode 100644 index cd05ef6514..0000000000 --- a/.changeset/angry-bottles-mix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Added a new experimental and hidden `backstage-cli repo lint` command that can be used to lint all packages in the project, similar to `lerna run lint`. diff --git a/.changeset/beige-feet-tap.md b/.changeset/beige-feet-tap.md deleted file mode 100644 index b37d249700..0000000000 --- a/.changeset/beige-feet-tap.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch ---- - -Do not use cross-fetch in the backend diff --git a/.changeset/blue-jobs-know.md b/.changeset/blue-jobs-know.md deleted file mode 100644 index 35feed552b..0000000000 --- a/.changeset/blue-jobs-know.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The `versions:bump --release next` command is updated to compare the `main` and `next` release manifests and prefer the latest. diff --git a/.changeset/brave-singers-burn.md b/.changeset/brave-singers-burn.md deleted file mode 100644 index e8ccfb42e7..0000000000 --- a/.changeset/brave-singers-burn.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -The following processors now properly accept an `ScmIntegrationRegistry` (an -interface) instead of an `ScmIntegrations` (which is a concrete class). - -- `AzureDevOpsDiscoveryProcessor` -- `CodeOwnersProcessor` -- `GitLabDiscoveryProcessor` -- `GithubDiscoveryProcessor` -- `GithubMultiOrgReaderProcessor` -- `GithubOrgReaderProcessor` diff --git a/.changeset/bright-games-eat.md b/.changeset/bright-games-eat.md deleted file mode 100644 index 3bae70283f..0000000000 --- a/.changeset/bright-games-eat.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Remove SearchContextProvider from `` - -The `SidebarSearchModal` exported from `plugin-search` internally renders `SearchContextProvider`, so it can be removed from `Root.tsx`: - -```diff --import { -- SidebarSearchModal, -- SearchContextProvider, --} from '@backstage/plugin-search'; -+import { SidebarSearchModal } from '@backstage/plugin-search'; - -... omitted ... - - } to="/search"> -- -- -- -+ - -``` diff --git a/.changeset/bright-nails-grin.md b/.changeset/bright-nails-grin.md deleted file mode 100644 index 434a17efbb..0000000000 --- a/.changeset/bright-nails-grin.md +++ /dev/null @@ -1,78 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/catalog-client': patch -'@backstage/cli': patch -'@backstage/config-loader': patch -'@backstage/core-app-api': patch -'@backstage/core-components': patch -'@backstage/core-plugin-api': patch -'@backstage/errors': patch -'@backstage/integration': patch -'@backstage/integration-react': patch -'@backstage/release-manifests': patch -'@backstage/test-utils': patch -'@backstage/plugin-airbrake': patch -'@backstage/plugin-allure': patch -'@backstage/plugin-analytics-module-ga': patch -'@backstage/plugin-apache-airflow': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-auth-backend': patch -'@backstage/plugin-auth-node': patch -'@backstage/plugin-azure-devops': patch -'@backstage/plugin-badges': patch -'@backstage/plugin-bazaar': patch -'@backstage/plugin-bitrise': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-catalog-graphql': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-circleci': patch -'@backstage/plugin-cloudbuild': patch -'@backstage/plugin-code-coverage': patch -'@backstage/plugin-config-schema': patch -'@backstage/plugin-cost-insights': patch -'@backstage/plugin-explore': patch -'@backstage/plugin-explore-react': patch -'@backstage/plugin-firehydrant': patch -'@backstage/plugin-fossa': patch -'@backstage/plugin-gcp-projects': patch -'@backstage/plugin-git-release-manager': patch -'@backstage/plugin-github-actions': patch -'@backstage/plugin-github-deployments': patch -'@backstage/plugin-gitops-profiles': patch -'@backstage/plugin-gocd': patch -'@backstage/plugin-graphiql': patch -'@backstage/plugin-home': patch -'@backstage/plugin-ilert': patch -'@backstage/plugin-jenkins': patch -'@backstage/plugin-kafka': patch -'@backstage/plugin-kubernetes': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-newrelic': patch -'@backstage/plugin-newrelic-dashboard': patch -'@backstage/plugin-org': patch -'@backstage/plugin-pagerduty': patch -'@backstage/plugin-permission-backend': patch -'@backstage/plugin-permission-common': patch -'@backstage/plugin-permission-react': patch -'@backstage/plugin-rollbar': patch -'@backstage/plugin-rollbar-backend': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-search': patch -'@backstage/plugin-sentry': patch -'@backstage/plugin-shortcuts': patch -'@backstage/plugin-sonarqube': patch -'@backstage/plugin-splunk-on-call': patch -'@backstage/plugin-tech-insights': patch -'@backstage/plugin-tech-radar': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-techdocs-backend': patch -'@backstage/plugin-todo': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-xcmetrics': patch ---- - -Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 diff --git a/.changeset/brown-impalas-greet.md b/.changeset/brown-impalas-greet.md deleted file mode 100644 index 1717c33451..0000000000 --- a/.changeset/brown-impalas-greet.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Fix error handling of the `runCommand` helper to return `Error` -instance. diff --git a/.changeset/chilly-goats-suffer.md b/.changeset/chilly-goats-suffer.md new file mode 100644 index 0000000000..da98b4019b --- /dev/null +++ b/.changeset/chilly-goats-suffer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Applied fix from version 0.17.2 of this package, which is part of the v0.69.2 release of Backstage. diff --git a/.changeset/clever-pears-turn.md b/.changeset/clever-pears-turn.md deleted file mode 100644 index 33e08c231b..0000000000 --- a/.changeset/clever-pears-turn.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-graph': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-org': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-tech-insights-backend': patch ---- - -Use updated type names from `@backstage/catalog-client` diff --git a/.changeset/curvy-worms-exercise.md b/.changeset/curvy-worms-exercise.md deleted file mode 100644 index b88e9b5891..0000000000 --- a/.changeset/curvy-worms-exercise.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Deprecated `parseLocationReference` and `stringifyLocationReference`, -introducing `parseLocationRef` and `stringifyLocationRef` in their place. diff --git a/.changeset/cyan-oranges-sit.md b/.changeset/cyan-oranges-sit.md deleted file mode 100644 index 8a3244745f..0000000000 --- a/.changeset/cyan-oranges-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Deprecates the `Location` type export from this package. Imports of the `Location` type should now be done from the `@backstage/catalog-client` package instead. diff --git a/.changeset/dependabot-8ed326b.md b/.changeset/dependabot-8ed326b.md deleted file mode 100644 index 88a91baeac..0000000000 --- a/.changeset/dependabot-8ed326b.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-module-elasticsearch': patch ---- - -chore(deps-dev): bump `@elastic/elasticsearch-mock` from 0.3.0 to 1.0.0 diff --git a/.changeset/dry-lies-move.md b/.changeset/dry-lies-move.md deleted file mode 100644 index b0eb1be6f7..0000000000 --- a/.changeset/dry-lies-move.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-todo-backend': patch ---- - -Replace usage of `serializeEntityRef` with `stringifyEntityRef` diff --git a/.changeset/eight-adults-joke.md b/.changeset/eight-adults-joke.md new file mode 100644 index 0000000000..ffda738213 --- /dev/null +++ b/.changeset/eight-adults-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-analytics-module-ga': patch +--- + +Added CSP instructions to README diff --git a/.changeset/fair-bees-join.md b/.changeset/fair-bees-join.md deleted file mode 100644 index 455547cec2..0000000000 --- a/.changeset/fair-bees-join.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Deprecated `ENTITY_DEFAULT_NAMESPACE` constant in favour of `DEFAULT_NAMESPACE`. diff --git a/.changeset/fair-emus-check.md b/.changeset/fair-emus-check.md deleted file mode 100644 index 328d5498b9..0000000000 --- a/.changeset/fair-emus-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Replaced use of deprecated `compareEntityToRef` utility. diff --git a/.changeset/fair-eyes-collect.md b/.changeset/fair-eyes-collect.md deleted file mode 100644 index 5a0cfafaab..0000000000 --- a/.changeset/fair-eyes-collect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Deprecated `compareEntityToRef` due to low usage and provided value. diff --git a/.changeset/fast-ducks-sell.md b/.changeset/fast-ducks-sell.md deleted file mode 100644 index 2ad654124d..0000000000 --- a/.changeset/fast-ducks-sell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Support "dependencyOf" relation in Resource entities diff --git a/.changeset/fast-lizards-fly.md b/.changeset/fast-lizards-fly.md deleted file mode 100644 index e6772b056f..0000000000 --- a/.changeset/fast-lizards-fly.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Bump `passport-oauth2` to version 1.6.1 diff --git a/.changeset/fast-mangos-tell.md b/.changeset/fast-mangos-tell.md deleted file mode 100644 index bfcf429485..0000000000 --- a/.changeset/fast-mangos-tell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -**BREAKING**: Updated `TemplateAction` and related types to have its type parameter extend `JsonObject` instead of `InputBase`. The `createTemplateAction` has also been updated to pass through the `TInput` type parameter to the return type, meaning the `TemplateAction` retains its type. This can lead to breakages during type checking especially within tests. diff --git a/.changeset/fast-paws-arrive.md b/.changeset/fast-paws-arrive.md new file mode 100644 index 0000000000..9c5f4f444f --- /dev/null +++ b/.changeset/fast-paws-arrive.md @@ -0,0 +1,31 @@ +--- +'@backstage/create-app': patch +--- + +Update the template to reflect the renaming of `DocsResultListItem` to `TechDocsSearchResultListItem` from `@backstage/plugin-techdocs`. + +To apply this change to an existing app, make the following change to `packages/app/src/components/search/SearchPage.tsx`: + +```diff +-import { DocsResultListItem } from '@backstage/plugin-techdocs'; ++import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; +``` + +```diff + case 'techdocs': + return ( +- +``` + +The `TechDocsIndexPage` now uses `DefaultTechDocsHome` as fall back if no children is provided as `LegacyTechDocsHome` is marked as deprecated. If you do not use a custom techdocs homepage, you can therefore update your app to the following: + +```diff +- }> +- +- ++ } /> +``` diff --git a/.changeset/fast-stingrays-hope.md b/.changeset/fast-stingrays-hope.md deleted file mode 100644 index 688f14c548..0000000000 --- a/.changeset/fast-stingrays-hope.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-catalog-backend-module-ldap': patch ---- - -Import `Location` from the `@backstage/catalog-client` package. diff --git a/.changeset/flat-crabs-smash.md b/.changeset/flat-crabs-smash.md deleted file mode 100644 index b56b340605..0000000000 --- a/.changeset/flat-crabs-smash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The `versions:bump` command now filters out `npm_` environment configuration when running `yarn install`. This has the effect of allowing it to consider local configuration files within the repository, which is the behavior that one would expect. diff --git a/.changeset/funny-crabs-drop.md b/.changeset/funny-crabs-drop.md deleted file mode 100644 index 764f7c042a..0000000000 --- a/.changeset/funny-crabs-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': minor ---- - -Removed `CatalogClientWrapper` as usage have been replaced by the `fetchApiRef`. diff --git a/.changeset/green-bobcats-burn.md b/.changeset/green-bobcats-burn.md deleted file mode 100644 index c28f4da678..0000000000 --- a/.changeset/green-bobcats-burn.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -'@backstage/plugin-permission-backend': minor ---- - -Add a warning if the permission backend is used without setting `permission.enabled=true`. - -**BREAKING** Permission backend's `createRouter` now requires a `config` option. - -```diff -// packages/backend/src/plugins/permission.ts - -... -export default async function createPlugin({ - ... -+ config, -}: PluginEnvironment) { - return createRouter({ - ... -+ config, - }); -} -``` diff --git a/.changeset/happy-lemons-drop.md b/.changeset/happy-lemons-drop.md deleted file mode 100644 index e4c6f52409..0000000000 --- a/.changeset/happy-lemons-drop.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-permission-common': minor ---- - -**BREAKING** `PermissionCriteria` now requires at least one condition in `anyOf` and `allOf` arrays. This addresses some ambiguous behavior outlined in #9280. diff --git a/.changeset/hip-carrots-suffer.md b/.changeset/hip-carrots-suffer.md deleted file mode 100644 index 3823cf982f..0000000000 --- a/.changeset/hip-carrots-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch ---- - -Fix bug where tech docs collator stores search indices with wrong entity ref casing. Make the collator to conform legacyPathCasing configuration option. diff --git a/.changeset/honest-shrimps-clap.md b/.changeset/honest-shrimps-clap.md deleted file mode 100644 index 12ae623704..0000000000 --- a/.changeset/honest-shrimps-clap.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@backstage/catalog-client': minor ---- - -**BREAKING**: Removed the explicit `DiscoveryApi` and `FetchApi` export symbols, -which were unnecessary duplicates from the well known core ones. - -The `CATALOG_FILTER_EXISTS` symbol's value has changed. However, this should not -affect any code in practice. diff --git a/.changeset/hungry-apples-sparkle.md b/.changeset/hungry-apples-sparkle.md deleted file mode 100644 index 6cfad2d2da..0000000000 --- a/.changeset/hungry-apples-sparkle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Deprecated the `EntityRefContext` type which had limited use. diff --git a/.changeset/khaki-socks-wash.md b/.changeset/khaki-socks-wash.md new file mode 100644 index 0000000000..241ac75be3 --- /dev/null +++ b/.changeset/khaki-socks-wash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Set timeout for scaffolder octokit client diff --git a/.changeset/lazy-jeans-obey.md b/.changeset/lazy-jeans-obey.md deleted file mode 100644 index 9124b983dc..0000000000 --- a/.changeset/lazy-jeans-obey.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Update internal `Location` validation. diff --git a/.changeset/little-carpets-itch.md b/.changeset/little-carpets-itch.md new file mode 100644 index 0000000000..6a4d95c140 --- /dev/null +++ b/.changeset/little-carpets-itch.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-scaffolder': patch +--- + +Added some deprecations as follows: + +- **DEPRECATED**: `TemplateCardComponent` and `TaskPageComponent` props have been deprecated, and moved to a `components` prop instead. You can pass them in through there instead. +- **DEPRECATED**: `TemplateList` and `TemplateListProps` has been deprecated. Please use the `TemplateCard` to create your own list component instead. +- **DEPRECATED**: `setSecret` has been deprecated in favour of `setSecrets` when calling `useTemplateSecrets` + +Other notable changes: + +- `scaffolderApi.scaffold()` `values` type has been narrowed from `Record` to `Record` instead. +- Moved all navigation internally over to using `routeRefs` and `subRouteRefs` diff --git a/.changeset/little-ravens-kick.md b/.changeset/little-ravens-kick.md deleted file mode 100644 index 1e14f2f37e..0000000000 --- a/.changeset/little-ravens-kick.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -The `versions:bump` command now also considers the root `package.json` when searching for updates. It has also received updates to its output, including a link the [Backstage upgrade helper](https://backstage.github.io/upgrade-helper) and silenced `yarn install` output. diff --git a/.changeset/lovely-goats-press.md b/.changeset/lovely-goats-press.md new file mode 100644 index 0000000000..fc3c0b4763 --- /dev/null +++ b/.changeset/lovely-goats-press.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Collapse techdocs sidebar on small devices diff --git a/.changeset/mighty-beds-fix.md b/.changeset/mighty-beds-fix.md deleted file mode 100644 index 3e5ab735dd..0000000000 --- a/.changeset/mighty-beds-fix.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/catalog-client': patch -'@backstage/techdocs-common': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-github-deployments': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-todo-backend': patch ---- - -Updated to use new names for `parseLocationRef` and `stringifyLocationRef` diff --git a/.changeset/moody-knives-jump.md b/.changeset/moody-knives-jump.md deleted file mode 100644 index 84e80d9f84..0000000000 --- a/.changeset/moody-knives-jump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-cicd-statistics': patch ---- - -Updated `@backstage/core-plugin-api` dependency. diff --git a/.changeset/olive-flies-reflect.md b/.changeset/olive-flies-reflect.md deleted file mode 100644 index 80075d8fb0..0000000000 --- a/.changeset/olive-flies-reflect.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/catalog-model': minor ---- - -**BREAKING**: Remove deprecated validation methods `analyzeLocationSchema`, `locationSchema` and `locationSpecSchema`. -This functionality was primarily used internally by the `catalog-backend`. diff --git a/.changeset/patched.json b/.changeset/patched.json index 1029c8f106..486cfe13a5 100644 --- a/.changeset/patched.json +++ b/.changeset/patched.json @@ -1,3 +1,5 @@ { - "currentReleaseVersion": {} + "currentReleaseVersion": { + "@backstage/plugin-scaffolder-backend": "0.17.2" + } } diff --git a/.changeset/perfect-papayas-crash.md b/.changeset/perfect-papayas-crash.md deleted file mode 100644 index 99c93d1b8d..0000000000 --- a/.changeset/perfect-papayas-crash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-permission-node': minor ---- - -**BREAKING** `PermissionCriteria` now requires at least one condition in `anyOf` and `allOf` arrays. This addresses some ambiguous behavior outlined in #9280. diff --git a/.changeset/pink-onions-ring.md b/.changeset/pink-onions-ring.md deleted file mode 100644 index 2b81ad4b43..0000000000 --- a/.changeset/pink-onions-ring.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': minor ---- - -**BREAKING** Completely removed the `EntitySystemDiagramCard` component which was deprecated in a previous release. Any remaining references to this component are now broken and should be replaced with `EntityCatalogGraphCard`, which can be imported from package `@backstage/plugin-catalog-graph`. diff --git a/.changeset/plenty-boxes-wink.md b/.changeset/plenty-boxes-wink.md deleted file mode 100644 index 0011641938..0000000000 --- a/.changeset/plenty-boxes-wink.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': minor ---- - -**BREAKING**: Remove deprecated `serializeEntityRef` which is replaced by `stringifyEntityRef`. diff --git a/.changeset/polite-dots-train.md b/.changeset/polite-dots-train.md deleted file mode 100644 index dbd11d3e60..0000000000 --- a/.changeset/polite-dots-train.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-yeoman': patch ---- - -Bump `yeoman-environment` dependency from `^3.6.0` to `^3.9.1`. diff --git a/.changeset/proud-fishes-carry.md b/.changeset/proud-fishes-carry.md deleted file mode 100644 index 3dd8a445e6..0000000000 --- a/.changeset/proud-fishes-carry.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog': minor ---- - -Removed deprecated `Router` and `EntityPageLayout` exports. -`Router` is replaced by plugin extensions and `EntityPageLayout` is replaced by `CatalogEntityPage`. diff --git a/.changeset/purple-files-hang.md b/.changeset/purple-files-hang.md deleted file mode 100644 index 680de46a80..0000000000 --- a/.changeset/purple-files-hang.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-client': patch ---- - -Export the `Location` type that was previously exported by the `@backstage/catalog-model` package. diff --git a/.changeset/seven-rocks-share.md b/.changeset/seven-rocks-share.md deleted file mode 100644 index db131a85dc..0000000000 --- a/.changeset/seven-rocks-share.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/integration-react': patch ---- - -Updated the `ScmAuth` error message for missing provider configurations to link to `ScmAuthApi` setup documentation. diff --git a/.changeset/shaggy-readers-explain.md b/.changeset/shaggy-readers-explain.md new file mode 100644 index 0000000000..29266e5a32 --- /dev/null +++ b/.changeset/shaggy-readers-explain.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Cleanup API report diff --git a/.changeset/shy-peaches-begin.md b/.changeset/shy-peaches-begin.md deleted file mode 100644 index 4787fe8dea..0000000000 --- a/.changeset/shy-peaches-begin.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-react': patch ---- - -Fix CatalogPage showing all components when owned filter was pre-selected diff --git a/.changeset/silly-ghosts-dance.md b/.changeset/silly-ghosts-dance.md deleted file mode 100644 index e83fc87521..0000000000 --- a/.changeset/silly-ghosts-dance.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Improve overall appearance of highlighted code in docs. diff --git a/.changeset/smart-items-fry.md b/.changeset/smart-items-fry.md new file mode 100644 index 0000000000..625db82ce7 --- /dev/null +++ b/.changeset/smart-items-fry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Applied fix from `v0.17.1` of this package which is part of the `v0.69.1` release of Backstage. diff --git a/.changeset/sour-hats-love.md b/.changeset/sour-hats-love.md deleted file mode 100644 index f2cc88e66a..0000000000 --- a/.changeset/sour-hats-love.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-common': patch ---- - -Updated `isDatabaseConflictError` to handle modern sqlite conflict errors diff --git a/.changeset/spicy-items-itch.md b/.changeset/spicy-items-itch.md deleted file mode 100644 index e6617a42a3..0000000000 --- a/.changeset/spicy-items-itch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-jenkins-backend': patch ---- - -The `DefaultJenkinsInfoProvider.fromConfig` now requires an implementation of the `CatalogApi` rather than a `CatalogClient` instance. diff --git a/.changeset/spicy-lies-grin.md b/.changeset/spicy-lies-grin.md new file mode 100644 index 0000000000..b7e020f1db --- /dev/null +++ b/.changeset/spicy-lies-grin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': patch +--- + +Fixed bug in Catalog LDAP module to acknowledge page events to continue receiving entries if pagePause=true diff --git a/.changeset/stupid-cows-design.md b/.changeset/stupid-cows-design.md deleted file mode 100644 index 2df99664fb..0000000000 --- a/.changeset/stupid-cows-design.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Deprecates the following exports which all have very limited and internal use. `ENTITY_META_GENERATED_FIELDS`, `entityHasChanges`, `generateEntityEtag`, `generateEntityUid`, `generateUpdatedEntity`, `ENTITY_META_GENERATED_FIELDS`. diff --git a/.changeset/sweet-keys-dress.md b/.changeset/sweet-keys-dress.md deleted file mode 100644 index 4217f2fcf4..0000000000 --- a/.changeset/sweet-keys-dress.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated the default [sucrase](https://github.com/alangpierce/sucrase)-based Jest transform to include source maps if the environment variable `ENABLE_SOURCE_MAPS` is non-empty. This can be used to better support editor test debugging integrations. diff --git a/.changeset/swift-rules-hunt.md b/.changeset/swift-rules-hunt.md new file mode 100644 index 0000000000..15707bbf90 --- /dev/null +++ b/.changeset/swift-rules-hunt.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +adds passing projectID to the Storage client diff --git a/.changeset/tasty-eagles-retire.md b/.changeset/tasty-eagles-retire.md deleted file mode 100644 index c39f30d57f..0000000000 --- a/.changeset/tasty-eagles-retire.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/catalog-client': patch ---- - -Deprecated the following types used by the catalog client, and created new -corresponding types to make them more consistent: - -- `CatalogEntitiesRequest` -> `GetEntitiesRequest` -- `CatalogListResponse` was removed and generally replaced with `GetEntitiesResponse` (which does not use a type parameter argument) -- `CatalogEntityAncestorsRequest`-> `GetEntityAncestorsRequest` -- `CatalogEntityAncestorsResponse` -> `GetEntityAncestorsResponse` diff --git a/.changeset/tasty-taxis-dance.md b/.changeset/tasty-taxis-dance.md deleted file mode 100644 index 34ed30f686..0000000000 --- a/.changeset/tasty-taxis-dance.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/catalog-client': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-backend-module-ldap': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-github-deployments': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-todo': patch -'@backstage/plugin-todo-backend': patch ---- - -Replaces use of deprecated catalog-model constants. diff --git a/.changeset/techdocs-cats-whisper.md b/.changeset/techdocs-cats-whisper.md new file mode 100644 index 0000000000..290f8a1d7a --- /dev/null +++ b/.changeset/techdocs-cats-whisper.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Remove copyright from old footer in documentation generated with previous version of `mkdocs-techdocs-plugin` (`v0.2.2`). diff --git a/.changeset/techdocs-toes-lie.md b/.changeset/techdocs-toes-lie.md new file mode 100644 index 0000000000..14ca2567e2 --- /dev/null +++ b/.changeset/techdocs-toes-lie.md @@ -0,0 +1,5 @@ +--- +'@techdocs/cli': patch +--- + +Bump `@backstage/techdocs-common` to `0.11.10` to use `spotify/techdocs:v0.3.7` which upgrades `mkdocs-theme` as a dependency of `mkdocs-techdocs-core`. diff --git a/.changeset/techdocs-war-on-war.md b/.changeset/techdocs-war-on-war.md new file mode 100644 index 0000000000..28722feed3 --- /dev/null +++ b/.changeset/techdocs-war-on-war.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fixed a bug that could cause searches in the in-context TechDocs search bar to show results from a different TechDocs site. diff --git a/.changeset/three-roses-swim.md b/.changeset/three-roses-swim.md deleted file mode 100644 index ae6ad44845..0000000000 --- a/.changeset/three-roses-swim.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/techdocs-common': patch -'@backstage/plugin-auth-backend': patch -'@backstage/plugin-badges': patch -'@backstage/plugin-badges-backend': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-graph': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-cost-insights': patch -'@backstage/plugin-ilert': patch -'@backstage/plugin-org': patch -'@backstage/plugin-pagerduty': patch -'@backstage/plugin-search': patch -'@backstage/plugin-techdocs-backend': patch ---- - -Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. diff --git a/.changeset/tidy-boxes-sell.md b/.changeset/tidy-boxes-sell.md deleted file mode 100644 index 82ee16af57..0000000000 --- a/.changeset/tidy-boxes-sell.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@backstage/plugin-search': minor ---- - -**BREAKING**: `useSearch` doesn't return anymore `open` and `toggleModal`. -The two properties have been moved to the `useSearchModal` hook. - -``` -import { SearchModal, useSearchModal } from '@backstage/plugin-search'; - -const Foo = () => { - const { state, setOpen, toggleModal } = useSearchModal(); - - return ( - - ); -}; -``` diff --git a/.changeset/tiny-lobsters-exercise.md b/.changeset/tiny-lobsters-exercise.md new file mode 100644 index 0000000000..78cc753bab --- /dev/null +++ b/.changeset/tiny-lobsters-exercise.md @@ -0,0 +1,29 @@ +--- +'@backstage/plugin-techdocs': minor +--- + +**BREAKING:** +Table column utilities `createNameColumn`, `createOwnerColumn`, `createTypeColumn` as well as actions utilities `createCopyDocsUrlAction` and `createStarEntityAction` are no longer directly exported. Instead accessible through DocsTable and EntityListDocsTable. + +Use as following: + +```tsx +DocsTable.columns.createNameColumn(); +DocsTable.columns.createOwnerColumn(); +DocsTable.columns.createTypeColumn(); + +DocsTable.actions.createCopyDocsUrlAction(); +DocsTable.actions.createStarEntityAction(); +``` + +- Renamed `DocsResultListItem` to `TechDocsSearchResultListItem`, leaving the old name in place as a deprecations. + +- Renamed `TechDocsPage` to `TechDocsReaderPage`, leaving the old name in place as a deprecations. + +- Renamed `TechDocsPageRenderFunction` to `TechDocsPageRenderFunction`, leaving the old name in place as a deprecations. + +- Renamed `TechDocsPageHeader` to `TechDocsReaderPageHeader`, leaving the old name in place as a deprecations. + +- `LegacyTechDocsHome` marked as deprecated and will be deleted in next release, use `TechDocsCustomHome` instead. + +- `LegacyTechDocsPage` marked as deprecated and will be deleted in next release, use `TechDocsReaderPage` instead. diff --git a/.changeset/tough-clocks-enjoy.md b/.changeset/tough-clocks-enjoy.md new file mode 100644 index 0000000000..90e00382df --- /dev/null +++ b/.changeset/tough-clocks-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend': patch +--- + +Tweaked the wording of the "does not have a location" errors to include the actual missing annotation name, to help users better in fixing their inputs. diff --git a/.changeset/tough-pets-beg.md b/.changeset/tough-pets-beg.md new file mode 100644 index 0000000000..7fdcc6dc01 --- /dev/null +++ b/.changeset/tough-pets-beg.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Fix Bitbucket Cloud and Bitbucket Server line number reference. diff --git a/.changeset/tough-stingrays-sit.md b/.changeset/tough-stingrays-sit.md deleted file mode 100644 index 5cef08c8e3..0000000000 --- a/.changeset/tough-stingrays-sit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Optimized entity provider mutations with large numbers of new additions, such as big initial startup commits diff --git a/.changeset/twenty-dodos-rule.md b/.changeset/twenty-dodos-rule.md deleted file mode 100644 index 5167811a3d..0000000000 --- a/.changeset/twenty-dodos-rule.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Deprecates `EDIT_URL_ANNOTATION`, `LOCATION_ANNOTATION`, `ORIGIN_LOCATION_ANNOTATION`, `SOURCE_LOCATION_ANNOTATION`, `VIEW_URL_ANNOTATION` -and replaces these constants all prefixed with ANNOTATION\_ `ANNOTATION_EDIT_URL`, `ANNOTATION_LOCATION`, `ANNOTATION_ORIGIN_LOCATION`, `ANNOTATION_SOURCE_LOCATION`, `ANNOTATION_VIEW_URL` diff --git a/.changeset/unlucky-queens-brush.md b/.changeset/unlucky-queens-brush.md new file mode 100644 index 0000000000..c080856246 --- /dev/null +++ b/.changeset/unlucky-queens-brush.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Added ability for SidebarSubmenuItem to handle external links correctly via the "to" prop diff --git a/.changeset/wise-foxes-confess.md b/.changeset/wise-foxes-confess.md new file mode 100644 index 0000000000..8322b66555 --- /dev/null +++ b/.changeset/wise-foxes-confess.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Fix the support for custom defaultBranch values for Bitbucket Cloud at the `publish:bitbucket` scaffolder action. diff --git a/.changeset/witty-mails-search.md b/.changeset/witty-mails-search.md deleted file mode 100644 index a98fc52884..0000000000 --- a/.changeset/witty-mails-search.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend-module-cookiecutter': minor -'@backstage/plugin-scaffolder-backend-module-rails': minor -'@backstage/plugin-scaffolder-backend-module-yeoman': minor ---- - -Updated to the latest version of `@backstage/plugin-scaffolder-backend`, meaning the `TemplateAction` now exposes the precise input type rather than `any`. diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 8303510e77..6359493d4b 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -3,6 +3,8 @@ accessors addon addons Airbrake +Airbrakes +airbrake Anddddd Apdex api @@ -17,6 +19,7 @@ autoscaling Autoscaling autoselect Avro +aws backrub backported backporting @@ -275,6 +278,8 @@ sqlite squidfunk src stdout +stringify +stringified subcomponent subcomponents subkey diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 82bf46a17a..49c97bb197 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,4 @@ name: CI - on: pull_request: paths-ignore: diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index 8b92c000ed..7931f7ade5 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -1,5 +1,4 @@ name: Deploy Microsite - on: push: branches: diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index 3fbc2fafe5..4997668563 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -1,5 +1,4 @@ name: Deploy Nightly Release - on: schedule: - cron: '0 2 * * *' # run at 2 AM UTC diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index ed02a313fe..9b7a233767 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -1,5 +1,4 @@ name: Deploy Packages - on: workflow_dispatch: push: @@ -189,7 +188,7 @@ jobs: run: yarn tsc:full - name: build packages - run: yarn lerna -- run --ignore example-app build + run: yarn lerna -- run --ignore example-app --ignore example-backend build # Publishes current version of packages that are not already present in the registry - name: publish diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index 7e0458cb21..75c154792c 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -1,5 +1,4 @@ name: Sync code formatting - on: push: branches: diff --git a/.github/workflows/sync_dependabot-changesets.yml b/.github/workflows/sync_dependabot-changesets.yml index ed97cf05f1..58e0a1ac0f 100644 --- a/.github/workflows/sync_dependabot-changesets.yml +++ b/.github/workflows/sync_dependabot-changesets.yml @@ -2,7 +2,7 @@ name: Sync Dependabot changeset on: pull_request_target: paths: - - .github/workflows/dependabot-changeset-maker.yml + - '.github/workflows/sync_dependabot-changesets.yml' - '**/yarn.lock' jobs: diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 4b44caafba..f4d9afc0df 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -5,6 +5,7 @@ on: version: description: Version number, without any 'v' prefix required: true + jobs: create-new-version: runs-on: ubuntu-latest diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index 93b6c696d0..0bf0340f68 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -1,5 +1,4 @@ name: Sync Snyk GitHub issues - on: schedule: - cron: '0 */4 * * *' diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index f9e6679173..bf50748dc0 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -1,11 +1,10 @@ name: Sync Snyk Monitoring - on: workflow_dispatch: push: branches: [master] paths: - - '.github/workflows/snyk-monitor.yml' + - '.github/workflows/sync_snyk-monitor.yml' - '**/.snyk' - '**/package.json' - 'yarn.lock' diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index e028387190..58d715c5a8 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -1,5 +1,4 @@ name: Sync Version Packages PR - on: push: branches: diff --git a/.github/workflows/verify_docs-quality.yml b/.github/workflows/verify_docs-quality.yml index 57298d69dd..55c1c23587 100644 --- a/.github/workflows/verify_docs-quality.yml +++ b/.github/workflows/verify_docs-quality.yml @@ -1,9 +1,9 @@ name: Verify Docs Quality - on: pull_request: branches: [master] paths: + - '.github/workflows/verify_docs-quality.yml' - '**.md' jobs: diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index 6694ace02b..3e39aee1ae 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -1,5 +1,4 @@ name: E2E Test Linux - on: pull_request: paths-ignore: diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index 3806eade25..7caf880366 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -1,5 +1,4 @@ name: E2E Test Techdocs - on: pull_request: paths-ignore: diff --git a/.github/workflows/verify_e2e-tugboat.yml b/.github/workflows/verify_e2e-tugboat.yml index a9c810ff68..1054ccf579 100644 --- a/.github/workflows/verify_e2e-tugboat.yml +++ b/.github/workflows/verify_e2e-tugboat.yml @@ -1,5 +1,6 @@ name: E2E Test Tugboat on: deployment_status + jobs: set-pending: if: github.event.deployment_status.state != 'success' && github.event.deployment_status.state != 'failed' diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index b4347898e6..0977831d19 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -1,11 +1,11 @@ -name: E2E Test Windows - # Building on windows is really slow, so this workflow is separate from e2e.yml and only builds on changes # to the cli itself. They're more likely to introduce issues on windows, compared to changes to core and yarn.lock. + +name: E2E Test Windows on: pull_request: paths: - - '.github/workflows/e2e-win.yml' + - '.github/workflows/verify_e2e-windows.yml' - 'packages/cli/**' - 'packages/e2e-test/**' - 'packages/create-app/**' @@ -20,7 +20,7 @@ jobs: strategy: matrix: - os: [windows-latest] + os: [windows-2019] node-version: [14.x, 16.x] env: diff --git a/.github/workflows/verify_microsite.yml b/.github/workflows/verify_microsite.yml index ac05f72be5..24a6ff5351 100644 --- a/.github/workflows/verify_microsite.yml +++ b/.github/workflows/verify_microsite.yml @@ -1,9 +1,8 @@ name: Verify Microsite - on: pull_request: paths: - - '.github/workflows/microsite-build-check.yml' + - '.github/workflows/verify_microsite.yml' - 'microsite/**' - 'docs/**' diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 2142f52445..60f58f4c66 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -2,7 +2,7 @@ name: Verify Storybook on: pull_request: paths: - - '.github/workflows/chromatic-storybook-test.yml' + - '.github/workflows/verify_storybook.yml' - 'storybook/**' - 'packages/config/src/**' - 'packages/theme/src/**' diff --git a/.github/workflows/verify_windows.yml b/.github/workflows/verify_windows.yml index 55959510d3..9ae4397bba 100644 --- a/.github/workflows/verify_windows.yml +++ b/.github/workflows/verify_windows.yml @@ -1,13 +1,15 @@ name: Verify Master Branch on Windows - on: workflow_dispatch: push: branches: [master] + pull_request: + paths: + - '.github/workflows/verify_windows.yml' jobs: build: - runs-on: windows-latest + runs-on: windows-2019 strategy: matrix: diff --git a/ADOPTERS.md b/ADOPTERS.md index 1682e13316..eb83726b8d 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,3 +1,7 @@ +# Adopters + +_If you're using Backstage in your organization, please try to add your company name to this list. This really helps the project to gain momentum and credibility. It's a small contribution back to the project with a big impact._ + | Organization | Contact | Description of Use | | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Spotify](https://www.spotify.com) | [@leemills83](https://github.com/leemills83) | Main interface towards all of Spotify's infrastructure and technical documentation. | @@ -74,7 +78,7 @@ | [GoTo](https://www.goto.com) | [Lorenzo Orsatti](https://github.com/lorsatti) | Improve onboarding experience of new developers. Discover faster and painlessly developer documentation, API definitions and team information. Provide useful dev metrics in a central place. Provide easy-to-use templates for new services. | | [Telstra](https://www.telstra.com.au) | [@kiranpatel11](https://github.com/kiranpatel11), [JasonC](https://github.com/JasonC17) | Primary usage: software catalog and templates
Emerging usage : TechDocs, Explore Ecosystem, TechRadar, etc | | [Mosaico](https://www.mosaico.com.br/) | [Wédney Yuri](https://github.com/wedneyyuri),[@tino.milton](https://github.com/miltonjacomini) | A centralized service catalog of our documentation for our service engineers. | -| [Mox Bank](https://www.mox.com/) | [Nick Laqua](https://github.com/nick-laqua-dragon) | "Single pane of glass" developer portal for providing a best-in-class developer experience to our product teams and making Mox the best tech environment in Hongkong 🥰🚀 | +| [Mox Bank](https://www.mox.com/) | [Nick Laqua](https://github.com/nick-laqua-dragon) | "Single pane of glass" developer portal for providing a best-in-class developer experience to our product teams and making Mox the best tech environment in Hongkong 🥰🚀 | | [Keyloop](https://www.keyloop.com/) | [Andre Wanlin](https://github.com/awanlin) | Future-motive Developer Portal to help our teams create technology to make everything about buying and owning a car better. 🚗 | | [Simply Business](https://sbtech.simplybusiness.co.uk/) | [@addersuk](https://github.com/addersuk), [@LightningStairs](https://github.com/LightningStairs), [@punitcse](https://github.com/punitcse), [@moltenice](https://github.com/moltenice) | Central developer portal to access everything a developer needs such as docs, internal service catalog, and the ability to quickly create a new service from a template. Internally developed Backstage plugins allow us to customise the experience to how we work. | | [Overwolf](https://www.overwolf.com) | [@tomwolfgang](https://github.com/tomwolfgang) | Dev portal - software catalog, tech-docs, scaffolding | @@ -93,3 +97,6 @@ | [Invitae](https://www.invitae.com/en) | [@ryan-hanchett](https://github.com/ryan-hanchett), [@gmandler42](https://github.com/gmandler42) | Centralized Developer Experience portal, putting all of our tooling behind a single pane of glass and creating a living service catalog. | | [Fortum](https://www.fortum.com/) | [@brunoamaroalmeida](https://github.com/brunoamaroalmeida), [@dhaval-vithalani](https://github.com/dhaval-vithalani), [@sunilkumarmohanty](https://github.com/sunilkumarmohanty) | A central portal containing information about our applications, services, processes and other software assets to be used by Fortum software engineering community. | | [Procore](https://www.procore.com/jobs/engineering) | [@shayon](https://github.com/Shayon), [@jamesdabbs-procore](https://github.com/jamesdabbs-procore) | Developer portal - centralized software catalog and templates to enable self serve and reduce cognitive load. | +| [Bradesco](https://banco.bradesco/html/classic/sobre/index.shtm) | [@danilosoarescardoso](https://github.com/danilosoarescardoso) | Centralized developer portal with software catalog, software templates, techdocs and some plugins from community and by our own. | +| [SeatGeek](https://seatgeek.com) | [@zhammer](https://github.com/zhammer) | Software catalog and documentation platform | +| [Grupo Boticário](https://www.grupoboticario.com.br/) | [@chicoribas](https://github.com/chicoribas), [@fndiaz](https://github.com/fndiaz), [@digogid](https://github.com/digogid), [@manumbs](https://github.com/manumbs), [@haooliveira84](https://github.com/haooliveira84), [@Rhullyam](https://github.com/Rhullyam) | Centralized developer portal with catalog, documentation, and software templates to build a ready to go application, including pipelines (CI/CD) and cloud services provisioning | \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f054de05ce..e853a68ad3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ Our vision for Backstage is for it to become the trusted standard toolbox (read: UX layer) for the open source infrastructure landscape. Think of it like Kubernetes for developer experience. We realize this is an ambitious goal. We can’t do it alone. -Therefore we want to create strong community of contributors -- all working together to create the kind of delightful experience that our developers deserve. +Therefore we want to create a strong community of contributors -- all working together to create the kind of delightful experience that our developers deserve. Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. ❤️ @@ -100,7 +100,7 @@ Signed-off-by: Jane Smith - In case you forgot to add it to the last N commits in your branch, use `git rebase --signoff HEAD~N` and replace N with the number of new commits you created in your branch. - If you have a very deep branch with a lot of commits, run `git rebase -i --signoff $(git merge-base -a master HEAD)`, double check to make sense of the commits (keep all lines as `pick`) and save and close the editor. This should bulk sign all the commits in your PR. Do be careful though. If you have a complex flow with a lot of branching and re-merging of work branches and stuff, merge-base may not be the right solution for you. -Note: If you have already pushed you branch to a remote, you might have to force push: `git push -f` after the rebase. +Note: If you have already pushed your branch to a remote, you might have to force push: `git push -f` after the rebase. ### Using GitHub Desktop? diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 3bcef61021..5bafa19c81 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -24,7 +24,7 @@ To become a maintainer you need to demonstrate the following: - Monitor Discord (delayed response is perfectly acceptable). - Triage GitHub issues and perform pull request reviews for other maintainers and the community. - Triage build issues - file issues for known flaky builds or bugs, and either fix or find someone to fix any master build breakages. -- During GitHub issue triage, apply all applicable ([labels](https://github.com/backstage/backstage/labels)) to each new issue. Labels are extremely useful for future issue follow up. Which labels to apply is somewhat subjective so just use your best judgment. A few of the most important labels that are not self explanatory are: +- During GitHub issue triage, apply all applicable ([labels](https://github.com/backstage/backstage/labels)) to each new issue. Labels are extremely useful for future issue follow-up. Which labels to apply is somewhat subjective so just use your best judgment. A few of the most important labels that are not self-explanatory are: - good first issue: Mark any issue that can reasonably be accomplished by a new contributor with this label. - help wanted: Unless it is immediately obvious that someone is going to work on an issue (and if so assign it), mark it help wanted. - Make sure that ongoing PRs are moving forward at the right pace or closing them. diff --git a/STYLE.md b/STYLE.md index d968d387fc..d74616a88c 100644 --- a/STYLE.md +++ b/STYLE.md @@ -131,7 +131,7 @@ We use [API Extractor](https://api-extractor.com/pages/overview/demo_docs/) to g The doc comments are of the good old `/** ... */` format, with tags of the format `@` used to mark various things. The [TSDoc website](https://tsdoc.org/) has a good index of all available tags. -There are a few things to pay attention to make the documentation show up in a nice way on the website... +There are a few things to pay attention to, in order to make the documentation show up in a nice way on the website... ## Declare exported functions using the `function` keyword @@ -163,7 +163,7 @@ If the parameters of a function are destructed in the parameter list, they will ![image](https://user-images.githubusercontent.com/4984472/133117011-3e6cb6da-40dd-450e-8d33-51bdcf412e08.png) -Instead prefer to use a single parameter variable and then move the destructuring into the function body instead, which will look much nicer: +Instead prefer to use a single parameter variable and then move the destructuring into the function body, which will look much nicer: ![image](https://user-images.githubusercontent.com/4984472/133120542-d648de43-5495-43a8-b532-f5e6f6ab736e.png) diff --git a/app-config.yaml b/app-config.yaml index b025064c74..ab68d4398f 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -211,14 +211,6 @@ catalog: - Location processors: - githubOrg: - providers: - - target: https://github.com - token: ${GITHUB_TOKEN} - #### Example for how to add your GitHub Enterprise instance using the API: - # - target: https://ghe.example.net - # apiBaseUrl: https://ghe.example.net/api - # token: ${GHE_TOKEN} ldapOrg: ### Example for how to add your enterprise LDAP server # providers: diff --git a/contrib/chart/backstage/templates/ingress.yaml b/contrib/chart/backstage/templates/ingress.yaml index 3fa052485e..cac9b61553 100644 --- a/contrib/chart/backstage/templates/ingress.yaml +++ b/contrib/chart/backstage/templates/ingress.yaml @@ -15,7 +15,6 @@ metadata: {{- if .Values.issuer.email }} cert-manager.io/cluster-issuer: {{ .Values.issuer.clusterIssuer }} {{- end }} - kubernetes.io/ingress.class: nginx nginx.ingress.kubernetes.io/ssl-redirect: "false" nginx.ingress.kubernetes.io/configuration-snippet: | if ($scheme = https) { diff --git a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md index 6b1b8cb4e7..a95b276365 100644 --- a/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md +++ b/contrib/docs/tutorials/aws-alb-aad-oidc-auth.md @@ -182,12 +182,12 @@ Use the following `auth` configuration when running Backstage on AWS: auth: providers: awsalb: - issuer: - issuer: https://login.microsoftonline.com/{TENANT_ID}/v2.0 - region: { AWS_REGION } + development: + issuer: https://login.microsoftonline.com//v2.0 + region: ``` -Replace `{TENANT_ID}` with the value of `Directory (tenant) ID` of the AAD App and `{AWS_REGION}` with the AWS region identifier where the ALB is deployed (for example: `eu-central-1`). +Replace `` with the value of `Directory (tenant) ID` of the AAD App and `` with the AWS region identifier where the ALB is deployed (for example: `eu-central-1`). ## Conclusion diff --git a/docs/assets/getting-started/sidebar-with-catalog.png b/docs/assets/getting-started/sidebar-with-catalog.png new file mode 100644 index 0000000000..d80346c211 Binary files /dev/null and b/docs/assets/getting-started/sidebar-with-catalog.png differ diff --git a/docs/assets/getting-started/sidebar-without-catalog.png b/docs/assets/getting-started/sidebar-without-catalog.png new file mode 100644 index 0000000000..bfeab13235 Binary files /dev/null and b/docs/assets/getting-started/sidebar-without-catalog.png differ diff --git a/docs/assets/getting-started/simple-homepage.png b/docs/assets/getting-started/simple-homepage.png new file mode 100644 index 0000000000..128707e9ff Binary files /dev/null and b/docs/assets/getting-started/simple-homepage.png differ diff --git a/docs/auth/bitbucket/provider.md b/docs/auth/bitbucket/provider.md index ee87c2d4e6..c54fb75ffa 100644 --- a/docs/auth/bitbucket/provider.md +++ b/docs/auth/bitbucket/provider.md @@ -67,7 +67,7 @@ Bitbucket username to `bitbucket.org/username` annotations of `User` entities in the catalog. Note that you must populate your catalog with matching entities or users will not be able to sign in. -The second resolver is the `bitbucketUsernameSignInResolver`, which works the +The second resolver is the `bitbucketUserIdSignInResolver`, which works the same way, but uses the Bitbucket user ID instead, and matches on the `bitbucket.org/user-id` annotation. diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index 6f58abe769..36967f352a 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -56,7 +56,7 @@ Once the host build is complete, we are ready to build our image. The following `Dockerfile` is included when creating a new app with `@backstage/create-app`: ```Dockerfile -FROM node:14-buster-slim +FROM node:16-bullseye-slim WORKDIR /app # Copy repo skeleton first, to avoid unnecessary docker cache invalidation. @@ -65,6 +65,12 @@ WORKDIR /app COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz +# install sqlite3 dependencies +RUN apt-get update && \ + apt-get install -y libsqlite3-dev python3 cmake g++ && \ + rm -rf /var/lib/apt/lists/* && \ + yarn config set python /usr/bin/python3 + RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" # Then copy the rest of the backend bundle, along with any other files we might want. @@ -134,23 +140,28 @@ the repo root: ```Dockerfile # Stage 1 - Create yarn install skeleton layer -FROM node:14-buster-slim AS packages +FROM node:16-bullseye-slim AS packages WORKDIR /app COPY package.json yarn.lock ./ COPY packages packages + # Comment this out if you don't have any internal plugins COPY plugins plugins RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+ # Stage 2 - Install dependencies and build packages -FROM node:14-buster-slim AS build +FROM node:16-bullseye-slim AS build WORKDIR /app COPY --from=packages /app . +# install sqlite3 dependencies +RUN apt-get update && apt-get install -y libsqlite3-dev python3 cmake g++ && \ + yarn config set python /usr/bin/python3 + RUN yarn install --frozen-lockfile --network-timeout 600000 && rm -rf "$(yarn cache dir)" COPY . . @@ -159,7 +170,7 @@ RUN yarn tsc RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies # Stage 3 - Build the actual backend image and install production dependencies -FROM node:14-buster-slim +FROM node:16-bullseye-slim WORKDIR /app @@ -167,6 +178,12 @@ WORKDIR /app COPY --from=build /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz +# install sqlite3 dependencies +RUN apt-get update && \ + apt-get install -y libsqlite3-dev python3 cmake g++ && \ + rm -rf /var/lib/apt/lists/* && \ + yarn config set python /usr/bin/python3 + RUN yarn install --frozen-lockfile --production --network-timeout 600000 && rm -rf "$(yarn cache dir)" # Copy the built packages from the build stage diff --git a/docs/features/software-catalog/catalog-customization.md b/docs/features/software-catalog/catalog-customization.md index 821f39ff6e..80c9458aee 100644 --- a/docs/features/software-catalog/catalog-customization.md +++ b/docs/features/software-catalog/catalog-customization.md @@ -93,7 +93,7 @@ export type CustomFilters = DefaultEntityFilters & { To control this filter, we can create a React component that shows checkboxes for the security tiers. This component will make use of the -`useEntityListProvider` hook, which accepts this extended filter type as a +`useEntityList` hook, which accepts this extended filter type as a [generic](https://www.typescriptlang.org/docs/handbook/2/generics.html) parameter: @@ -103,7 +103,7 @@ export const EntitySecurityTierPicker = () => { const { filters: { securityTiers }, updateFilters, - } = useEntityListProvider(); + } = useEntityList(); // Toggles the value, depending on whether it's already selected function onChange(value: string) { diff --git a/docs/features/software-catalog/external-integrations.md b/docs/features/software-catalog/external-integrations.md index b14fd732d7..59caf9475c 100644 --- a/docs/features/software-catalog/external-integrations.md +++ b/docs/features/software-catalog/external-integrations.md @@ -367,11 +367,11 @@ The class will have this basic structure: ```ts import { UrlReader } from '@backstage/backend-common'; -import { LocationSpec } from '@backstage/catalog-model'; import { results, CatalogProcessor, CatalogProcessorEmit, + LocationSpec, } from '@backstage/plugin-catalog-backend'; // A processor that reads from the fictional System-X @@ -448,13 +448,14 @@ behavior for `system-x` that we implemented earlier. ```ts import { UrlReader } from '@backstage/backend-common'; -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { results, CatalogProcessor, CatalogProcessorEmit, CatalogProcessorCache, CatalogProcessorParser, + LocationSpec, } from '@backstage/plugin-catalog-backend'; // It's recommended to always bump the CACHE_KEY version if you make @@ -472,8 +473,8 @@ type CacheItem = { export class SystemXReaderProcessor implements CatalogProcessor { constructor(private readonly reader: UrlReader) {} - // It's recommended to give the processor a unique name. getProcessorName() { + // The processor name must be unique. return 'system-x-processor'; } diff --git a/docs/features/techdocs/cli.md b/docs/features/techdocs/cli.md index 8122fefdcd..a5dc8c3c54 100644 --- a/docs/features/techdocs/cli.md +++ b/docs/features/techdocs/cli.md @@ -123,7 +123,7 @@ Options: --no-pull Do not pull the latest docker image --no-docker Do not use Docker, use MkDocs executable and plugins in current user environment. --techdocs-ref The repository hosting documentation source files e.g. - github:https://ghe.mycompany.net.com/org/repo. + url:https://ghe.mycompany.net.com/org/repo. This value is same as the backstage.io/techdocs-ref annotation of the corresponding Backstage entity. It is completely fine to skip this as it is only being used to set repo_url in mkdocs.yml diff --git a/docs/getting-started/app-custom-theme.md b/docs/getting-started/app-custom-theme.md index fc68008ae8..34eca2429a 100644 --- a/docs/getting-started/app-custom-theme.md +++ b/docs/getting-started/app-custom-theme.md @@ -257,68 +257,4 @@ const LogoFull = () => { ## Custom Homepage In addition to a custom theme, a custom logo, you can also customize the -homepage of your app. To do that we need to go through a few steps. - -### Setting up the Home Page - -1. Create a Home Page Component that will be used for composition. - -`packages/app/src/components/home/HomePage.tsx` - -```tsx -import React from 'react'; - -export const HomePage = () => { - return { - /* TODO: Compose a Home Page here */ - }; -}; -``` - -2. Add a route where the homepage will live, presumably `/`. - -`packages/app/src/App.tsx` - -```tsx -import { HomepageCompositionRoot } from '@backstage/plugin-home'; -import { HomePage } from './components/home/HomePage'; - -// ... -}> - -; -// ... -``` - -### Composing your Home Page - -Composing a Home Page is no different from creating a regular React Component, -i.e. the App Integrator is free to include whatever content they like. However, -there are components developed with the Home Page in mind. If you are looking -for components to use when composing your homepage, you can take a look at the -[collection of Homepage components](https://backstage.io/?path=/story/plugins-home-components) -in storybook. If you don't find a component that suits your needs but want to -contribute, check the -[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing). - -> If you want to use one of the available homepage templates you can find the -> [templates](https://backstage.io/storybook/?path=/story/plugins-home-templates) -> in the storybook under the "Home" plugin. And if you would like to contribute -> a template, please see the -> [Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing) - -```tsx -import React from 'react'; -import Grid from '@material-ui/core/Grid'; -import { HomePageCompanyLogo } from '@backstage/plugin-home'; - -export const HomePage = () => { - return ( - - - - - - ); -}; -``` +homepage of your app. Read the full guide on the [next page](homepage.md). diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index 55285e7f4e..082fbf5b70 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -28,7 +28,7 @@ At the end of this tutorial, you can expect: These instructions can be skipped if you already have a PostgreSQL server installed and created a schema and user. The example below is for Linux, but -luckily there's detailed instructions on how to +luckily there are detailed instructions on how to [install PostgreSQL](https://www.postgresql.org/download/) to help you get started. @@ -113,7 +113,7 @@ new component, or register an existing one it will be saved in the database. Later in this tutorial you'll add a service, and you can test if it's persistent as advertised. -If you want to read more about the database configuration, here's some helpful +If you want to read more about the database configuration, here are some helpful links: - [Configuring Plugin Databases](../tutorials/configuring-plugin-databases.md#privileges) @@ -122,7 +122,7 @@ links: ### Setting up authentication -There's multiple authentication providers available for you to use with +There are multiple authentication providers available for you to use with Backstage, feel free to follow [the instructions for adding authentication](../auth/). @@ -191,7 +191,7 @@ components: { That should be it. You can stop your Backstage App. When you start it again and go to your Backstage portal in your browser, you should have your login prompt! -To learn more about Authentication in Backstage, there's the following docs you +To learn more about Authentication in Backstage, here are some docs you could read: - [Adding Authentication](../auth/) @@ -208,7 +208,7 @@ and Groups can also be loaded from an organization. While using GitHub Apps might be the best way to set up integrations, for this tutorial you'll use a Personal Access Token. -Create your Personal Access Token by opening the +Create your Personal Access Token by opening [the GitHub token creation page](https://github.com/settings/tokens/new). Use a name to identify this token and put it in the notes field. Choose a number of days for expiration. If you have a hard time picking a number, we suggest to go diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md index 1d65f25d1b..05c12ffb75 100644 --- a/docs/getting-started/create-an-app.md +++ b/docs/getting-started/create-an-app.md @@ -16,8 +16,7 @@ may want to [Run Backstage Locally](./running-backstage-locally.md) instead. ## Create an app To create a Backstage app, you will need to have -[Node.js](https://nodejs.org/en/download/) Active LTS Release installed -(currently v14). +Node.js [Active LTS Release](https://nodejs.org/en/about/releases/) installed. Backstage provides a utility for creating new apps. It guides you through the initial setup of selecting the name of the app and a database for the backend. diff --git a/docs/getting-started/homepage.md b/docs/getting-started/homepage.md new file mode 100644 index 0000000000..59272a285b --- /dev/null +++ b/docs/getting-started/homepage.md @@ -0,0 +1,166 @@ +--- +id: homepage +title: Backstage homepage - Setup and Customization +description: Documentation on setting up and customizing Backstage homepage +--- + +## Homepage + +Having a good Backstage homepage can significantly improve the discoverability of the platform. You want your users to find all the things they need right from the homepage and never have to remember direct URLs in Backstage. The [Home plugin](https://github.com/backstage/backstage/tree/master/plugins/home) introduces a system for composing a homepage for Backstage in order to surface relevant info and provide convenient shortcuts for common tasks. It's designed with composability in mind with an open ecosystem that allows anyone to contribute with any component, to be included in any homepage. + +For App Integrators, the system is designed to be composable to give total freedom in designing a Homepage that suits the needs of the organization. From the perspective of a Component Developer who wishes to contribute with building blocks to be included in Homepages, there's a convenient interface for bundling the different parts and exporting them with both error boundary and lazy loading handled under the surface. + +At the end of this tutorial, you can expect: + +- Your Backstage app to have a dedicated homepage instead of Software Catalog. +- Understand the composability of homepage and how to start customizing it for your own organization. + +### Prerequisites + +Before we begin, make sure + +- You have created your own standalone Backstage app using [`@backstage/create-app`](index.md#create-your-backstage-app) and not using a fork of the [backstage](https://github.com/backstage/backstage) repository. +- You do not have an existing homepage, and by default you are redirected to Software Catalog when you open Backstage. + +Now, let's get started by installing the home plugin and creating a simple homepage for your Backstage app. + +### Setup homepage + +#### 1. Install the plugin + +``` +# From your Backstage application directory +cd packages/app +yarn add @backstage/plugin-home +``` + +#### 2. Create a new HomePage component + +Inside your `packages/app` directory, create a new file where our new homepage component is going to live. Create `packages/app/src/components/home/HomePage.tsx` with the following initial code + +```tsx +import React from 'react'; + +export const HomePage = () => { + /* We will shortly compose a pretty homepage here. */ + return

Welcome to Backstage!

; +}; +``` + +#### 3. Update router for the root `/` route + +If you don't have a homepage already, most likely you have a redirect setup to use the Catalog homepage as a homepage. + +Inside your `packages/app/src/App.tsx`, look for + +```tsx +const routes = ( + + +``` + +Let's replace the `` line and use the new component we created in the previous step as the new homepage. + +```diff +// File: packages/app/src/App.tsx + ++ import { HomepageCompositionRoot } from '@backstage/plugin-home'; ++ import { HomePage } from './components/home/HomePage'; + +// ... +const routes = ( + +- ++ }> ++ ++ +// ... + +) +``` + +### 4. Update sidebar items + +Let's update the route for "Home" in the Backstage sidebar to point to the new homepage. We'll also add a Sidebar item to quickly open Catalog. + + + + + + + + +
Sidebar without Catalog + Sidebar with Catalog +
BeforeAfter
+ +The code for the Backstage sidebar is most likely inside your [`packages/app/src/components/Root/Root.tsx`](https://github.com/backstage/backstage/blob/master/packages/app/src/components/Root/Root.tsx). + +Let's make the following changes + +```diff +// Other imports ++ import CategoryIcon from '@material-ui/icons/Category'; + +export const Root = ({ children }: PropsWithChildren<{}>) => ( + + + + # ... + }> + {/* Global nav, not org-specific */} +- ++ ++ + + + + + {/* End global nav */} + +``` + +That's it! You should now have _(although slightly boring)_ a homepage! + +Screenshot of a blank homepage + +In the next steps, we will make it interesting and useful! + +### Use the default template + +There is a default homepage template ([storybook link](https://backstage.io/storybook/?path=/story/plugins-home-templates--default-template)) which we will use to set up our homepage. Checkout the [blog post announcement](https://backstage.io/blog/2022/01/25/backstage-homepage-templates) about the Backstage homepage templates for more information. + + + +### Composing your homepage + +Composing a homepage is no different from creating a regular React Component, +i.e. the App Integrator is free to include whatever content they like. However, +there are components developed with the homepage in mind. If you are looking +for components to use when composing your homepage, you can take a look at the +[collection of Homepage components](https://backstage.io/storybook?path=/story/plugins-home-components) +in storybook. If you don't find a component that suits your needs but want to +contribute, check the +[Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing). + +> If you want to use one of the available homepage templates you can find the +> [templates](https://backstage.io/storybook/?path=/story/plugins-home-templates) +> in the storybook under the "Home" plugin. And if you would like to contribute +> a template, please see the +> [Contributing documentation](https://github.com/backstage/backstage/blob/master/plugins/home/README.md#contributing) + +```tsx +import React from 'react'; +import Grid from '@material-ui/core/Grid'; +import { HomePageCompanyLogo } from '@backstage/plugin-home'; + +export const HomePage = () => { + return ( + + + + + + ); +}; +``` diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index a622313739..8c2f6bc865 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -25,7 +25,7 @@ guide to do a repository-based installation. [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/) - An account with elevated rights to install the dependencies - `curl` or `wget` installed -- Node.js Active LTS Release installed (currently v14) using one of these +- Node.js [Active LTS Release](https://nodejs.org/en/about/releases/) installed using one of these methods: - Using `nvm` (recommended) - [Installing nvm](https://github.com/nvm-sh/nvm#install--update-script) diff --git a/docs/getting-started/running-backstage-locally.md b/docs/getting-started/running-backstage-locally.md index daad7bf195..3443b85285 100644 --- a/docs/getting-started/running-backstage-locally.md +++ b/docs/getting-started/running-backstage-locally.md @@ -13,13 +13,14 @@ trying to contribute, follow the instructions to - Node.js -First make sure you are using Node.js with an Active LTS Release, currently v14. +First make sure you are using Node.js with an [Active LTS Release](https://nodejs.org/en/about/releases/). This is made easy with a version manager such as [nvm](https://github.com/nvm-sh/nvm) which allows for version switching. ```bash -# Installing a new version -nvm install 14 +# Installing current LTS release +nvm install --lts +> Installing latest LTS version. > Downloading and installing node v14.15.1... > Now using node v14.15.1 (npm v6.14.8) diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index ad452f47af..1deec423a7 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -12,7 +12,7 @@ DevOps organization and register entities matching the configured path. This can be useful as an alternative to static locations or manually adding things to the catalog. -To use the discovery processor, you'll need a GitHub integration +To use the discovery processor, you'll need a Azure integration [set up](locations.md) with a `AZURE_TOKEN`. Then you can add a location target to the catalog configuration: diff --git a/docs/integrations/github/locations.md b/docs/integrations/github/locations.md index 330b03eab9..3395c61117 100644 --- a/docs/integrations/github/locations.md +++ b/docs/integrations/github/locations.md @@ -57,6 +57,22 @@ public GitHub, for which we can infer them). The `apiBaseUrl` will always be preferred over the other if a `token` is given, otherwise `rawBaseUrl` will be preferred. +### Token scopes + +When creating a personal access token on GitHub, you must select scopes to +define the level of access for the token. The scopes required vary depending on +your use of the integration: + +- Reading software components: + - `repo` +- Reading organization data: + - `read:org` + - `read:user` + - `user:email` +- Publishing software templates: + - `repo` + - `workflow` (if templates include GitHub workflows) + ## Authentication with GitHub Apps Alternatively, Backstage can use GitHub Apps for backend authentication. This diff --git a/docs/integrations/github/org.md b/docs/integrations/github/org.md index 7140ffa505..5843944e8a 100644 --- a/docs/integrations/github/org.md +++ b/docs/integrations/github/org.md @@ -22,19 +22,6 @@ entities that mirror your org setup. The processor that performs the import, `GithubOrgReaderProcessor`, comes installed with the default setup of Backstage. -If you have _replaced_ the set of processors in your installation using -`replaceProcessors`, you can import and add it as follows: - -```ts -// Typically in packages/backend/src/plugins/catalog.ts -import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend'; - -builder.replaceProcessors( - GithubOrgReaderProcessor.fromConfig(config, { logger }), - // ... -); -``` - ## Configuration The following configuration enables an import of the teams and users under the @@ -51,10 +38,12 @@ If Backstage is configured to use GitHub Apps authentication you must grant `Read-Only` access for `Members` under `Organization` in order to ingest users correctly. You can modify the app's permissions under the organization settings, `https://github.com/organizations/{ORG}/settings/apps/{APP_NAME}/permissions`. + ![permissions](../../assets/integrations/github/permissions.png) **Please note that when you change permissions, the app owner will get an email that must be approved first before the changes are applied.** + ![email](../../assets/integrations/github/email.png) Locations point out the specific org(s) you want to import. The `type` of these @@ -65,3 +54,22 @@ The authorization for loading org information comes from a configured [GitHub integration](locations.md#configuration). When using a personal access token, the token needs to have at least the scopes `read:org`, `read:user`, and `user:email` in the given `target`. + +## Addendum + +Some Backstage apps have _replaced_ the catalog processors to override the +default catalog processing behavior. These apps will already have a call to +`replaceProcessors` in the catalog initialization. + +In the unlikely scenario that this applies to your app, you can import and +add the `GithubOrgReaderProcessor` as follows: + +```ts +// Typically in packages/backend/src/plugins/catalog.ts +import { GithubOrgReaderProcessor } from '@backstage/plugin-catalog-backend'; + +builder.replaceProcessors( + // ... other processor replacements + GithubOrgReaderProcessor.fromConfig(config, { logger }), +); +``` diff --git a/docs/local-dev/cli-build-system.md b/docs/local-dev/cli-build-system.md index c4a08afc56..75f0606752 100644 --- a/docs/local-dev/cli-build-system.md +++ b/docs/local-dev/cli-build-system.md @@ -334,13 +334,19 @@ The following is an example of a `Dockerfile` that can be used to package the output of `backstage-cli backend:bundle` into an image: ```Dockerfile -FROM node:14-buster-slim +FROM node:16-bullseye-slim WORKDIR /app COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz -RUN yarn install --production --frozen-lockfile --network-timeout 300000 && rm -rf "$(yarn cache dir)" +# install sqlite3 dependencies +RUN apt-get update && \ + apt-get install -y libsqlite3-dev python3 cmake g++ && \ + rm -rf /var/lib/apt/lists/* && \ + yarn config set python /usr/bin/python3 + +RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" COPY packages/backend/dist/bundle.tar.gz app-config.yaml ./ RUN tar xzf bundle.tar.gz && rm bundle.tar.gz diff --git a/docs/overview/adopting.md b/docs/overview/adopting.md index 152be21a2c..0765d4a452 100644 --- a/docs/overview/adopting.md +++ b/docs/overview/adopting.md @@ -25,7 +25,7 @@ This team will have **four** primary objectives: 2. Drive adoption of customers (developers at your company). -3. Work with senior tech leadership and architects to ensure your organizations +3. Work with senior tech leadership and architects to ensure your organization's best practices for software development are encoded into a set of [Software Templates](../features/software-templates/index.md). @@ -62,7 +62,7 @@ products they maintain. ### Tactics -Example of tactics we have used to evangelize Backstage internally: +Examples of tactics we have used to evangelize Backstage internally: - Arrange "Lunch & Learns" and seminars. Frequently offer teams interested in Backstage development to come to a seminar where you show, for example, how to @@ -77,13 +77,13 @@ Example of tactics we have used to evangelize Backstage internally: - Show & tell meetings. In order to build an internal community around Backstage we have quarterly meetings where anyone working on Backstage is invited to - present their work. This is a not only a great way to get early feedback, but + present their work. This is not only a great way to get early feedback, but also helps coordination between teams that are building overlapping experiences. - Provide metrics. Add instrumentation to your Backstage deployment and make - metrics available to contributing teams. At Spotify we have even gone so far - as sending out weekly digest email showing how usage metrics have changed for + metrics available to contributing teams. At Spotify, we have even gone so far + as to send out weekly digest emails showing how usage metrics have changed for individual plugins. - Pro-actively identify new plugins. Reach out to teams that own internal UIs or @@ -109,12 +109,12 @@ successful impact on your software development process: - **Deploys to production** Cousin to the metric above: How many times does an engineer push changes into production. -- **MTTR** With clear ownership of all the pieces in your micro services +- **MTTR** With clear ownership of all the pieces in your microservices ecosystem and all tools integrated into one place, Backstage makes it quicker for teams to find the root cause of failures, and fix them. - **Context switching** Reducing context switching can help engineers stay in - the "zone". We measure the number of different tools an engineer have to + the "zone". We measure the number of different tools an engineer has to interact with in order to get a certain job done (e.g. push a change, follow it into production and validate it did not break anything). diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 5d27cc4184..358dc53852 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -281,11 +281,11 @@ stores as a means of improving performance or reliability. Similar to how databases are supported, plugins receive logically separated cache connections, which are powered by [Keyv](https://github.com/lukechilds/keyv) under the hood. -At this time of writing, Backstage can be configured to use one of two cache -stores: memory, which is mainly used for local testing, and memcache, which is a -cache store better suited for production deployment. The right cache store for -your Backstage instance will depend on your own run-time constraints and those -required of the plugins you're running. +At this time of writing, Backstage can be configured to use one of three cache +stores: memory, which is mainly used for local testing, memcache or Redis, +which are cache stores better suited for production deployment. The right cache +store for your Backstage instance will depend on your own run-time constraints +and those required of the plugins you're running. ### Use memory for cache @@ -304,6 +304,15 @@ backend: connection: user:pass@cache.example.com:11211 ``` +### Use Redis for cache + +```yaml +backend: + cache: + store: redis + connection: user:pass@cache.example.com:6379 +``` + Contributions supporting other cache stores are welcome! ## Containerization diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 1c620348bb..8ecd7c4a38 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -21,7 +21,7 @@ mean features in the radar, but not yet scheduled. The long-term roadmap (12 - 36 months) is not detailed in the public roadmap. Third-party contributions are also not currently included in the roadmap. Let us -know about any ongoing developments and we’re happy to include it here as well. +know about any ongoing developments and we’re happy to include them here as well. ## What’s next diff --git a/docs/plugins/github-apps.md b/docs/plugins/github-apps.md index 1be1502b6b..434c9a4882 100644 --- a/docs/plugins/github-apps.md +++ b/docs/plugins/github-apps.md @@ -109,11 +109,21 @@ privateKey: | This will result in backstage preventing the use of any installation that is not within the allow list. -### Permissions for pull requests +### App permissions -These are the minimum permissions required for creating a pull request with -Backstage software templates: +When creating a GitHub App, you must select permissions to define the level of +access for the app. The permissions required vary depending on your use of the +integration: -- Read and Write permissions for `Contents`. -- Read and write permissions for `Pull Requests` and `Issues`. -- Read permissions on `Metadata`. +- Reading software components: + - `Contents`: `Read-only` +- Reading organization data: + - `Members`: `Read-only` +- Publishing software templates: + + - `Administration`: `Read & write` (for creating repositories) + - `Contents`: `Read & write` + - `Metadata`: `Read-only` + - `Pull requests`: `Read & write` + - `Issues`: `Read & write` + - `Workflows`: `Read & write` (if templates include GitHub workflows) diff --git a/docs/publishing.md b/docs/publishing.md index 2951f7d30b..fbbef93493 100644 --- a/docs/publishing.md +++ b/docs/publishing.md @@ -65,8 +65,8 @@ process is used to release an emergency fix as version `6.5.1` in the patch rele master branch as well. Create a PR that contains the following: - [ ] The fix, which you can likely cherry-pick from your patch branch: `git cherry-pick origin/patch/v1.18.0^` - - [ ] An updated `CHANGELOG.md` of all patched patches from the tip of the patch branch, which you can get using `git checkout origin/patch/v1.18.0 -- plugins/foo/CHANGELOG.md`. - - [ ] A changeset with the message "Applied the fix from version `6.5.1` of this package, which is part of the `v1.18.1` release of Backstage.", + - [ ] An updated `CHANGELOG.md` of all patched packages from the tip of the patch branch, `git checkout origin/patch/v1.18.0 -- {packages,plugins}/*/CHANGELOG.md`. + - [ ] A changeset with the message "Applied the fix from version `6.5.1` of this package, which is part of the `v1.18.1` release of Backstage." - [ ] An entry in `.changeset/patched.json` that sets the current release version to `6.5.1`: ```json diff --git a/microsite/blog/2022-01-25-backstage-homepage-templates.md b/microsite/blog/2022-01-25-backstage-homepage-templates.md index 7ed5ee92e3..8ca8b7373e 100644 --- a/microsite/blog/2022-01-25-backstage-homepage-templates.md +++ b/microsite/blog/2022-01-25-backstage-homepage-templates.md @@ -33,7 +33,7 @@ _Example of the default homepage template in Backstage Storybook_ ## How to get started -To get started, you first need to set up your homepage plugin in the app if you have not already, then you can move on to composing your homepage. You can find our step-by-step documentation of how to do this under the [App Configuration - Customize the look-and-feel of your App documentation](https://backstage.io/docs/getting-started/app-custom-theme#custom-homepage). You can also access the [Backstage UI Kit in Figma](https://www.figma.com/file/nUaAw56hTgC0RIOYkuLSrB/Backstage-Design-System?node-id=2185%3A2978) to duplicate your own version and play around with a fitting homepage for your own organization. +To get started, you first need to set up your homepage plugin in the app if you have not already, then you can move on to composing your homepage. You can find our step-by-step documentation of how to do this under the [App Configuration - Customize the look-and-feel of your App documentation](https://backstage.io/docs/getting-started/homepage). You can also access the [Backstage UI Kit in Figma](https://www.figma.com/file/nUaAw56hTgC0RIOYkuLSrB/Backstage-Design-System?node-id=2185%3A2978) to duplicate your own version and play around with a fitting homepage for your own organization. ![Example of homepage components in the Backstage UI Kit in Figma](assets/22-01-25/homepage-components-figma.png) _Example of the homepage components in the Backstage UI Kit in Figma_ diff --git a/microsite/data/plugins/airbrake.yaml b/microsite/data/plugins/airbrake.yaml index 96de75c002..29c1f394d7 100644 --- a/microsite/data/plugins/airbrake.yaml +++ b/microsite/data/plugins/airbrake.yaml @@ -4,6 +4,6 @@ author: Simply Business authorUrl: https://github.com/simplybusiness/ category: Monitoring description: Access Airbrake error monitoring and other integrations from within Backstage -documentation: https://github.com/backstage/backstage/blob/master/plugins/airbrake/README.md +documentation: https://github.com/backstage/backstage/blob/master/plugins/airbrake iconUrl: https://wp-assets.airbrake.io/wp-content/uploads/2020/10/05222904/Square-white-A-on-Orange.png npmPackageName: '@backstage/plugin-airbrake' diff --git a/microsite/sidebars.json b/microsite/sidebars.json index cb7c0d5036..5369182013 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -22,7 +22,8 @@ "label": "App configuration", "ids": [ "getting-started/configure-app-with-plugins", - "getting-started/app-custom-theme" + "getting-started/app-custom-theme", + "getting-started/homepage" ] }, "getting-started/keeping-backstage-updated", diff --git a/microsite/yarn.lock b/microsite/yarn.lock index 01226a9bf9..a9161f57fd 100644 --- a/microsite/yarn.lock +++ b/microsite/yarn.lock @@ -5215,9 +5215,9 @@ prettier@^2.5.1: integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg== prismjs@^1.22.0: - version "1.25.0" - resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== + version "1.27.0" + resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== process-nextick-args@~2.0.0: version "2.0.1" @@ -6499,9 +6499,9 @@ url-parse-lax@^3.0.0: prepend-http "^2.0.0" url-parse@^1.4.3: - version "1.5.3" - resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" - integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== + version "1.5.10" + resolved "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" diff --git a/package.json b/package.json index 242c64c8bb..e7cb42c1c6 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "**/@roadiehq/**/@backstage/plugin-catalog": "*", "**/@roadiehq/**/@backstage/catalog-model": "*" }, - "version": "0.67.0", + "version": "0.69.0", "dependencies": { "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.15.0", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 74313008cd..6fb6c46b76 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/app-defaults +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + - @backstage/core-app-api@0.5.4 + - @backstage/plugin-permission-react@0.3.2 + +## 0.1.8 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-app-api@0.5.3 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-permission-react@0.3.1 + - @backstage/theme@0.2.15 + ## 0.1.7 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index c7cb87faa4..e65d8f53ac 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,13 +1,16 @@ { "name": "@backstage/app-defaults", "description": "Provides the default wiring of a Backstage App", - "version": "0.1.7", + "version": "0.1.9", "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,19 +24,20 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-app-api": "^0.5.2", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-permission-react": "^0.3.0", - "@backstage/theme": "^0.2.14", + "@backstage/core-components": "^0.8.10", + "@backstage/core-app-api": "^0.5.4", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-permission-react": "^0.3.2", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "react-router-dom": "6.0.0-beta.0" @@ -42,8 +46,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@types/jest": "^26.0.7", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 1139bf2cbb..029b97ec30 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,110 @@ # example-app +## 0.2.66 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@0.9.0 + - @backstage/cli@0.14.1 + - @backstage/core-components@0.8.10 + - @backstage/plugin-api-docs@0.8.0 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/plugin-catalog-graph@0.2.12 + - @backstage/plugin-jenkins@0.6.0 + - @backstage/plugin-scaffolder@0.13.0 + - @backstage/plugin-techdocs@0.14.0 + - @backstage/plugin-airbrake@0.3.0 + - @backstage/catalog-model@0.11.0 + - @backstage/plugin-catalog-import@0.8.3 + - @backstage/plugin-explore@0.3.31 + - @backstage/core-plugin-api@0.7.0 + - @backstage/plugin-circleci@0.3.0 + - @backstage/plugin-cloudbuild@0.3.0 + - @backstage/plugin-github-actions@0.5.0 + - @backstage/plugin-kafka@0.3.0 + - @backstage/plugin-kubernetes@0.6.0 + - @backstage/plugin-lighthouse@0.3.0 + - @backstage/plugin-org@0.5.0 + - @backstage/plugin-rollbar@0.4.0 + - @backstage/plugin-gocd@0.1.6 + - @backstage/plugin-azure-devops@0.1.16 + - @backstage/plugin-badges@0.2.24 + - @backstage/plugin-code-coverage@0.1.27 + - @backstage/plugin-home@0.4.16 + - @backstage/plugin-newrelic-dashboard@0.1.8 + - @backstage/plugin-pagerduty@0.3.27 + - @backstage/plugin-search@0.7.1 + - @backstage/plugin-sentry@0.3.38 + - @backstage/plugin-tech-insights@0.1.10 + - @backstage/plugin-todo@0.2.2 + - @backstage/plugin-cost-insights@0.11.22 + - @backstage/app-defaults@0.1.9 + - @backstage/core-app-api@0.5.4 + - @backstage/integration-react@0.1.23 + - @backstage/plugin-apache-airflow@0.1.8 + - @backstage/plugin-gcp-projects@0.3.19 + - @backstage/plugin-graphiql@0.2.32 + - @backstage/plugin-newrelic@0.3.18 + - @backstage/plugin-permission-react@0.3.2 + - @backstage/plugin-shortcuts@0.2.1 + - @backstage/plugin-tech-radar@0.5.7 + - @backstage/plugin-user-settings@0.3.21 + +## 0.2.65 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.14.0 + - @backstage/core-app-api@0.5.3 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/integration-react@0.1.22 + - @backstage/plugin-airbrake@0.2.0 + - @backstage/plugin-apache-airflow@0.1.7 + - @backstage/plugin-api-docs@0.7.3 + - @backstage/plugin-azure-devops@0.1.15 + - @backstage/plugin-badges@0.2.23 + - @backstage/plugin-catalog@0.8.0 + - @backstage/plugin-catalog-import@0.8.2 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/plugin-circleci@0.2.38 + - @backstage/plugin-cloudbuild@0.2.36 + - @backstage/plugin-code-coverage@0.1.26 + - @backstage/plugin-cost-insights@0.11.21 + - @backstage/plugin-explore@0.3.30 + - @backstage/plugin-gcp-projects@0.3.18 + - @backstage/plugin-github-actions@0.4.36 + - @backstage/plugin-gocd@0.1.5 + - @backstage/plugin-graphiql@0.2.31 + - @backstage/plugin-home@0.4.15 + - @backstage/plugin-jenkins@0.5.21 + - @backstage/plugin-kafka@0.2.29 + - @backstage/plugin-kubernetes@0.5.8 + - @backstage/plugin-lighthouse@0.2.38 + - @backstage/plugin-newrelic@0.3.17 + - @backstage/plugin-newrelic-dashboard@0.1.7 + - @backstage/plugin-org@0.4.3 + - @backstage/plugin-pagerduty@0.3.26 + - @backstage/plugin-permission-react@0.3.1 + - @backstage/plugin-rollbar@0.3.27 + - @backstage/plugin-scaffolder@0.12.3 + - @backstage/plugin-search@0.7.0 + - @backstage/plugin-sentry@0.3.37 + - @backstage/plugin-shortcuts@0.2.0 + - @backstage/plugin-tech-insights@0.1.9 + - @backstage/plugin-tech-radar@0.5.6 + - @backstage/plugin-techdocs@0.13.4 + - @backstage/plugin-todo@0.2.1 + - @backstage/plugin-user-settings@0.3.20 + - @backstage/app-defaults@0.1.8 + - @backstage/catalog-model@0.10.0 + - @backstage/search-common@0.2.3 + - @backstage/theme@0.2.15 + - @backstage/plugin-catalog-common@0.1.3 + - @backstage/plugin-catalog-graph@0.2.11 + ## 0.2.64 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index bdade7974a..25dcbbd945 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,65 +1,68 @@ { "name": "example-app", - "version": "0.2.64", + "version": "0.2.66", "private": true, + "backstage": { + "role": "frontend" + }, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^0.1.7", - "@backstage/catalog-model": "^0.9.10", - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/integration-react": "^0.1.21", - "@backstage/plugin-airbrake": "^0.1.3", - "@backstage/plugin-api-docs": "^0.7.2", - "@backstage/plugin-azure-devops": "^0.1.14", - "@backstage/plugin-apache-airflow": "^0.1.6", - "@backstage/plugin-badges": "^0.2.22", - "@backstage/plugin-catalog": "^0.7.12", - "@backstage/plugin-catalog-common": "^0.1.2", - "@backstage/plugin-catalog-graph": "^0.2.10", - "@backstage/plugin-catalog-import": "^0.8.1", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/plugin-circleci": "^0.2.37", - "@backstage/plugin-cloudbuild": "^0.2.35", - "@backstage/plugin-code-coverage": "^0.1.25", - "@backstage/plugin-cost-insights": "^0.11.20", - "@backstage/plugin-explore": "^0.3.29", - "@backstage/plugin-gcp-projects": "^0.3.17", - "@backstage/plugin-github-actions": "^0.4.35", - "@backstage/plugin-gocd": "^0.1.4", - "@backstage/plugin-graphiql": "^0.2.30", - "@backstage/plugin-home": "^0.4.14", - "@backstage/plugin-jenkins": "^0.5.20", - "@backstage/plugin-kafka": "^0.2.28", - "@backstage/plugin-kubernetes": "^0.5.7", - "@backstage/plugin-lighthouse": "^0.2.37", - "@backstage/plugin-newrelic": "^0.3.16", - "@backstage/plugin-newrelic-dashboard": "^0.1.6", - "@backstage/plugin-org": "^0.4.2-next.0", - "@backstage/plugin-pagerduty": "0.3.25", - "@backstage/plugin-permission-react": "^0.3.0", - "@backstage/plugin-rollbar": "^0.3.26", - "@backstage/plugin-scaffolder": "^0.12.2", - "@backstage/plugin-search": "^0.6.2", - "@backstage/plugin-sentry": "^0.3.36", - "@backstage/plugin-shortcuts": "^0.1.22", - "@backstage/plugin-tech-radar": "^0.5.5", - "@backstage/plugin-techdocs": "^0.13.3", - "@backstage/plugin-todo": "^0.2.0", - "@backstage/plugin-user-settings": "^0.3.19", - "@backstage/search-common": "^0.2.2", - "@backstage/plugin-tech-insights": "^0.1.8", - "@backstage/theme": "^0.2.14", + "@backstage/app-defaults": "^0.1.9", + "@backstage/catalog-model": "^0.11.0", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/integration-react": "^0.1.23", + "@backstage/plugin-airbrake": "^0.3.0", + "@backstage/plugin-api-docs": "^0.8.0", + "@backstage/plugin-azure-devops": "^0.1.16", + "@backstage/plugin-apache-airflow": "^0.1.8", + "@backstage/plugin-badges": "^0.2.24", + "@backstage/plugin-catalog": "^0.9.0", + "@backstage/plugin-catalog-common": "^0.1.4", + "@backstage/plugin-catalog-graph": "^0.2.12", + "@backstage/plugin-catalog-import": "^0.8.3", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/plugin-circleci": "^0.3.0", + "@backstage/plugin-cloudbuild": "^0.3.0", + "@backstage/plugin-code-coverage": "^0.1.27", + "@backstage/plugin-cost-insights": "^0.11.22", + "@backstage/plugin-explore": "^0.3.31", + "@backstage/plugin-gcp-projects": "^0.3.19", + "@backstage/plugin-github-actions": "^0.5.0", + "@backstage/plugin-gocd": "^0.1.6", + "@backstage/plugin-graphiql": "^0.2.32", + "@backstage/plugin-home": "^0.4.16", + "@backstage/plugin-jenkins": "^0.6.0", + "@backstage/plugin-kafka": "^0.3.0", + "@backstage/plugin-kubernetes": "^0.6.0", + "@backstage/plugin-lighthouse": "^0.3.0", + "@backstage/plugin-newrelic": "^0.3.18", + "@backstage/plugin-newrelic-dashboard": "^0.1.8", + "@backstage/plugin-org": "^0.5.0", + "@backstage/plugin-pagerduty": "0.3.27", + "@backstage/plugin-permission-react": "^0.3.2", + "@backstage/plugin-rollbar": "^0.4.0", + "@backstage/plugin-scaffolder": "^0.13.0", + "@backstage/plugin-search": "^0.7.1", + "@backstage/plugin-sentry": "^0.3.38", + "@backstage/plugin-shortcuts": "^0.2.1", + "@backstage/plugin-tech-radar": "^0.5.7", + "@backstage/plugin-techdocs": "^0.14.0", + "@backstage/plugin-todo": "^0.2.2", + "@backstage/plugin-user-settings": "^0.3.21", + "@backstage/search-common": "^0.2.4", + "@backstage/plugin-tech-insights": "^0.1.10", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "@octokit/rest": "^18.5.3", - "@roadiehq/backstage-plugin-github-insights": "^1.4.2", - "@roadiehq/backstage-plugin-github-pull-requests": "^1.3.2", - "@roadiehq/backstage-plugin-travis-ci": "^1.3.2", - "@roadiehq/backstage-plugin-buildkite": "^1.3.4", + "@roadiehq/backstage-plugin-github-insights": "^1.5.0", + "@roadiehq/backstage-plugin-github-pull-requests": "^1.4.0", + "@roadiehq/backstage-plugin-travis-ci": "^1.3.6", + "@roadiehq/backstage-plugin-buildkite": "^1.3.8", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^17.0.2", @@ -71,8 +74,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/plugin-permission-react": "^0.3.0", - "@backstage/test-utils": "^0.2.4", + "@backstage/plugin-permission-react": "^0.3.2", + "@backstage/test-utils": "^0.2.6", "@rjsf/core": "^3.2.1", "@testing-library/cypress": "^8.0.2", "@testing-library/jest-dom": "^5.10.1", @@ -84,18 +87,18 @@ "@types/react-dom": "*", "@types/zen-observable": "^0.8.0", "cross-env": "^7.0.0", - "cypress": "^7.3.0", + "cypress": "^9.5.0", "eslint-plugin-cypress": "^2.10.3", "start-server-and-test": "^1.10.11" }, "scripts": { - "start": "backstage-cli app:serve", - "build": "backstage-cli app:build", - "clean": "backstage-cli clean", - "test": "backstage-cli test", + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "test": "backstage-cli package test", "test:e2e": "start-server-and-test start http://localhost:3000 cy:dev", "test:e2e:ci": "start-server-and-test start http://localhost:3000 cy:run", - "lint": "backstage-cli lint", + "lint": "backstage-cli package lint", "cy:dev": "cypress open", "cy:run": "cypress run" }, diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 345915189a..374d637b2e 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -65,7 +65,6 @@ import { import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; import { - DefaultTechDocsHome, TechDocsIndexPage, techdocsPlugin, TechDocsReaderPage, @@ -169,9 +168,7 @@ const routes = ( /> } /> - }> - - + } /> } diff --git a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx index 1c96e9325c..61407e2918 100644 --- a/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx +++ b/packages/app/src/components/scaffolder/customScaffolderExtensions.tsx @@ -13,14 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - +import React from 'react'; import type { FieldValidation } from '@rjsf/core'; import { createScaffolderFieldExtension, - TextValuePicker, + FieldExtensionComponentProps, scaffolderPlugin, } from '@backstage/plugin-scaffolder'; +import { TextField } from '@material-ui/core'; + +const TextValuePicker = (props: FieldExtensionComponentProps) => { + const { + onChange, + required, + schema: { title, description }, + rawErrors, + formData, + uiSchema: { 'ui:autofocus': autoFocus }, + idSchema, + placeholder, + } = props; + + return ( + onChange(value)} + margin="normal" + error={rawErrors?.length > 0 && !formData} + inputProps={{ autoFocus }} + /> + ); +}; + export const LowerCaseValuePickerFieldExtension = scaffolderPlugin.provide( createScaffolderFieldExtension({ name: 'LowerCaseValuePicker', diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index d4e60244d7..65da72d91c 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -24,7 +24,7 @@ import { SidebarPinStateContext, } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -import { CatalogResultListItem } from '@backstage/plugin-catalog'; +import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; import { catalogApiRef, CATALOG_FILTER_EXISTS, @@ -38,7 +38,7 @@ import { SearchType, useSearch, } from '@backstage/plugin-search'; -import { DocsResultListItem } from '@backstage/plugin-techdocs'; +import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; import { Grid, List, makeStyles, Paper, Theme } from '@material-ui/core'; import React, { useContext } from 'react'; @@ -136,14 +136,14 @@ const SearchPage = () => { switch (type) { case 'software-catalog': return ( - ); case 'techdocs': return ( - diff --git a/packages/app/src/components/techdocs/TechDocsPage.tsx b/packages/app/src/components/techdocs/TechDocsPage.tsx index be11e30e61..4f49cf38e8 100644 --- a/packages/app/src/components/techdocs/TechDocsPage.tsx +++ b/packages/app/src/components/techdocs/TechDocsPage.tsx @@ -16,18 +16,18 @@ import { Content } from '@backstage/core-components'; import { - TechDocsPageHeader, - TechDocsPage, + TechDocsReaderPageHeader, + TechDocsReaderPage, Reader, } from '@backstage/plugin-techdocs'; import React from 'react'; const DefaultTechDocsPage = () => { return ( - + {({ techdocsMetadataValue, entityMetadataValue, entityRef, onReady }) => ( <> - { )} - + ); }; diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 2e80c62c92..ce5c2f0894 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,48 @@ # @backstage/backend-common +## 0.11.0 + +### Minor Changes + +- 7d12e4cf32: feat(backend-common): add Redis backed cache store + +### Patch Changes + +- b2f8bb99d3: Make backend.auth.keys optional in config schema. Previously backend.auth was optional but keys was not, which meant that if another plugin introduced additional properties under backend.auth, it would implicitly make backend.auth.keys mandatory. +- d64b8d3678: chore(deps): bump `minimatch` from 3.0.4 to 5.0.0 +- Updated dependencies + - @backstage/config-loader@0.9.6 + - @backstage/integration@0.7.5 + +## 0.10.9 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/cli-common@0.1.8 + - @backstage/config@0.1.15 + - @backstage/config-loader@0.9.5 + - @backstage/errors@0.2.2 + - @backstage/integration@0.7.4 + - @backstage/types@0.1.3 + +## 0.10.8 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 0107c9aa08: chore(deps): bump `helmet` from 4.4.1 to 5.0.2 +- b590e9b58d: Updated `isDatabaseConflictError` to handle modern sqlite conflict errors +- Updated dependencies + - @backstage/config-loader@0.9.4 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/cli-common@0.1.7 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.10.7 ### Patch Changes diff --git a/packages/backend-common/config.d.ts b/packages/backend-common/config.d.ts index 16ca1698f6..57645d9fda 100644 --- a/packages/backend-common/config.d.ts +++ b/packages/backend-common/config.d.ts @@ -23,7 +23,7 @@ export interface Config { /** Backend configuration for when request authentication is enabled */ auth?: { /** Keys shared by all backends for signing and validating backend tokens. */ - keys: { + keys?: { /** * Secret for generating tokens. Should be a base64 string, recommended * length is 24 bytes. @@ -134,6 +134,14 @@ export interface Config { | { store: 'memory'; } + | { + store: 'redis'; + /** + * A redis connection string in the form `user:pass@host:port`. + * @secret + */ + connection: string; + } | { store: 'memcache'; /** diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 13a34a1d81..fd5ea443ab 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.10.7", + "version": "0.11.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -11,6 +11,9 @@ "types": "dist/index.d.ts", "alphaTypes": "dist/index.alpha.d.ts" }, + "backstage": { + "role": "node-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,20 +25,21 @@ ], "license": "Apache-2.0", "scripts": { - "build": "backstage-cli build --experimental-type-build --outputs cjs,types", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build --experimental-type-build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/cli-common": "^0.1.6", - "@backstage/config": "^0.1.13", - "@backstage/config-loader": "^0.9.3", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/types": "^0.1.1", + "@backstage/cli-common": "^0.1.8", + "@backstage/config": "^0.1.15", + "@backstage/config-loader": "^0.9.6", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/types": "^0.1.3", "@google-cloud/storage": "^5.8.0", "@manypkg/get-packages": "^1.1.3", "@octokit/rest": "^18.5.3", @@ -53,16 +57,17 @@ "express-promise-router": "^4.1.0", "fs-extra": "9.1.0", "git-url-parse": "^11.6.0", - "helmet": "^4.0.0", + "helmet": "^5.0.2", "isomorphic-git": "^1.8.0", "jose": "^1.27.1", "keyv": "^4.0.3", "keyv-memcache": "^1.2.5", + "@keyv/redis": "^2.2.3", "knex": "^1.0.2", "lodash": "^4.17.21", "logform": "^2.3.2", "luxon": "^2.0.2", - "minimatch": "^3.0.4", + "minimatch": "^5.0.0", "minimist": "^1.2.5", "morgan": "^1.10.0", "node-abort-controller": "^3.0.1", @@ -84,8 +89,8 @@ } }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@types/archiver": "^5.1.0", "@types/compression": "^1.7.0", "@types/concat-stream": "^2.0.0", diff --git a/packages/backend-common/src/cache/CacheManager.test.ts b/packages/backend-common/src/cache/CacheManager.test.ts index ce5b6f5999..660aa82bb0 100644 --- a/packages/backend-common/src/cache/CacheManager.test.ts +++ b/packages/backend-common/src/cache/CacheManager.test.ts @@ -18,6 +18,8 @@ import { ConfigReader } from '@backstage/config'; import Keyv from 'keyv'; /* @ts-expect-error */ import KeyvMemcache from 'keyv-memcache'; +/* @ts-expect-error */ +import KeyvRedis from '@keyv/redis'; import { DefaultCacheClient } from './CacheClient'; import { CacheManager } from './CacheManager'; import { NoStore } from './NoStore'; @@ -26,6 +28,8 @@ jest.createMockFromModule('keyv'); jest.mock('keyv'); jest.createMockFromModule('keyv-memcache'); jest.mock('keyv-memcache'); +jest.createMockFromModule('@keyv/redis'); +jest.mock('@keyv/redis'); jest.mock('./CacheClient', () => { return { DefaultCacheClient: jest.fn(), @@ -190,6 +194,33 @@ describe('CacheManager', () => { }); }); + it('returns a Redis client when configured', () => { + const redisHostAndPort = '127.0.0.1:6379'; + const expectedHost = `redis://${redisHostAndPort}`; + const manager = CacheManager.fromConfig( + new ConfigReader({ + backend: { + cache: { + store: 'redis', + connection: redisHostAndPort, + }, + }, + }), + ); + const expectedTtl = 3600; + manager.forPlugin('test').getClient({ defaultTtl: expectedTtl }); + + const cache = Keyv as unknown as jest.Mock; + const mockCacheCalls = cache.mock.calls.splice(-1); + expect(mockCacheCalls[0][0]).toMatchObject({ + ttl: expectedTtl, + }); + expect(mockCacheCalls[0][0].store).toBeInstanceOf(KeyvRedis); + const redis = KeyvRedis as jest.Mock; + const mockRedisCalls = redis.mock.calls.splice(-1); + expect(mockRedisCalls[0][0]).toEqual(expectedHost); + }); + describe('connection errors', () => { it('uses provided logger', () => { // Set up and inject mock logger. diff --git a/packages/backend-common/src/cache/CacheManager.ts b/packages/backend-common/src/cache/CacheManager.ts index 66e9849e77..d63ac6d940 100644 --- a/packages/backend-common/src/cache/CacheManager.ts +++ b/packages/backend-common/src/cache/CacheManager.ts @@ -18,6 +18,8 @@ import { Config } from '@backstage/config'; import Keyv from 'keyv'; // @ts-expect-error import KeyvMemcache from 'keyv-memcache'; +// @ts-expect-error +import KeyvRedis from '@keyv/redis'; import { Logger } from 'winston'; import { getRootLogger } from '../logging'; import { DefaultCacheClient, CacheClient } from './CacheClient'; @@ -37,6 +39,7 @@ export class CacheManager { * that return Keyv instances appropriate to the store. */ private readonly storeFactories = { + redis: this.getRedisClient, memcache: this.getMemcacheClient, memory: this.getMemoryClient, none: this.getNoneClient, @@ -123,6 +126,17 @@ export class CacheManager { return this.storeFactories[this.store].call(this, pluginId, ttl); } + private getRedisClient( + pluginId: string, + defaultTtl: number | undefined, + ): Keyv { + return new Keyv({ + namespace: pluginId, + ttl: defaultTtl, + store: new KeyvRedis(`redis://${this.connection}`), + }); + } + private getMemcacheClient( pluginId: string, defaultTtl: number | undefined, diff --git a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts index d155287b34..105704159b 100644 --- a/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts +++ b/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts @@ -19,7 +19,7 @@ import compression from 'compression'; import cors from 'cors'; import express, { Router, ErrorRequestHandler } from 'express'; import helmet from 'helmet'; -import { ContentSecurityPolicyOptions } from 'helmet/dist/middlewares/content-security-policy'; +import { ContentSecurityPolicyOptions } from 'helmet/dist/types/middlewares/content-security-policy'; import * as http from 'http'; import stoppable from 'stoppable'; import { Logger } from 'winston'; @@ -215,8 +215,17 @@ export class ServiceBuilderImpl implements ServiceBuilder { httpsSettings: this.httpsSettings, helmetOptions: { contentSecurityPolicy: { + useDefaults: false, directives: applyCspDirectives(this.cspOptions), }, + // These are all disabled in order to maintain backwards compatibility + // when bumping helmet v5. We can't enable these by default because + // there is no way for users to configure them. + // TODO(Rugvip): We should give control of this setup to consumers + crossOriginEmbedderPolicy: false, + crossOriginOpenerPolicy: false, + crossOriginResourcePolicy: false, + originAgentCluster: false, }, }; } @@ -232,6 +241,11 @@ export function applyCspDirectives( // It should be replaced by any other solution that doesn't require unsafe-eval. result['script-src'] = ["'self'", "'unsafe-eval'"]; + // TODO(Rugvip): This is removed so that we maintained backwards compatibility + // when bumping to helmet v5, we could remove this as well as + // skip setting `useDefaults: false` in the future. + delete result['form-action']; + if (directives) { for (const [key, value] of Object.entries(directives)) { if (value === false) { diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index d611ee8ac1..546d4a8187 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/backend-tasks +## 0.1.9 + +### Patch Changes + +- dc97845422: Only output janitor logs when actually timing out tasks +- Updated dependencies + - @backstage/backend-common@0.11.0 + +## 0.1.8 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/types@0.1.3 + +## 0.1.7 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.1.6 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 364f1d1380..215fafe970 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.1.6", + "version": "0.1.9", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "node-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,18 +24,19 @@ ], "license": "Apache-2.0", "scripts": { - "build": "backstage-cli build --outputs cjs,types", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/types": "^0.1.1", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/types": "^0.1.3", "@types/luxon": "^2.0.4", "knex": "^1.0.2", "lodash": "^4.17.21", @@ -43,8 +47,8 @@ "zod": "^3.9.5" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.17", - "@backstage/cli": "^0.13.2", + "@backstage/backend-test-utils": "^0.1.19", + "@backstage/cli": "^0.14.1", "jest": "^26.0.1", "wait-for-expect": "^3.0.2" }, diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts index 8b90afff42..c77fe4e925 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerJanitor.ts @@ -62,19 +62,21 @@ export class PluginTaskSchedulerJanitor { const dbNull = this.knex.raw('null'); - const tasksQuery = this.knex(DB_TASKS_TABLE) + const tasks = await this.knex(DB_TASKS_TABLE) .where('current_run_expires_at', '<', this.knex.fn.now()) .update({ current_run_ticket: dbNull, current_run_started_at: dbNull, current_run_expires_at: dbNull, - }); + }) + .returning(['id']); - if (this.knex.client.config.client === 'sqlite3') { - const tasks = await tasksQuery; - this.logger.warn(`${tasks} tasks timed out and were lost`); + // sqlite ignores "returning", returns number of rows changed instead + if (typeof tasks === 'number') { + if (tasks > 0) { + this.logger.warn(`${tasks} tasks timed out and were lost`); + } } else { - const tasks = await tasksQuery.returning(['id']); for (const { id } of tasks) { this.logger.warn(`Task timed out and was lost: ${id}`); } diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 55fb3c94cd..03f564074b 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/backend-test-utils +## 0.1.19 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/cli@0.14.1 + +## 0.1.18 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/cli@0.14.0 + - @backstage/backend-common@0.10.8 + - @backstage/config@0.1.14 + ## 0.1.17 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 8a9767b292..2cc3b8a07b 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-test-utils", "description": "Test helpers library for Backstage backends", - "version": "0.1.17", + "version": "0.1.19", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "node-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,17 +25,18 @@ ], "license": "Apache-2.0", "scripts": { - "build": "backstage-cli build --outputs cjs,types", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/cli": "^0.13.2", - "@backstage/config": "^0.1.13", + "@backstage/backend-common": "^0.11.0", + "@backstage/cli": "^0.14.1", + "@backstage/config": "^0.1.15", "@vscode/sqlite3": "^5.0.7", "knex": "^1.0.2", "mysql2": "^2.2.5", @@ -41,7 +45,7 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "jest": "^26.0.1" }, "files": [ diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index d68ec80469..227b8ee231 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,120 @@ # example-backend +## 0.2.66 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-catalog-backend@0.22.0 + - @backstage/plugin-scaffolder-backend@0.17.0 + - @backstage/plugin-graphql-backend@0.1.17 + - @backstage/plugin-auth-backend@0.11.0 + - @backstage/plugin-kubernetes-backend@0.4.10 + - @backstage/plugin-code-coverage-backend@0.1.25 + - @backstage/plugin-jenkins-backend@0.1.16 + - @backstage/plugin-tech-insights-backend@0.2.7 + - @backstage/plugin-todo-backend@0.1.24 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/plugin-badges-backend@0.1.21 + - @backstage/backend-tasks@0.1.9 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.2 + - @backstage/plugin-techdocs-backend@0.14.0 + - @backstage/plugin-permission-node@0.5.2 + - @backstage/integration@0.7.5 + - example-app@0.2.66 + - @backstage/plugin-app-backend@0.3.27 + - @backstage/plugin-auth-node@0.1.3 + - @backstage/plugin-azure-devops-backend@0.3.6 + - @backstage/plugin-kafka-backend@0.2.20 + - @backstage/plugin-permission-backend@0.5.2 + - @backstage/plugin-proxy-backend@0.2.21 + - @backstage/plugin-rollbar-backend@0.1.24 + - @backstage/plugin-search-backend@0.4.5 + - @backstage/plugin-search-backend-module-pg@0.2.9 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.11 + - @backstage/plugin-tech-insights-node@0.2.5 + +## 0.2.66 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/backend-tasks@0.1.8 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/integration@0.7.4 + - @backstage/plugin-app-backend@0.3.26 + - @backstage/plugin-auth-backend@0.10.2 + - @backstage/plugin-auth-node@0.1.2 + - @backstage/plugin-azure-devops-backend@0.3.5 + - @backstage/plugin-badges-backend@0.1.20 + - @backstage/plugin-catalog-backend@0.21.5 + - @backstage/plugin-code-coverage-backend@0.1.24 + - @backstage/plugin-graphql-backend@0.1.16 + - @backstage/plugin-jenkins-backend@0.1.15 + - @backstage/plugin-kafka-backend@0.2.19 + - @backstage/plugin-kubernetes-backend@0.4.9 + - @backstage/plugin-permission-backend@0.5.1 + - @backstage/plugin-permission-common@0.5.1 + - @backstage/plugin-permission-node@0.5.1 + - @backstage/plugin-proxy-backend@0.2.20 + - @backstage/plugin-rollbar-backend@0.1.23 + - @backstage/plugin-scaffolder-backend@0.16.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.1 + - @backstage/plugin-search-backend@0.4.4 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.10 + - @backstage/plugin-search-backend-module-pg@0.2.8 + - @backstage/plugin-search-backend-node@0.4.7 + - @backstage/plugin-tech-insights-backend@0.2.6 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.10 + - @backstage/plugin-tech-insights-node@0.2.4 + - @backstage/plugin-techdocs-backend@0.13.5 + - @backstage/plugin-todo-backend@0.1.23 + +## 0.2.65 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-backend@0.13.4 + - @backstage/plugin-catalog-backend@0.21.4 + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/integration@0.7.3 + - @backstage/plugin-auth-backend@0.10.1 + - @backstage/plugin-auth-node@0.1.1 + - @backstage/plugin-permission-backend@0.5.0 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/plugin-rollbar-backend@0.1.22 + - @backstage/plugin-scaffolder-backend@0.16.0 + - @backstage/backend-tasks@0.1.7 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/plugin-app-backend@0.3.25 + - @backstage/plugin-azure-devops-backend@0.3.4 + - @backstage/plugin-badges-backend@0.1.19 + - @backstage/plugin-code-coverage-backend@0.1.23 + - @backstage/plugin-graphql-backend@0.1.15 + - @backstage/plugin-jenkins-backend@0.1.14 + - @backstage/plugin-kafka-backend@0.2.18 + - @backstage/plugin-kubernetes-backend@0.4.8 + - @backstage/plugin-permission-node@0.5.0 + - @backstage/plugin-proxy-backend@0.2.19 + - @backstage/plugin-scaffolder-backend-module-rails@0.3.0 + - @backstage/plugin-search-backend@0.4.3 + - @backstage/plugin-search-backend-module-elasticsearch@0.0.9 + - @backstage/plugin-search-backend-module-pg@0.2.7 + - @backstage/plugin-search-backend-node@0.4.6 + - @backstage/plugin-tech-insights-backend@0.2.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.9 + - @backstage/plugin-tech-insights-node@0.2.3 + - @backstage/plugin-todo-backend@0.1.22 + - example-app@0.2.65 + ## 0.2.64 ### Patch Changes diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index 31231a3a4a..dd35d4ddbc 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -9,7 +9,7 @@ # # Once the commands have been run, you can build the image using `yarn build-image` -FROM node:14-buster-slim +FROM node:16-bullseye-slim WORKDIR /app @@ -19,6 +19,12 @@ WORKDIR /app COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz +# install sqlite3 dependencies +RUN apt-get update && \ + apt-get install -y libsqlite3-dev python3 cmake g++ && \ + rm -rf /var/lib/apt/lists/* && \ + yarn config set python /usr/bin/python3 + RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" # Then copy the rest of the backend bundle, along with any other files we might want. diff --git a/packages/backend/package.json b/packages/backend/package.json index 35b234aeda..cc960e3c24 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,10 +1,13 @@ { "name": "example-backend", - "version": "0.2.64", + "version": "0.2.66", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", "private": true, + "backstage": { + "role": "backend" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -15,48 +18,48 @@ "backstage" ], "scripts": { - "build": "backstage-cli backend:bundle", + "build": "backstage-cli package build", "build-image": "docker build ../.. -f Dockerfile --tag example-backend", - "start": "backstage-cli backend:dev", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "clean": "backstage-cli clean", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/backend-tasks": "^0.1.6", - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/integration": "^0.7.2", - "@backstage/plugin-app-backend": "^0.3.24", - "@backstage/plugin-auth-backend": "^0.10.0", - "@backstage/plugin-auth-node": "^0.1.0", - "@backstage/plugin-azure-devops-backend": "^0.3.3", - "@backstage/plugin-badges-backend": "^0.1.18", - "@backstage/plugin-catalog-backend": "^0.21.3", - "@backstage/plugin-code-coverage-backend": "^0.1.22", - "@backstage/plugin-graphql-backend": "^0.1.14", - "@backstage/plugin-jenkins-backend": "^0.1.13", - "@backstage/plugin-kubernetes-backend": "^0.4.7", - "@backstage/plugin-kafka-backend": "^0.2.17", - "@backstage/plugin-permission-backend": "^0.4.3", - "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/plugin-permission-node": "^0.4.3", - "@backstage/plugin-proxy-backend": "^0.2.18", - "@backstage/plugin-rollbar-backend": "^0.1.21", - "@backstage/plugin-scaffolder-backend": "^0.15.24", - "@backstage/plugin-scaffolder-backend-module-rails": "^0.2.6", - "@backstage/plugin-search-backend": "^0.4.2", - "@backstage/plugin-search-backend-node": "^0.4.5", - "@backstage/plugin-search-backend-module-elasticsearch": "^0.0.8", - "@backstage/plugin-search-backend-module-pg": "^0.2.6", - "@backstage/plugin-techdocs-backend": "^0.13.3", - "@backstage/plugin-tech-insights-backend": "^0.2.4", - "@backstage/plugin-tech-insights-node": "^0.2.2", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.8", - "@backstage/plugin-todo-backend": "^0.1.21", + "@backstage/backend-common": "^0.11.0", + "@backstage/backend-tasks": "^0.1.9", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/integration": "^0.7.5", + "@backstage/plugin-app-backend": "^0.3.27", + "@backstage/plugin-auth-backend": "^0.11.0", + "@backstage/plugin-auth-node": "^0.1.3", + "@backstage/plugin-azure-devops-backend": "^0.3.6", + "@backstage/plugin-badges-backend": "^0.1.21", + "@backstage/plugin-catalog-backend": "^0.22.0", + "@backstage/plugin-code-coverage-backend": "^0.1.25", + "@backstage/plugin-graphql-backend": "^0.1.17", + "@backstage/plugin-jenkins-backend": "^0.1.16", + "@backstage/plugin-kubernetes-backend": "^0.4.10", + "@backstage/plugin-kafka-backend": "^0.2.20", + "@backstage/plugin-permission-backend": "^0.5.2", + "@backstage/plugin-permission-common": "^0.5.1", + "@backstage/plugin-permission-node": "^0.5.2", + "@backstage/plugin-proxy-backend": "^0.2.21", + "@backstage/plugin-rollbar-backend": "^0.1.24", + "@backstage/plugin-scaffolder-backend": "^0.17.0", + "@backstage/plugin-scaffolder-backend-module-rails": "^0.3.2", + "@backstage/plugin-search-backend": "^0.4.5", + "@backstage/plugin-search-backend-node": "^0.4.7", + "@backstage/plugin-search-backend-module-elasticsearch": "^0.0.10", + "@backstage/plugin-search-backend-module-pg": "^0.2.9", + "@backstage/plugin-techdocs-backend": "^0.14.0", + "@backstage/plugin-tech-insights-backend": "^0.2.7", + "@backstage/plugin-tech-insights-node": "^0.2.5", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.11", + "@backstage/plugin-todo-backend": "^0.1.24", "@gitbeaker/node": "^35.1.0", "@octokit/rest": "^18.5.3", "@vscode/sqlite3": "^5.0.7", @@ -73,7 +76,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5" diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 664e7a49ed..93f248ff0e 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,67 @@ # @backstage/catalog-client +## 0.7.2 + +### Patch Changes + +- ed09ad8093: Updated usage of the `LocationSpec` type from `@backstage/catalog-model`, which is deprecated. +- 46dee04eba: Deprecated `AddLocationRequest.presence`, as it is already being ignored. +- 6e1cbc12a6: Added `CatalogApi.getEntityFacets`. Marking this as a breaking change since it + is a non-optional addition to the API and depends on the backend being in place. + If you are mocking this interface in your tests, you will need to add an extra + `getEntityFacets: jest.fn()` or similar to that interface. +- Updated dependencies + - @backstage/catalog-model@0.11.0 + +## 0.7.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/catalog-model@0.10.1 + - @backstage/errors@0.2.2 + +## 0.7.0 + +### Minor Changes + +- 8eda0e7a9c: **BREAKING**: Removed the explicit `DiscoveryApi` and `FetchApi` export symbols, + which were unnecessary duplicates from the well known core ones. + + The `CATALOG_FILTER_EXISTS` symbol's value has changed. However, this should not + affect any code in practice. + +- deaf6065db: Removed `CatalogApi.geLocationByEntity` and `CatalogApi.getOriginLocationByEntity`, and replaced them with `CatalogApi.getLocationByRef`. + + If you were using one of the two old methods, you can update your code as follows: + + ```diff + -const originLocation = catalogApi.getOriginLocationByEntity(entity); + +const originLocation = catalogApi.getLocationByRef(entity.metadata.annotations[ANNOTATION_ORIGIN_LOCATION]!); + -const location = catalogApi.getLocationByEntity(entity); + +const location = catalogApi.getLocationByRef(entity.metadata.annotations[ANNOTATION_LOCATION]!); + ``` + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 216725b434: Updated to use new names for `parseLocationRef` and `stringifyLocationRef` +- 244d24ebc4: Export the `Location` type that was previously exported by the `@backstage/catalog-model` package. +- 538ca90790: Deprecated the following types used by the catalog client, and created new + corresponding types to make them more consistent: + + - `CatalogEntitiesRequest` -> `GetEntitiesRequest` + - `CatalogListResponse` was removed and generally replaced with `GetEntitiesResponse` (which does not use a type parameter argument) + - `CatalogEntityAncestorsRequest`-> `GetEntityAncestorsRequest` + - `CatalogEntityAncestorsResponse` -> `GetEntityAncestorsResponse` + +- 27eccab216: Replaces use of deprecated catalog-model constants. +- Updated dependencies + - @backstage/errors@0.2.1 + - @backstage/catalog-model@0.10.0 + ## 0.6.0 ### Minor Changes diff --git a/packages/catalog-client/api-report.md b/packages/catalog-client/api-report.md index 090a1dbcc4..c957859fec 100644 --- a/packages/catalog-client/api-report.md +++ b/packages/catalog-client/api-report.md @@ -5,7 +5,6 @@ ```ts import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; -import { LocationSpec } from '@backstage/catalog-model'; // @public export type AddLocationRequest = { @@ -43,16 +42,16 @@ export interface CatalogApi { name: EntityName, options?: CatalogRequestOptions, ): Promise; - getLocationByEntity( - entity: Entity, + getEntityFacets( + request: GetEntityFacetsRequest, options?: CatalogRequestOptions, - ): Promise; + ): Promise; getLocationById( id: string, options?: CatalogRequestOptions, ): Promise; - getOriginLocationByEntity( - entity: Entity, + getLocationByRef( + locationRef: string, options?: CatalogRequestOptions, ): Promise; refreshEntity( @@ -95,6 +94,11 @@ export class CatalogClient implements CatalogApi { compoundName: EntityName, options?: CatalogRequestOptions, ): Promise; + getEntityFacets( + request: GetEntityFacetsRequest, + options?: CatalogRequestOptions, + ): Promise; + // @deprecated (undocumented) getLocationByEntity( entity: Entity, options?: CatalogRequestOptions, @@ -103,6 +107,11 @@ export class CatalogClient implements CatalogApi { id: string, options?: CatalogRequestOptions, ): Promise; + getLocationByRef( + locationRef: string, + options?: CatalogRequestOptions, + ): Promise; + // @deprecated (undocumented) getOriginLocationByEntity( entity: Entity, options?: CatalogRequestOptions, @@ -178,9 +187,32 @@ export interface GetEntityAncestorsResponse { rootEntityRef: string; } +// @public +export interface GetEntityFacetsRequest { + facets: string[]; + filter?: + | Record[] + | Record + | undefined; +} + +// @public +export interface GetEntityFacetsResponse { + facets: Record< + string, + Array<{ + value: string; + count: number; + }> + >; +} + // @public type Location_2 = { id: string; -} & LocationSpec; + type: string; + target: string; + presence?: 'optional' | 'required'; +}; export { Location_2 as Location }; ``` diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 3a45a74680..ffcfbc16b7 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-client", "description": "An isomorphic client for the catalog backend", - "version": "0.6.0", + "version": "0.7.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -12,6 +12,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,20 +25,20 @@ "backstage" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/errors": "^0.2.0", + "@backstage/catalog-model": "^0.11.0", + "@backstage/errors": "^0.2.2", "cross-fetch": "^3.1.5" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/jest": "^26.0.7", "msw": "^0.35.0" }, diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index bcf8750658..bb3c9604b3 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -36,6 +36,8 @@ import { GetEntityAncestorsRequest, GetEntityAncestorsResponse, Location, + GetEntityFacetsRequest, + GetEntityFacetsResponse, } from './types/api'; import { DiscoveryApi } from './types/discovery'; import { FetchApi } from './types/fetch'; @@ -97,14 +99,13 @@ export class CatalogClient implements CatalogApi { options?: CatalogRequestOptions, ): Promise { const { filter = [], fields = [], offset, limit, after } = request ?? {}; - const filterItems = [filter].flat(); const params: string[] = []; // filter param can occur multiple times, for example // /api/catalog/entities?filter=metadata.name=wayback-search,kind=component&filter=metadata.name=www-artist,kind=component' // the "outer array" defined by `filter` occurrences corresponds to "anyOf" filters // the "inner array" defined within a `filter` param corresponds to "allOf" filters - for (const filterItem of filterItems) { + for (const filterItem of [filter].flat()) { const filterParts: string[] = []; for (const [key, value] of Object.entries(filterItem)) { for (const v of [value].flat()) { @@ -207,6 +208,47 @@ export class CatalogClient implements CatalogApi { } } + /** + * {@inheritdoc CatalogApi.getEntityFacets} + */ + async getEntityFacets( + request: GetEntityFacetsRequest, + options?: CatalogRequestOptions, + ): Promise { + const { filter = [], facets } = request; + const params: string[] = []; + + // filter param can occur multiple times, for example + // /api/catalog/entities?filter=metadata.name=wayback-search,kind=component&filter=metadata.name=www-artist,kind=component' + // the "outer array" defined by `filter` occurrences corresponds to "anyOf" filters + // the "inner array" defined within a `filter` param corresponds to "allOf" filters + for (const filterItem of [filter].flat()) { + const filterParts: string[] = []; + for (const [key, value] of Object.entries(filterItem)) { + for (const v of [value].flat()) { + if (v === CATALOG_FILTER_EXISTS) { + filterParts.push(encodeURIComponent(key)); + } else if (typeof v === 'string') { + filterParts.push( + `${encodeURIComponent(key)}=${encodeURIComponent(v)}`, + ); + } + } + } + + if (filterParts.length) { + params.push(`filter=${filterParts.join(',')}`); + } + } + + for (const facet of facets) { + params.push(`facet=${encodeURIComponent(facet)}`); + } + + const query = params.length ? `?${params.join('&')}` : ''; + return await this.requestOptional('GET', `/entity-facets${query}`, options); + } + /** * {@inheritdoc CatalogApi.addLocation} */ @@ -246,7 +288,7 @@ export class CatalogClient implements CatalogApi { } /** - * {@inheritdoc CatalogApi.getOriginLocationByEntity} + * @deprecated please use getLocationByRef instead */ async getOriginLocationByEntity( entity: Entity, @@ -268,7 +310,7 @@ export class CatalogClient implements CatalogApi { } /** - * {@inheritdoc CatalogApi.getLocationByEntity} + * @deprecated please use getLocationByRef instead */ async getLocationByEntity( entity: Entity, @@ -288,6 +330,23 @@ export class CatalogClient implements CatalogApi { .find(l => locationCompound === stringifyLocationRef(l)); } + /** + * {@inheritdoc CatalogApi.getLocationByRef} + */ + async getLocationByRef( + locationRef: string, + options?: CatalogRequestOptions, + ): Promise { + const all: { data: Location }[] = await this.requestRequired( + 'GET', + '/locations', + options, + ); + return all + .map(r => r.data) + .find(l => locationRef === stringifyLocationRef(l)); + } + /** * {@inheritdoc CatalogApi.removeLocationById} */ diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index bf5a7327c6..4a86ca41f3 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity, EntityName, LocationSpec } from '@backstage/catalog-model'; +import { Entity, EntityName } from '@backstage/catalog-model'; /** * This symbol can be used in place of a value when passed to filters in e.g. @@ -142,6 +142,90 @@ export interface GetEntityAncestorsResponse { }>; } +/** + * The request type for {@link CatalogClient.getEntityFacets}. + * + * @public + */ +export interface GetEntityFacetsRequest { + /** + * If given, return only entities that match the given patterns. + * + * @remarks + * + * If multiple filter sets are given as an array, then there is effectively an + * OR between each filter set. + * + * Within one filter set, there is effectively an AND between the various + * keys. + * + * Within one key, if there are more than one value, then there is effectively + * an OR between them. + * + * Example: For an input of + * + * ``` + * [ + * { kind: ['API', 'Component'] }, + * { 'metadata.name': 'a', 'metadata.namespace': 'b' } + * ] + * ``` + * + * This effectively means + * + * ``` + * (kind = EITHER 'API' OR 'Component') + * OR + * (metadata.name = 'a' AND metadata.namespace = 'b' ) + * ``` + * + * Each key is a dot separated path in each object. + * + * As a value you can also pass in the symbol `CATALOG_FILTER_EXISTS` + * (exported from this package), which means that you assert on the existence + * of that key, no matter what its value is. + */ + filter?: + | Record[] + | Record + | undefined; + /** + * Dot separated paths for the facets to extract from each entity. + * + * @remarks + * + * Example: For an input of `['kind', 'metadata.annotations.backstage.io/orphan']`, then the + * response will be shaped like + * + * ``` + * { + * "facets": { + * "kind": [ + * { "key": "Component", "count": 22 }, + * { "key": "API", "count": 13 } + * ], + * "metadata.annotations.backstage.io/orphan": [ + * { "key": "true", "count": 2 } + * ] + * } + * } + * ``` + */ + facets: string[]; +} + +/** + * The response type for {@link CatalogClient.getEntityFacets}. + * + * @public + */ +export interface GetEntityFacetsResponse { + /** + * The computed facets, one entry per facet in the request. + */ + facets: Record>; +} + /** * Options you can pass into a catalog request for additional information. * @@ -158,7 +242,11 @@ export interface CatalogRequestOptions { */ export type Location = { id: string; -} & LocationSpec; + type: string; + target: string; + /** @deprecated This field is is ignored */ + presence?: 'optional' | 'required'; +}; /** * The request type for {@link CatalogClient.addLocation}. @@ -169,6 +257,7 @@ export type AddLocationRequest = { type?: string; target: string; dryRun?: boolean; + /** @deprecated This field is is ignored */ presence?: 'optional' | 'required'; }; @@ -248,6 +337,17 @@ export interface CatalogApi { options?: CatalogRequestOptions, ): Promise; + /** + * Gets a summary of field facets of entities in the catalog. + * + * @param request - Request parameters + * @param options - Additional options + */ + getEntityFacets( + request: GetEntityFacetsRequest, + options?: CatalogRequestOptions, + ): Promise; + // Locations /** @@ -262,24 +362,13 @@ export interface CatalogApi { ): Promise; /** - * Gets origin location by Entity. + * Gets a registered location by its ref. * - * @param entity - An {@link catalog-model#Entity}. + * @param locationRef - A location ref, e.g. "url:https://github.com/..." * @param options - Additional options */ - getOriginLocationByEntity( - entity: Entity, - options?: CatalogRequestOptions, - ): Promise; - - /** - * Gets Location by Entity. - * - * @param entity - An {@link catalog-model#Entity}. - * @param options - Additional options - */ - getLocationByEntity( - entity: Entity, + getLocationByRef( + locationRef: string, options?: CatalogRequestOptions, ): Promise; diff --git a/packages/catalog-client/src/types/index.ts b/packages/catalog-client/src/types/index.ts index 580bb7330d..39c8962b68 100644 --- a/packages/catalog-client/src/types/index.ts +++ b/packages/catalog-client/src/types/index.ts @@ -25,6 +25,8 @@ export type { GetEntityAncestorsRequest, GetEntityAncestorsResponse, Location, + GetEntityFacetsRequest, + GetEntityFacetsResponse, } from './api'; -export { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from './status'; export * from './deprecated'; +export { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from './status'; diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 9dbf2f116b..cbb2e681bf 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,86 @@ # @backstage/catalog-model +## 0.11.0 + +### Minor Changes + +- 919cf2f836: **BREAKING**: Added `EntityRelation.targetRef` (a string form entity ref), and + deprecated `EntityRelation.target` (a kind + namespace + name object). This + aligns the type with our goal of using string form entity refs more coherently. + There will be a period of time when both fields need to be present, which may in + particular affect your tests which now have to list both the string form and the + object form side by side. + + ```diff + const mockEntity: Entity = { + kind: 'Component', + relations: [ + { + type: RELATION_MEMBER_OF, + + targetRef: 'component:default/foo', + target: { kind: 'component', namespace: 'default', name: 'foo' } + } + ] + ``` + + The `target` field will be entirely removed from this type in a future release. + +- 7010349c9a: **BREAKING**: Removed `EntityRelationSpec` as it is only used during the catalog during the catalog processing. +- 68f0871b76: **BREAKING**: Simplified the `parseEntityRef` function to _always_ either return + a complete `EntityName`, complete with both kind, namespace and name, or throw + an error if it for some reason did not have enough information to form that + result. This makes its usage and its type declaration vastly simpler. + +### Patch Changes + +- ed09ad8093: Deprecated the `LocationSpec` export, which should now be imported from `@backstage/plugin-catalog-backend` instead. +- b5c02d10a6: Remove all usage of the deprecated `ENTITY_DEFAULT_NAMESPACE` +- 67a7c02d26: **DEPRECATED**: The `EntityRef` type and `parseEntityName` function are now + deprecated, and will soon be removed. This is part of a larger movement toward + fixing the poorly named `EntityName` type which should instead have been named + `EntityRef`. Please remove any usage of these as soon as possible. +- da44c47f43: Deprecated the `entity.metadata.generation` as the field has never been fully functioning. + +## 0.10.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/types@0.1.3 + +## 0.10.0 + +### Minor Changes + +- e483dd6c72: **BREAKING**: Remove deprecated validation methods `analyzeLocationSchema`, `locationSchema` and `locationSpecSchema`. + This functionality was primarily used internally by the `catalog-backend`. +- edbc03814a: **BREAKING**: Remove deprecated `serializeEntityRef` which is replaced by `stringifyEntityRef`. +- e72d371296: **BREAKING**: Removed `TemplateEntityV1beta2` from the model and moved it to + `@backstage/plugin-scaffolder-common` where `TemplateEntityV1beta3` already + lived. It has also been marked as deprecated in the process - please consider + [migrating to `v1beta3` templates](https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3). + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 216725b434: Deprecated `parseLocationReference` and `stringifyLocationReference`, + introducing `parseLocationRef` and `stringifyLocationRef` in their place. +- 244d24ebc4: Deprecates the `Location` type export from this package. Imports of the `Location` type should now be done from the `@backstage/catalog-client` package instead. +- 7aeb491394: Deprecated `ENTITY_DEFAULT_NAMESPACE` constant in favour of `DEFAULT_NAMESPACE`. +- 0d03e42a89: Deprecated `compareEntityToRef` due to low usage and provided value. +- e8c6f9d282: Deprecated the `EntityRefContext` type which had limited use. +- 7f21538c9e: Deprecated `JSONSchema` export type. +- 20f3244102: Deprecates the following exports which all have very limited and internal use. `ENTITY_META_GENERATED_FIELDS`, `entityHasChanges`, `generateEntityEtag`, `generateEntityUid`, `generateUpdatedEntity`, `ENTITY_META_GENERATED_FIELDS`. +- 27eccab216: Deprecates `EDIT_URL_ANNOTATION`, `LOCATION_ANNOTATION`, `ORIGIN_LOCATION_ANNOTATION`, `SOURCE_LOCATION_ANNOTATION`, `VIEW_URL_ANNOTATION` + and replaces these constants all prefixed with ANNOTATION\_ `ANNOTATION_EDIT_URL`, `ANNOTATION_LOCATION`, `ANNOTATION_ORIGIN_LOCATION`, `ANNOTATION_SOURCE_LOCATION`, `ANNOTATION_VIEW_URL` +- Updated dependencies + - @backstage/errors@0.2.1 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.9.10 ### Patch Changes diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md index 10ef63388b..210d0187e2 100644 --- a/packages/catalog-model/api-report.md +++ b/packages/catalog-model/api-report.md @@ -69,7 +69,14 @@ export class CommonValidatorFunctions { // @public @deprecated export function compareEntityToRef( entity: Entity, - ref: EntityRef | EntityName, + ref: + | string + | { + kind?: string; + namespace?: string; + name: string; + } + | EntityName, context?: { defaultKind?: string; defaultNamespace?: string; @@ -212,7 +219,7 @@ export type EntityPolicy = { enforce(entity: Entity): Promise; }; -// @public +// @public @deprecated export type EntityRef = | string | { @@ -231,13 +238,7 @@ export type EntityRefContext = { export type EntityRelation = { type: string; target: EntityName; -}; - -// @public -export type EntityRelationSpec = { - source: EntityName; - type: string; - target: EntityName; + targetRef: string; }; // @public @@ -311,7 +312,7 @@ export { GroupEntityV1alpha1 }; // @public export const groupEntityV1alpha1Validator: KindValidator; -// @public +// @public @deprecated export type JSONSchema = JSONSchema7 & { [key in string]?: JsonValue; }; @@ -370,7 +371,7 @@ export { LocationEntityV1alpha1 }; // @public export const locationEntityV1alpha1Validator: KindValidator; -// @public +// @public @deprecated export type LocationSpec = { type: string; target: string; @@ -391,9 +392,15 @@ export class NoForeignRootFieldsEntityPolicy implements EntityPolicy { export const ORIGIN_LOCATION_ANNOTATION = 'backstage.io/managed-by-origin-location'; -// @public +// @public @deprecated export function parseEntityName( - ref: EntityRef, + ref: + | string + | { + kind?: string; + namespace?: string; + name: string; + }, context?: { defaultKind?: string; defaultNamespace?: string; @@ -402,37 +409,19 @@ export function parseEntityName( // @public export function parseEntityRef( - ref: EntityRef, + ref: + | string + | { + kind?: string; + namespace?: string; + name: string; + }, context?: { - defaultKind: string; - defaultNamespace: string; + defaultKind?: string; + defaultNamespace?: string; }, ): EntityName; -// @public -export function parseEntityRef( - ref: EntityRef, - context?: { - defaultKind: string; - }, -): { - kind: string; - namespace?: string; - name: string; -}; - -// @public -export function parseEntityRef( - ref: EntityRef, - context?: { - defaultNamespace: string; - }, -): { - kind?: string; - namespace: string; - name: string; -}; - // @public export function parseLocationRef(ref: string): { type: string; @@ -558,33 +547,6 @@ export { SystemEntityV1alpha1 }; // @public export const systemEntityV1alpha1Validator: KindValidator; -// @public -export interface TemplateEntityV1beta2 extends Entity { - // (undocumented) - apiVersion: 'backstage.io/v1beta2'; - // (undocumented) - kind: 'Template'; - // (undocumented) - spec: { - type: string; - parameters?: JsonObject | JsonObject[]; - steps: Array<{ - id?: string; - name?: string; - action: string; - input?: JsonObject; - if?: string | boolean; - }>; - output?: { - [name: string]: string; - }; - owner?: string; - }; -} - -// @public -export const templateEntityV1beta2Validator: KindValidator; - // @public interface UserEntityV1alpha1 extends Entity { // (undocumented) diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 62e577df4a..70ed0fbe4d 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-model", "description": "Types and validators that help describe the model of a Backstage Catalog", - "version": "0.9.10", + "version": "0.11.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -13,6 +13,9 @@ "types": "dist/index.d.ts", "alphaTypes": "dist/index.alpha.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -23,17 +26,17 @@ "backstage" ], "scripts": { - "build": "backstage-cli build --experimental-type-build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build --experimental-type-build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/types": "^0.1.1", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/types": "^0.1.3", "@types/json-schema": "^7.0.5", "ajv": "^7.0.3", "json-schema": "^0.4.0", @@ -41,7 +44,7 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", + "@backstage/cli": "^0.14.1", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", "yaml": "^1.9.2" diff --git a/packages/catalog-model/src/entity/Entity.ts b/packages/catalog-model/src/entity/Entity.ts index f56774025c..ef9b4352da 100644 --- a/packages/catalog-model/src/entity/Entity.ts +++ b/packages/catalog-model/src/entity/Entity.ts @@ -116,6 +116,7 @@ export type EntityMeta = JsonObject & { * This field can not be set by the user at creation time, and the server * will reject an attempt to do so. The field will be populated in read * operations. + * @deprecated field is not supported. */ generation?: number; @@ -197,30 +198,15 @@ export type EntityRelation = { /** * The target entity of this relation. + * + * @deprecated use targetRef instead */ target: EntityName; -}; - -/** - * Holds the relation data for entities. - * - * @public - */ -export type EntityRelationSpec = { - /** - * The source entity of this relation. - */ - source: EntityName; /** - * The type of the relation. + * The entity ref of the target of this relation. */ - type: string; - - /** - * The target entity of this relation. - */ - target: EntityName; + targetRef: string; }; /** diff --git a/packages/catalog-model/src/entity/index.ts b/packages/catalog-model/src/entity/index.ts index 938bdb2b9b..f619d0f39e 100644 --- a/packages/catalog-model/src/entity/index.ts +++ b/packages/catalog-model/src/entity/index.ts @@ -29,7 +29,6 @@ export type { EntityLink, EntityMeta, EntityRelation, - EntityRelationSpec, } from './Entity'; export type { EntityEnvelope } from './EntityEnvelope'; export type { diff --git a/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.test.ts b/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.test.ts index 55188d84c8..b92abb0fc2 100644 --- a/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.test.ts +++ b/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.test.ts @@ -15,7 +15,7 @@ */ import yaml from 'yaml'; -import { ENTITY_DEFAULT_NAMESPACE } from '../constants'; +import { DEFAULT_NAMESPACE } from '../constants'; import { DefaultNamespaceEntityPolicy } from './DefaultNamespaceEntityPolicy'; describe('DefaultNamespaceEntityPolicy', () => { @@ -57,7 +57,7 @@ describe('DefaultNamespaceEntityPolicy', () => { expect.objectContaining({ metadata: { name: 'my-component-yay', - namespace: ENTITY_DEFAULT_NAMESPACE, + namespace: DEFAULT_NAMESPACE, }, }), ); diff --git a/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.ts b/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.ts index 9511dfb05f..5f6efc03dc 100644 --- a/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.ts +++ b/packages/catalog-model/src/entity/policies/DefaultNamespaceEntityPolicy.ts @@ -16,7 +16,7 @@ import lodash from 'lodash'; import { EntityPolicy } from './types'; -import { ENTITY_DEFAULT_NAMESPACE } from '../constants'; +import { DEFAULT_NAMESPACE } from '../constants'; import { Entity } from '../Entity'; /** @@ -27,7 +27,7 @@ import { Entity } from '../Entity'; export class DefaultNamespaceEntityPolicy implements EntityPolicy { private readonly namespace: string; - constructor(namespace: string = ENTITY_DEFAULT_NAMESPACE) { + constructor(namespace: string = DEFAULT_NAMESPACE) { this.namespace = namespace; } diff --git a/packages/catalog-model/src/entity/ref.test.ts b/packages/catalog-model/src/entity/ref.test.ts index f80b588d55..b0c7068fae 100644 --- a/packages/catalog-model/src/entity/ref.test.ts +++ b/packages/catalog-model/src/entity/ref.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ENTITY_DEFAULT_NAMESPACE } from './constants'; +import { DEFAULT_NAMESPACE } from './constants'; import { Entity } from './Entity'; import { compareEntityToRef, parseEntityName, parseEntityRef } from './ref'; @@ -29,7 +29,7 @@ describe('ref', () => { expect(() => parseEntityName('b/c')).toThrow(/kind/); expect(parseEntityName('a:c')).toEqual({ kind: 'a', - namespace: ENTITY_DEFAULT_NAMESPACE, + namespace: DEFAULT_NAMESPACE, name: 'c', }); expect(() => parseEntityName('c')).toThrow(/kind/); @@ -118,7 +118,7 @@ describe('ref', () => { ).toEqual({ kind: 'a', namespace: 'y', name: 'c' }); expect(parseEntityName('a:c', { defaultKind: 'x' })).toEqual({ kind: 'a', - namespace: ENTITY_DEFAULT_NAMESPACE, + namespace: DEFAULT_NAMESPACE, name: 'c', }); expect( @@ -126,7 +126,7 @@ describe('ref', () => { ).toEqual({ kind: 'x', namespace: 'y', name: 'c' }); expect(parseEntityName('c', { defaultKind: 'x' })).toEqual({ kind: 'x', - namespace: ENTITY_DEFAULT_NAMESPACE, + namespace: DEFAULT_NAMESPACE, name: 'c', }); }); @@ -152,7 +152,7 @@ describe('ref', () => { ).toEqual({ kind: 'a', namespace: 'y', name: 'c' }); expect( parseEntityName({ kind: 'a', name: 'c' }, { defaultKind: 'x' }), - ).toEqual({ kind: 'a', namespace: ENTITY_DEFAULT_NAMESPACE, name: 'c' }); + ).toEqual({ kind: 'a', namespace: DEFAULT_NAMESPACE, name: 'c' }); expect( parseEntityName( { name: 'c' }, @@ -161,7 +161,7 @@ describe('ref', () => { ).toEqual({ kind: 'x', namespace: 'y', name: 'c' }); expect(parseEntityName({ name: 'c' }, { defaultKind: 'x' })).toEqual({ kind: 'x', - namespace: ENTITY_DEFAULT_NAMESPACE, + namespace: DEFAULT_NAMESPACE, name: 'c', }); // empty strings are errors, not defaults @@ -187,19 +187,9 @@ describe('ref', () => { namespace: 'b', name: 'c', }); - expect(parseEntityRef('b/c')).toEqual({ - kind: undefined, - namespace: 'b', - name: 'c', - }); expect(parseEntityRef('a:c')).toEqual({ kind: 'a', - namespace: undefined, - name: 'c', - }); - expect(parseEntityRef('c')).toEqual({ - kind: undefined, - namespace: undefined, + namespace: 'default', name: 'c', }); }); diff --git a/packages/catalog-model/src/entity/ref.ts b/packages/catalog-model/src/entity/ref.ts index 122fe0b31a..66974f5a01 100644 --- a/packages/catalog-model/src/entity/ref.ts +++ b/packages/catalog-model/src/entity/ref.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { EntityName, EntityRef } from '../types'; -import { ENTITY_DEFAULT_NAMESPACE } from './constants'; +import { DEFAULT_NAMESPACE } from './constants'; +import { EntityName } from '../types'; import { Entity } from './Entity'; function parseRefString(ref: string): { @@ -48,7 +48,7 @@ function parseRefString(ref: string): { export function getEntityName(entity: Entity): EntityName { return { kind: entity.kind, - namespace: entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE, + namespace: entity.metadata.namespace || DEFAULT_NAMESPACE, name: entity.metadata.name, }; } @@ -76,13 +76,14 @@ export type EntityRefContext = { * otherwise specified as part of the options, and will throw an error if no * kind was specified in the input reference and no default kind was given. * + * @deprecated Please use parseEntityRef instead * @public * @param ref - The reference to parse * @param context - The context of defaults that the parsing happens within * @returns A complete entity name */ export function parseEntityName( - ref: EntityRef, + ref: string | { kind?: string; namespace?: string; name: string }, context: { /** The default kind, if none is given in the reference */ defaultKind?: string; @@ -91,7 +92,7 @@ export function parseEntityName( } = {}, ): EntityName { const { kind, namespace, name } = parseEntityRef(ref, { - defaultNamespace: ENTITY_DEFAULT_NAMESPACE, + defaultNamespace: DEFAULT_NAMESPACE, ...context, }); @@ -119,80 +120,52 @@ export function parseEntityName( * @returns The compound form of the reference */ export function parseEntityRef( - ref: EntityRef, - context?: { defaultKind: string; defaultNamespace: string }, -): EntityName; -/** - * parseEntityRef with optional Kind. - * - * @public - */ -export function parseEntityRef( - ref: EntityRef, - context?: { defaultKind: string }, -): { - kind: string; - namespace?: string; - name: string; -}; -/** - * parseEntityRef with optional Namespace. - * - * @public - */ -export function parseEntityRef( - ref: EntityRef, - context?: { defaultNamespace: string }, -): { - kind?: string; - namespace: string; - name: string; -}; -/** - * parseEntityRef with optional Kind and Namespace. - * - * @public - */ -export function parseEntityRef( - ref: EntityRef, - context: { + ref: string | { kind?: string; namespace?: string; name: string }, + context?: { /** The default kind, if none is given in the reference */ defaultKind?: string; /** The default namespace, if none is given in the reference */ defaultNamespace?: string; - } = {}, -): { - kind?: string; - namespace?: string; - name: string; -} { + }, +): EntityName { if (!ref) { throw new Error(`Entity reference must not be empty`); } + const defaultKind = context?.defaultKind; + const defaultNamespace = context?.defaultNamespace || DEFAULT_NAMESPACE; + + let kind: string | undefined; + let namespace: string | undefined; + let name: string | undefined; + if (typeof ref === 'string') { const parsed = parseRefString(ref); - return { - kind: parsed.kind ?? context.defaultKind, - namespace: parsed.namespace ?? context.defaultNamespace, - name: parsed.name, - }; + kind = parsed.kind ?? defaultKind; + namespace = parsed.namespace ?? defaultNamespace; + name = parsed.name; + } else { + kind = ref.kind ?? defaultKind; + namespace = ref.namespace ?? defaultNamespace; + name = ref.name; } - const { kind, namespace, name } = ref; - if (kind === '') { - throw new Error('Entity reference kinds must not be empty'); - } else if (namespace === '') { - throw new Error('Entity reference namespaces must not be empty'); + if (!kind) { + const textual = JSON.stringify(ref); + throw new Error( + `Entity reference ${textual} had missing or empty kind (e.g. did not start with "component:" or similar)`, + ); + } else if (!namespace) { + const textual = JSON.stringify(ref); + throw new Error( + `Entity reference ${textual} had missing or empty namespace`, + ); } else if (!name) { - throw new Error('Entity references must contain a name'); + const textual = JSON.stringify(ref); + throw new Error(`Entity reference ${textual} had missing or empty name`); } - return { - kind: kind ?? context.defaultKind, - namespace: namespace ?? context.defaultNamespace, - name, - }; + return { kind, namespace, name }; } /** @@ -218,11 +191,11 @@ export function stringifyEntityRef( if ('metadata' in ref) { kind = ref.kind; - namespace = ref.metadata.namespace ?? ENTITY_DEFAULT_NAMESPACE; + namespace = ref.metadata.namespace ?? DEFAULT_NAMESPACE; name = ref.metadata.name; } else { kind = ref.kind; - namespace = ref.namespace ?? ENTITY_DEFAULT_NAMESPACE; + namespace = ref.namespace ?? DEFAULT_NAMESPACE; name = ref.name; } @@ -249,7 +222,10 @@ export function stringifyEntityRef( */ export function compareEntityToRef( entity: Entity, - ref: EntityRef | EntityName, + ref: + | string + | { kind?: string; namespace?: string; name: string } + | EntityName, context?: { /** The default kind, if none is given in the reference */ defaultKind?: string; @@ -258,7 +234,7 @@ export function compareEntityToRef( }, ): boolean { const entityKind = entity.kind; - const entityNamespace = entity.metadata.namespace || ENTITY_DEFAULT_NAMESPACE; + const entityNamespace = entity.metadata.namespace || DEFAULT_NAMESPACE; const entityName = entity.metadata.name; let refKind: string | undefined; @@ -268,12 +244,12 @@ export function compareEntityToRef( const parsed = parseRefString(ref); refKind = parsed.kind || context?.defaultKind; refNamespace = - parsed.namespace || context?.defaultNamespace || ENTITY_DEFAULT_NAMESPACE; + parsed.namespace || context?.defaultNamespace || DEFAULT_NAMESPACE; refName = parsed.name; } else { refKind = ref.kind || context?.defaultKind; refNamespace = - ref.namespace || context?.defaultNamespace || ENTITY_DEFAULT_NAMESPACE; + ref.namespace || context?.defaultNamespace || DEFAULT_NAMESPACE; refName = ref.name; } diff --git a/packages/catalog-model/src/kinds/index.ts b/packages/catalog-model/src/kinds/index.ts index be9f7a0d6a..7211c25a8d 100644 --- a/packages/catalog-model/src/kinds/index.ts +++ b/packages/catalog-model/src/kinds/index.ts @@ -50,8 +50,6 @@ export type { SystemEntityV1alpha1 as SystemEntity, SystemEntityV1alpha1, } from './SystemEntityV1alpha1'; -export { templateEntityV1beta2Validator } from './TemplateEntityV1beta2'; -export type { TemplateEntityV1beta2 } from './TemplateEntityV1beta2'; export type { KindValidator } from './types'; export { userEntityV1alpha1Validator } from './UserEntityV1alpha1'; export type { diff --git a/packages/catalog-model/src/location/helpers.ts b/packages/catalog-model/src/location/helpers.ts index d308c00449..7c73908157 100644 --- a/packages/catalog-model/src/location/helpers.ts +++ b/packages/catalog-model/src/location/helpers.ts @@ -21,9 +21,6 @@ import { ANNOTATION_LOCATION } from './annotation'; /** * Parses a string form location reference. * - * Note that the return type is not `LocationSpec`, because we do not want to - * conflate the string form with the additional properties of that type. - * * @public * @param ref - A string-form location reference, e.g. `'url:https://host'` * @returns A location reference, e.g. `{ type: 'url', target: 'https://host' }` @@ -36,11 +33,6 @@ export function parseLocationReference(ref: string) { /** * Parses a string form location reference. * - * @remarks - * - * Note that the return type is not `LocationSpec`, because we do not want to - * conflate the string form with the additional properties of that type. - * * @public * @param ref - A string-form location ref, e.g. `'url:https://host'` * @returns A location ref, e.g. `{ type: 'url', target: 'https://host' }` @@ -83,11 +75,6 @@ export function parseLocationRef(ref: string): { /** * Turns a location reference into its string form. * - * @remarks - * - * Note that the input type is not `LocationSpec`, because we do not want to - * conflate the string form with the additional properties of that type. - * * @public * @param ref - A location reference, e.g. `{ type: 'url', target: 'https://host' }` * @returns A string-form location reference, e.g. `'url:https://host'` @@ -103,11 +90,6 @@ export function stringifyLocationReference(ref: { /** * Turns a location ref into its string form. * - * @remarks - * - * Note that the input type is not `LocationSpec`, because we do not want to - * conflate the string form with the additional properties of that type. - * * @public * @param ref - A location ref, e.g. `{ type: 'url', target: 'https://host' }` * @returns A string-form location ref, e.g. `'url:https://host'` diff --git a/packages/catalog-model/src/location/types.ts b/packages/catalog-model/src/location/types.ts index 2ed8fd717b..9b87e4919d 100644 --- a/packages/catalog-model/src/location/types.ts +++ b/packages/catalog-model/src/location/types.ts @@ -24,6 +24,7 @@ * default value: 'required'. * * @public + * @deprecated Import from `@backstage/plugin-catalog-backend` instead. */ export type LocationSpec = { type: string; diff --git a/packages/catalog-model/src/schema/EntityMeta.schema.json b/packages/catalog-model/src/schema/EntityMeta.schema.json index c834bfb113..7b2b9d3f82 100644 --- a/packages/catalog-model/src/schema/EntityMeta.schema.json +++ b/packages/catalog-model/src/schema/EntityMeta.schema.json @@ -38,7 +38,8 @@ "type": "integer", "description": "A positive nonzero number that indicates the current generation of data for this entity; the value is incremented each time the spec changes. This field can not be set by the user at creation time, and the server will reject an attempt to do so. The field will be populated in read operations.", "examples": [1], - "minimum": 1 + "minimum": 1, + "deprecated": true }, "name": { "type": "string", diff --git a/packages/catalog-model/src/types.ts b/packages/catalog-model/src/types.ts index 1a0fab267c..13550968be 100644 --- a/packages/catalog-model/src/types.ts +++ b/packages/catalog-model/src/types.ts @@ -21,6 +21,7 @@ import { JSONSchema7 } from 'json-schema'; * JSONSchema extendable by arbitrary JSON attributes * * @public + * @deprecated use JSONSchema7 from the json-schema package instead. */ export type JSONSchema = JSONSchema7 & { [key in string]?: JsonValue }; @@ -39,6 +40,7 @@ export type EntityName = { * A reference by name to an entity, either as a compact string representation, * or as a compound reference structure. * + * @deprecated Please use string directly, or EntityName (depending on what you actually need) * @remarks * * The string representation is on the form `[:][/]`. diff --git a/packages/cli-common/CHANGELOG.md b/packages/cli-common/CHANGELOG.md index e588c02edf..c890e44b29 100644 --- a/packages/cli-common/CHANGELOG.md +++ b/packages/cli-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/cli-common +## 0.1.8 + +### Patch Changes + +- Fix for the previous release with missing type declarations. + +## 0.1.7 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` + ## 0.1.6 ### Patch Changes diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json index cdb4b01b6d..3b05e93603 100644 --- a/packages/cli-common/package.json +++ b/packages/cli-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli-common", "description": "Common functionality used by cli, backend, and create-app", - "version": "0.1.6", + "version": "0.1.8", "private": false, "main": "src/index.ts", "types": "src/index.ts", @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "node-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,12 +24,13 @@ ], "license": "Apache-2.0", "scripts": { - "build": "backstage-cli build --outputs cjs,types", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "devDependencies": { "@types/jest": "^26.0.7", diff --git a/packages/cli/.snyk b/packages/cli/.snyk index 3ad43bd3de..0a32a6ac12 100644 --- a/packages/cli/.snyk +++ b/packages/cli/.snyk @@ -3,80 +3,65 @@ version: v1.22.1 # ignores vulnerabilities until expiry date; change duration by modifying expiry date ignore: SNYK-JS-ANSIHTML-1296849: - - 'webpack-dev-server > ansi-html': + - webpack-dev-server > ansi-html: reason: Developer tools are not a valid target for ReDoS attacks expires: 2022-03-06T17:18:55.019Z created: 2021-09-06T17:18:55.027Z - SNYK-JS-ANSIREGEX-1583908: - '*': reason: Developer tools are not a valid target for ReDoS attacks expires: 2022-03-06T17:18:55.019Z created: 2021-09-06T17:18:55.027Z - SNYK-JS-CSSWHAT-1298035: - '*': reason: Developer tools are not a valid target for ReDoS attacks expires: 2022-03-06T17:18:55.019Z created: 2021-09-06T17:18:55.027Z - SNYK-JS-BROWSERSLIST-1090194: - '*': reason: Developer tools are not a valid target for ReDoS attacks expires: 2022-03-06T17:18:55.019Z created: 2021-09-06T17:18:55.027Z - SNYK-JS-ISSVG-1243891: - '*': reason: Developer tools are not a valid target for ReDoS attacks expires: 2022-03-06T17:18:55.019Z created: 2021-09-06T17:18:55.027Z - SNYK-JS-ISSVG-1085627: - '*': reason: Developer tools are not a valid target for ReDoS attacks expires: 2022-03-06T17:18:55.019Z created: 2021-09-06T17:18:55.027Z - SNYK-JS-POSTCSS-1255640: - '*': reason: Developer tools are not a valid target for ReDoS attacks expires: 2022-03-06T17:18:55.019Z created: 2021-09-06T17:18:55.027Z - SNYK-JS-POSTCSS-1090595: - '*': reason: Developer tools are not a valid target for ReDoS attacks expires: 2022-03-06T17:18:55.019Z created: 2021-09-06T17:18:55.027Z - SNYK-JS-WS-1296835: - '*': reason: Developer tools are not a valid target for ReDoS attacks expires: 2022-03-06T17:18:55.019Z created: 2021-09-06T17:18:55.027Z - - SNYK-JS-IMMER-1540542: - - '*': - reason: Prototype pollution is not an effective attack against a CLI as it already executes arbitrary code - expires: 2022-03-06T17:18:55.019Z - created: 2021-09-06T17:18:55.027Z - - SNYK-JS-SETVALUE-1540541: - - '*': - reason: Prototype pollution is not an effective attack against a CLI as it already executes arbitrary code - expires: 2022-03-06T17:18:55.019Z - created: 2021-09-06T17:18:55.027Z - 'snyk:lic:npm:rollup-plugin-dts:LGPL-3.0': - '*': - reason: Backstage itself does not redistribute this dependency in minified form + reason: This is a development dependency only and is therefore acceptable expires: 2031-09-06T17:18:55.027Z created: 2021-09-06T17:18:55.027Z - 'snyk:lic:npm:axe-core:MPL-2.0': - '*': - reason: Backstage itself does not redistribute this dependency in minified form + reason: This is a development dependency only and is therefore acceptable expires: 2031-09-06T17:18:55.027Z created: 2021-09-06T17:18:55.027Z + SNYK-JS-UNSETVALUE-2400660: + - '*': + reason: >- + The pollution in this vulnerability does not allow injection of + arbitrary values and is therefore a negligible risk for a CLI. + expires: 2022-08-17T21:54:43.658Z + created: 2022-02-15T21:54:43.668Z patch: {} diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 37bea1e016..46b67615c4 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/cli +## 0.14.1 + +### Patch Changes + +- 5cc7f48400: Fixed a bug in the built-in Jest configuration that prevented it from identifying packages that had migrated to using the new package scripts to run tests. +- 49ae6c9573: chore(deps-dev): bump `@types/rollup-plugin-postcss` from 2.0.1 to 3.1.4 +- d64b8d3678: chore(deps): bump `minimatch` from 3.0.4 to 5.0.0 +- d2e9d2a34f: chore(deps): bump `@hot-loader/react-dom` from 16.13.0 to 17.0.2 +- c2f3a548cf: Fix building of backends with `repo build --all`, where it would previously only work if the build was executed within the backend package. +- 3d7ed5377a: Ignore setupTests and the file inside ./dev folder recursively. Eslint + can not resolve relative paths as we defined in the rule import/no-extraneous-dependencies, and it does not apply this rule. + + A downside to use a recursive definition would be to checking all `dev` folders, which might not be wanted. Ensure you don't use + the `dev` folder out of scope (must be used for dev. env. only) + +- Updated dependencies + - @backstage/config-loader@0.9.6 + +## 0.14.0 + +### Minor Changes + +- 1fc0cd3896: Bumped Webpack and Rollup `svgr` dependencies and updated the AST template for `.icon.svg` modules. This means that SVG icon imports are now using SVGO v2. + +### Patch Changes + +- 0b74c72987: Added a new experimental and hidden `backstage-cli repo lint` command that can be used to lint all packages in the project, similar to `lerna run lint`. +- 532dae9c4c: The `versions:bump --release next` command is updated to compare the `main` and `next` release manifests and prefer the latest. +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 22862486de: The `versions:bump` command now filters out `npm_` environment configuration when running `yarn install`. This has the effect of allowing it to consider local configuration files within the repository, which is the behavior that one would expect. +- 22862486de: The `versions:bump` command now also considers the root `package.json` when searching for updates. It has also received updates to its output, including a link the [Backstage upgrade helper](https://backstage.github.io/upgrade-helper) and silenced `yarn install` output. +- 7410e12268: Several changes were made to the new experimental package roles system. Unless you have been experimenting with using this new system, these changes have no affect on your project. + + Renamed the `backstage-cli migrate package-role` command to `backstage-cli migrate package-roles`. + + Updated the package role definitions by renaming `app` to `frontend`, `plugin-frontend` to `frontend-plugin`, `plugin-frontend-module` to `frontend-plugin-module`, `plugin-backend` to `backend-plugin`, and `plugin-backend-module` to `backend-plugin-module` + + The `backstage-cli migrate package-scripts` received several tweaks to make it more accurate. It now tries to maintain existing script arguments, like `--config` parameters for `build` and `start` scripts. + + The `script` command category has been renamed to `package`. + + The `backstage-cli package build` command set an incorrect target directory for `app` and `backend` packages, which has been fixed. + + The `backend:bundle` and `repo build` command for the `backend` role was previously ignoring building of bundled packages that had migrated to use package roles and the standard build script, this has now been fixed. + +- c6bbafb516: Updated the default [sucrase](https://github.com/alangpierce/sucrase)-based Jest transform to include source maps if the environment variable `ENABLE_SOURCE_MAPS` is non-empty. This can be used to better support editor test debugging integrations. +- 7410e12268: The `test` command now automatically adds `--passWithNoTests` to the Jest invocation. To revert this behavior, pass `--passWithNoTests=false` or `--no-passWithNoTests`. +- Updated dependencies + - @backstage/config-loader@0.9.4 + - @backstage/errors@0.2.1 + - @backstage/release-manifests@0.0.2 + - @backstage/cli-common@0.1.7 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.13.2 ### Patch Changes diff --git a/packages/cli/config/eslint.js b/packages/cli/config/eslint.js index 06f22933e8..5555dad451 100644 --- a/packages/cli/config/eslint.js +++ b/packages/cli/config/eslint.js @@ -55,8 +55,8 @@ module.exports = { devDependencies: [ '**/*.test.*', '**/*.stories.*', - 'src/setupTests.*', - 'dev/**', + '**/src/setupTests.*', + '**/dev/**', ], optionalDependencies: true, peerDependencies: true, diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 91cdd3c41b..d5b85b9748 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -175,7 +175,10 @@ async function getRootConfig() { // script to determine whether a given package should be tested const packageData = await fs.readJson(packagePath); const testScript = packageData.scripts && packageData.scripts.test; - if (testScript && testScript.includes('backstage-cli test')) { + const isSupportedTestScript = + testScript?.includes('backstage-cli test') || + testScript?.includes('backstage-cli package test'); + if (testScript && isSupportedTestScript) { return await getProjectConfig(projectPath, packageData.name); } diff --git a/packages/cli/package.json b/packages/cli/package.json index 589ac58646..b17d517ba1 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,11 +1,14 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.13.2", + "version": "0.14.1", "private": false, "publishConfig": { "access": "public" }, + "backstage": { + "role": "cli" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -18,23 +21,23 @@ "license": "Apache-2.0", "main": "dist/index.cjs.js", "scripts": { - "build": "backstage-cli build --outputs cjs", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "clean": "backstage-cli clean", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", "start": "nodemon --" }, "bin": { "backstage-cli": "bin/backstage-cli" }, "dependencies": { - "@backstage/cli-common": "^0.1.6", - "@backstage/config": "^0.1.13", - "@backstage/config-loader": "^0.9.3", - "@backstage/errors": "^0.2.0", - "@backstage/release-manifests": "^0.0.1", - "@backstage/types": "^0.1.1", - "@hot-loader/react-dom": "^16.13.0", + "@backstage/cli-common": "^0.1.8", + "@backstage/config": "^0.1.15", + "@backstage/config-loader": "^0.9.6", + "@backstage/errors": "^0.2.2", + "@backstage/release-manifests": "^0.0.2", + "@backstage/types": "^0.1.3", + "@hot-loader/react-dom": "^17.0.2", "@manypkg/get-packages": "^1.1.3", "@octokit/request": "^5.4.12", "@rollup/plugin-commonjs": "^21.0.1", @@ -48,8 +51,8 @@ "@sucrase/webpack-loader": "^2.0.0", "@svgr/plugin-jsx": "6.2.x", "@svgr/plugin-svgo": "6.2.x", - "@svgr/rollup": "5.5.x", - "@svgr/webpack": "5.5.x", + "@svgr/rollup": "6.2.x", + "@svgr/webpack": "6.2.x", "@types/webpack-env": "^1.15.2", "@typescript-eslint/eslint-plugin": "^5.9.0", "@typescript-eslint/parser": "^5.9.0", @@ -85,7 +88,7 @@ "json-schema": "^0.4.0", "jest-transform-yaml": "^1.0.0", "lodash": "^4.17.21", - "minimatch": "3.0.4", + "minimatch": "5.0.0", "mini-css-extract-plugin": "^2.4.2", "npm-packlist": "^3.0.0", "node-libs-browser": "^2.2.1", @@ -118,14 +121,14 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", - "@backstage/theme": "^0.2.14", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", + "@backstage/theme": "^0.2.15", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", "@types/fs-extra": "^9.0.1", @@ -137,7 +140,8 @@ "@types/npm-packlist": "^1.1.2", "@types/recursive-readdir": "^2.2.0", "@types/rollup-plugin-peer-deps-external": "^2.2.0", - "@types/rollup-plugin-postcss": "^2.0.0", + "@types/rollup-plugin-postcss": "^3.1.4", + "@types/svgo": "^2.6.2", "@types/tar": "^6.1.1", "@types/terser-webpack-plugin": "^5.0.4", "@types/yarnpkg__lockfile": "^1.1.4", diff --git a/packages/cli/src/commands/build/buildBackend.ts b/packages/cli/src/commands/build/buildBackend.ts index e32b7956db..d7c4f72dbe 100644 --- a/packages/cli/src/commands/build/buildBackend.ts +++ b/packages/cli/src/commands/build/buildBackend.ts @@ -35,7 +35,10 @@ export async function buildBackend(options: BuildBackendOptions) { const pkg = await fs.readJson(resolvePath(targetDir, 'package.json')); // We build the target package without generating type declarations. - await buildPackage({ outputs: new Set([Output.cjs]) }); + await buildPackage({ + targetDir: options.targetDir, + outputs: new Set([Output.cjs]), + }); const tmpDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-bundle')); try { @@ -49,19 +52,20 @@ export async function buildBackend(options: BuildBackendOptions) { // We built the target backend package using the regular build process, but the result of // that has now been packed into the dist workspace, so clean up the dist dir. - await fs.remove(targetDir); - await fs.mkdir(targetDir); + const distDir = resolvePath(targetDir, 'dist'); + await fs.remove(distDir); + await fs.mkdir(distDir); // Move out skeleton.tar.gz before we create the main bundle, no point having that included up twice. await fs.move( resolvePath(tmpDir, SKELETON_FILE), - resolvePath(targetDir, SKELETON_FILE), + resolvePath(distDir, SKELETON_FILE), ); // Create main bundle.tar.gz, with some tweaks to make it more likely hit Docker build cache. await tar.create( { - file: resolvePath(targetDir, BUNDLE_FILE), + file: resolvePath(distDir, BUNDLE_FILE), cwd: tmpDir, portable: true, noMtime: true, diff --git a/packages/cli/src/commands/build/buildApp.ts b/packages/cli/src/commands/build/buildFrontend.ts similarity index 95% rename from packages/cli/src/commands/build/buildApp.ts rename to packages/cli/src/commands/build/buildFrontend.ts index b0959b2d6a..6d7ca2e6a9 100644 --- a/packages/cli/src/commands/build/buildApp.ts +++ b/packages/cli/src/commands/build/buildFrontend.ts @@ -26,7 +26,7 @@ interface BuildAppOptions { configPaths: string[]; } -export async function buildApp(options: BuildAppOptions) { +export async function buildFrontend(options: BuildAppOptions) { const { targetDir, writeStats, configPaths } = options; const { name } = await fs.readJson(resolvePath(targetDir, 'package.json')); await buildBundle({ diff --git a/packages/cli/src/commands/build/command.ts b/packages/cli/src/commands/build/command.ts index 1189883b12..5c8065dbb4 100644 --- a/packages/cli/src/commands/build/command.ts +++ b/packages/cli/src/commands/build/command.ts @@ -18,22 +18,22 @@ import { Command } from 'commander'; import { buildPackage, Output } from '../../lib/builder'; import { findRoleFromCommand, getRoleInfo } from '../../lib/role'; import { paths } from '../../lib/paths'; -import { buildApp } from './buildApp'; +import { buildFrontend } from './buildFrontend'; import { buildBackend } from './buildBackend'; export async function command(cmd: Command): Promise { const role = await findRoleFromCommand(cmd); - if (role === 'app') { - return buildApp({ - targetDir: paths.resolveTarget('dist'), + if (role === 'frontend') { + return buildFrontend({ + targetDir: paths.targetDir, configPaths: cmd.config as string[], writeStats: Boolean(cmd.stats), }); } if (role === 'backend') { return buildBackend({ - targetDir: paths.resolveTarget('dist'), + targetDir: paths.targetDir, skipBuildDependencies: Boolean(cmd.skipBuildDependencies), }); } diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index 6f534d3e62..d3baa37b3f 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -61,14 +61,17 @@ export function registerRepoCommand(program: CommanderStatic) { export function registerScriptCommand(program: CommanderStatic) { const command = program - .command('script [command]', { hidden: true }) - .description('Lifecycle scripts for Backstage packages [EXPERIMENTAL]'); + .command('package [command]', { hidden: true }) + .description('Lifecycle scripts for individual packages [EXPERIMENTAL]'); command .command('start') .description('Start a package for local development') .option(...configOption) - .option('--role ', 'Run the command with an explicit package role') + .option( + '--role ', + 'Run the command with an explicit package role [EXPERIMENTAL]', + ) .option('--check', 'Enable type checking and linting if available') .option('--inspect', 'Enable debugger in Node.js environments') .option( @@ -80,6 +83,10 @@ export function registerScriptCommand(program: CommanderStatic) { command .command('build') .description('Build a package for production deployment or publishing') + .option( + '--role ', + 'Run the command with an explicit package role [EXPERIMENTAL]', + ) .option( '--minify', 'Minify the generated code. Does not apply to app or backend packages.', @@ -104,7 +111,7 @@ export function registerScriptCommand(program: CommanderStatic) { ) .action(lazy(() => import('./build').then(m => m.command))); - program + command .command('lint') .option( '--format ', @@ -115,7 +122,7 @@ export function registerScriptCommand(program: CommanderStatic) { .description('Lint a package') .action(lazy(() => import('./lint').then(m => m.default))); - program + command .command('test') .allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args .helpOption(', --backstage-cli-help') // Let Jest handle help @@ -144,7 +151,7 @@ export function registerMigrateCommand(program: CommanderStatic) { .description('Migration utilities [EXPERIMENTAL]'); command - .command('package-role') + .command('package-roles') .description(`Add package role field to packages that don't have it`) .action(lazy(() => import('./migrate/packageRole').then(m => m.default))); @@ -157,7 +164,7 @@ export function registerMigrateCommand(program: CommanderStatic) { } export function registerCommands(program: CommanderStatic) { - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('app:build') .description('Build an app for a production release') @@ -165,7 +172,7 @@ export function registerCommands(program: CommanderStatic) { .option(...configOption) .action(lazy(() => import('./app/build').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('app:serve') .description('Serve an app for local development') @@ -173,7 +180,7 @@ export function registerCommands(program: CommanderStatic) { .option(...configOption) .action(lazy(() => import('./app/serve').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('backend:build') .description('Build a backend plugin') @@ -181,7 +188,7 @@ export function registerCommands(program: CommanderStatic) { .option('--experimental-type-build', 'Enable experimental type build') .action(lazy(() => import('./backend/build').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('backend:bundle') .description('Bundle the backend into a deployment archive') @@ -191,7 +198,7 @@ export function registerCommands(program: CommanderStatic) { ) .action(lazy(() => import('./backend/bundle').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('backend:dev') .description('Start local development server with HMR for the backend') @@ -240,7 +247,7 @@ export function registerCommands(program: CommanderStatic) { lazy(() => import('./create-plugin/createPlugin').then(m => m.default)), ); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('plugin:build') .description('Build a plugin') @@ -248,7 +255,7 @@ export function registerCommands(program: CommanderStatic) { .option('--experimental-type-build', 'Enable experimental type build') .action(lazy(() => import('./plugin/build').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('plugin:serve') .description('Serves the dev/ folder of a plugin') @@ -263,7 +270,7 @@ export function registerCommands(program: CommanderStatic) { .description('Diff an existing plugin with the creation template') .action(lazy(() => import('./plugin/diff').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('build') .description('Build a package for publishing') @@ -272,7 +279,7 @@ export function registerCommands(program: CommanderStatic) { .option('--experimental-type-build', 'Enable experimental type build') .action(lazy(() => import('./oldBuild').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('lint') .option( @@ -284,7 +291,7 @@ export function registerCommands(program: CommanderStatic) { .description('Lint a package') .action(lazy(() => import('./lint').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('test') .allowUnknownOption(true) // Allows the command to run, but we still need to parse raw args @@ -370,19 +377,19 @@ export function registerCommands(program: CommanderStatic) { .description('Check Backstage package versioning') .action(lazy(() => import('./versions/lint').then(m => m.default))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('prepack') .description('Prepares a package for packaging before publishing') .action(lazy(() => import('./pack').then(m => m.pre))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('postpack') .description('Restores the changes made by the prepack command') .action(lazy(() => import('./pack').then(m => m.post))); - // TODO(Rugvip): Deprecate in favor of script variant + // TODO(Rugvip): Deprecate in favor of package variant program .command('clean') .description('Delete cache directories') diff --git a/packages/cli/src/commands/migrate/packageScripts.ts b/packages/cli/src/commands/migrate/packageScripts.ts index a3fdd20018..c7dab9466b 100644 --- a/packages/cli/src/commands/migrate/packageScripts.ts +++ b/packages/cli/src/commands/migrate/packageScripts.ts @@ -19,6 +19,8 @@ import { resolve as resolvePath } from 'path'; import { PackageGraph } from '../../lib/monorepo'; import { getRoleFromPackage, getRoleInfo, PackageRole } from '../../lib/role'; +const configArgPattern = /--config[=\s][^\s$]+/; + const noStartRoles: PackageRole[] = ['cli', 'common-library']; export async function command() { @@ -33,17 +35,52 @@ export async function command() { const roleInfo = getRoleInfo(role); const hasStart = !noStartRoles.includes(role); - const isBundled = roleInfo.output.includes('bundle'); + const needsPack = !(roleInfo.output.includes('bundle') || role === 'cli'); + const scripts = packageJson.scripts ?? {}; + + const startCmd = ['start']; + if (scripts.start?.includes('--check')) { + startCmd.push('--check'); + } + if (scripts.start?.includes('--config')) { + startCmd.push(...(scripts.start.match(configArgPattern) ?? [])); + } + + const buildCmd = ['build']; + if (scripts.build?.includes('--minify')) { + buildCmd.push('--minify'); + } + if (scripts.build?.includes('--experimental-type-build')) { + buildCmd.push('--experimental-type-build'); + } + if (scripts.build?.includes('--config')) { + buildCmd.push(...(scripts.build.match(configArgPattern) ?? [])); + } + + // For test scripts we keep all existing flags except for --passWithNoTests, since that's now default + const testCmd = ['test']; + if (scripts.test?.startsWith('backstage-cli test')) { + const args = scripts.test + .slice('backstage-cli test'.length) + .split(' ') + .filter(Boolean); + if (args.includes('--passWithNoTests')) { + args.splice(args.indexOf('--passWithNoTests'), 1); + } + testCmd.push(...args); + } const expectedScripts = { - ...(hasStart && { start: 'backstage-cli script start' }), - build: 'backstage-cli script build', - lint: 'backstage-cli script lint', - test: 'backstage-cli script test', - clean: 'backstage-cli script clean', - ...(!isBundled && { - postpack: 'backstage-cli script postpack', - prepack: 'backstage-cli script prepack', + ...(hasStart && { + start: `backstage-cli package ${startCmd.join(' ')}`, + }), + build: `backstage-cli package ${buildCmd.join(' ')}`, + lint: 'backstage-cli package lint', + test: `backstage-cli package ${testCmd.join(' ')}`, + clean: 'backstage-cli package clean', + ...(needsPack && { + postpack: 'backstage-cli package postpack', + prepack: 'backstage-cli package prepack', }), }; @@ -52,7 +89,12 @@ export async function command() { (packageJson.scripts = packageJson.scripts || {}); for (const [name, value] of Object.entries(expectedScripts)) { - if (currentScripts[name] !== value) { + const currentScript = currentScripts[name]; + + const isMissing = !currentScript; + const isDifferent = currentScript !== value; + const isBackstageScript = currentScript?.includes('backstage-cli'); + if (isMissing || (isDifferent && isBackstageScript)) { changed = true; currentScripts[name] = value; } diff --git a/packages/cli/src/commands/repo/build.ts b/packages/cli/src/commands/repo/build.ts index 9c32386304..ceb468ebfa 100644 --- a/packages/cli/src/commands/repo/build.ts +++ b/packages/cli/src/commands/repo/build.ts @@ -23,7 +23,7 @@ import { ExtendedPackage } from '../../lib/monorepo/PackageGraph'; import { runParallelWorkers } from '../../lib/parallel'; import { paths } from '../../lib/paths'; import { detectRoleFromPackage } from '../../lib/role'; -import { buildApp } from '../build/buildApp'; +import { buildFrontend } from '../build/buildFrontend'; import { buildBackend } from '../build/buildBackend'; function createScriptOptionsParser(anyCmd: Command, commandPath: string[]) { @@ -43,7 +43,9 @@ function createScriptOptionsParser(anyCmd: Command, commandPath: string[]) { } if (!targetCmd) { - throw new Error(`Could not find script command '${commandPath.join(' ')}'`); + throw new Error( + `Could not find package command '${commandPath.join(' ')}'`, + ); } const cmd = targetCmd; @@ -80,7 +82,7 @@ export async function command(cmd: Command): Promise { const apps = new Array(); const backends = new Array(); - const parseBuildScript = createScriptOptionsParser(cmd, ['script', 'build']); + const parseBuildScript = createScriptOptionsParser(cmd, ['package', 'build']); const options = packages.flatMap(pkg => { const role = @@ -90,7 +92,7 @@ export async function command(cmd: Command): Promise { return []; } - if (role === 'app') { + if (role === 'frontend') { apps.push(pkg); return []; } else if (role === 'backend') { @@ -137,7 +139,7 @@ export async function command(cmd: Command): Promise { ); return; } - await buildApp({ + await buildFrontend({ targetDir: pkg.dir, configPaths: (buildOptions.config as string[]) ?? [], writeStats: Boolean(buildOptions.stats), diff --git a/packages/cli/src/commands/start/command.ts b/packages/cli/src/commands/start/command.ts index 6615d5fd46..7a11028800 100644 --- a/packages/cli/src/commands/start/command.ts +++ b/packages/cli/src/commands/start/command.ts @@ -31,19 +31,19 @@ export async function command(cmd: Command): Promise { switch (role) { case 'backend': - case 'plugin-backend': - case 'plugin-backend-module': + case 'backend-plugin': + case 'backend-plugin-module': case 'node-library': return startBackend(options); - case 'app': + case 'frontend': return startFrontend({ ...options, entry: 'src/index', verifyVersions: true, }); case 'web-library': - case 'plugin-frontend': - case 'plugin-frontend-module': + case 'frontend-plugin': + case 'frontend-plugin-module': return startFrontend({ entry: 'dev/index', ...options }); default: throw new Error( diff --git a/packages/cli/src/commands/testCommand.ts b/packages/cli/src/commands/testCommand.ts index 24a4e06f9e..7165b20925 100644 --- a/packages/cli/src/commands/testCommand.ts +++ b/packages/cli/src/commands/testCommand.ts @@ -29,7 +29,11 @@ function includesAnyOf(hayStack: string[], ...needles: string[]) { export default async (cmd: Command) => { // all args are forwarded to jest - const rawArgs = cmd.parent.rawArgs as string[]; + let parent = cmd; + while (parent.parent) { + parent = parent.parent; + } + const rawArgs = parent.rawArgs as string[]; const args = rawArgs.slice(rawArgs.indexOf('test') + 1); // Only include our config if caller isn't passing their own config @@ -37,6 +41,10 @@ export default async (cmd: Command) => { args.push('--config', paths.resolveOwn('config/jest.js')); } + if (!includesAnyOf(args, '--no-passWithNoTests', '--passWithNoTests=false')) { + args.push('--passWithNoTests'); + } + // Run in watch mode unless in CI, coverage mode, or running all tests if ( !process.env.CI && diff --git a/packages/cli/src/lib/diff/handlers.ts b/packages/cli/src/lib/diff/handlers.ts index 5bd90e3d22..55e9cddd3d 100644 --- a/packages/cli/src/lib/diff/handlers.ts +++ b/packages/cli/src/lib/diff/handlers.ts @@ -173,6 +173,14 @@ class PackageJsonHandler { return; } + // Skip diffing package scripts that have been migrated to the new commands + const hasNewScript = Object.values(targetScripts).some(script => + String(script).includes('backstage-cli package '), + ); + if (hasNewScript) { + return; + } + for (const key of Object.keys(pkgScripts)) { await this.syncField(key, pkgScripts, targetScripts, 'scripts'); } diff --git a/packages/cli/src/lib/monorepo/PackageGraph.ts b/packages/cli/src/lib/monorepo/PackageGraph.ts index a1f77299df..4bbb9eac67 100644 --- a/packages/cli/src/lib/monorepo/PackageGraph.ts +++ b/packages/cli/src/lib/monorepo/PackageGraph.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { relative as relativePath, posix as posixPath } from 'path'; +import path from 'path'; import { getPackages, Package } from '@manypkg/get-packages'; import { paths } from '../paths'; import { PackageRole } from '../role'; @@ -166,8 +166,11 @@ export class PackageGraph extends Map { const dirMap = new Map( Array.from(this.values()).map(pkg => [ - posixPath.normalize(relativePath(paths.targetRoot, pkg.dir)) + - posixPath.sep, + // relative from root, convert to posix, and add a / at the end + path + .relative(paths.targetRoot, pkg.dir) + .split(path.sep) + .join(path.posix.sep) + path.posix.sep, pkg, ]), ); diff --git a/packages/cli/src/lib/packager/createDistWorkspace.ts b/packages/cli/src/lib/packager/createDistWorkspace.ts index 15ccda30c2..bce6c5c14c 100644 --- a/packages/cli/src/lib/packager/createDistWorkspace.ts +++ b/packages/cli/src/lib/packager/createDistWorkspace.ts @@ -38,6 +38,7 @@ import { Output, } from '../builder'; import { copyPackageDist } from './copyPackageDist'; +import { getRoleInfo } from '../role'; // These packages aren't safe to pack in parallel since the CLI depends on them const UNSAFE_PACKAGES = [ @@ -131,7 +132,9 @@ export async function createDistWorkspace( } const role = pkg.packageJson.backstage?.role; if (!role) { - console.warn(`Ignored ${pkg.packageJson.name} because it has no role`); + console.warn( + `Building ${pkg.packageJson.name} separately because it has no role`, + ); customBuild.push(pkg.packageJson.name); continue; } @@ -142,9 +145,17 @@ export async function createDistWorkspace( continue; } - if (!buildScript.startsWith('backstage-cli script build')) { + if (!buildScript.startsWith('backstage-cli package build')) { console.warn( - `Ignored ${pkg.packageJson.name} because it has a custom build script, '${buildScript}'`, + `Building ${pkg.packageJson.name} separately because it has a custom build script, '${buildScript}'`, + ); + customBuild.push(pkg.packageJson.name); + continue; + } + + if (getRoleInfo(role).output.includes('bundle')) { + console.warn( + `Building ${pkg.packageJson.name} separately because it is a bundled package`, ); customBuild.push(pkg.packageJson.name); continue; @@ -216,7 +227,7 @@ export async function createDistWorkspace( const FAST_PACK_SCRIPTS = [ undefined, 'backstage-cli prepack', - 'backstage-cli script prepack', + 'backstage-cli package prepack', ]; async function moveToDistWorkspace( diff --git a/packages/cli/src/lib/role/packageRoles.test.ts b/packages/cli/src/lib/role/packageRoles.test.ts index f6c9fb418a..759be462d2 100644 --- a/packages/cli/src/lib/role/packageRoles.test.ts +++ b/packages/cli/src/lib/role/packageRoles.test.ts @@ -31,8 +31,8 @@ describe('getRoleInfo', () => { output: ['types', 'esm'], }); - expect(getRoleInfo('app')).toEqual({ - role: 'app', + expect(getRoleInfo('frontend')).toEqual({ + role: 'frontend', platform: 'web', output: ['bundle'], }); @@ -56,10 +56,10 @@ describe('getRoleFromPackage', () => { expect( getRoleFromPackage({ backstage: { - role: 'app', + role: 'frontend', }, }), - ).toEqual('app'); + ).toEqual('frontend'); expect(() => getRoleFromPackage({ @@ -135,7 +135,7 @@ describe('detectRoleFromPackage', () => { 'cy:run': 'cypress run', }, }), - ).toEqual('app'); + ).toEqual('frontend'); }); it('detects the role of example-backend', () => { @@ -180,7 +180,7 @@ describe('detectRoleFromPackage', () => { clean: 'backstage-cli clean', }, }), - ).toEqual('plugin-frontend'); + ).toEqual('frontend-plugin'); }); it('detects the role of @backstage/plugin-catalog-backend', () => { @@ -204,7 +204,7 @@ describe('detectRoleFromPackage', () => { clean: 'backstage-cli clean', }, }), - ).toEqual('plugin-backend'); + ).toEqual('backend-plugin'); }); it('detects the role of @backstage/plugin-catalog-react', () => { @@ -274,7 +274,7 @@ describe('detectRoleFromPackage', () => { clean: 'backstage-cli clean', }, }), - ).toEqual('plugin-backend-module'); + ).toEqual('backend-plugin-module'); }); it('detects the role of @backstage/plugin-permission-node', () => { @@ -323,6 +323,6 @@ describe('detectRoleFromPackage', () => { clean: 'backstage-cli clean', }, }), - ).toEqual('plugin-frontend-module'); + ).toEqual('frontend-plugin-module'); }); }); diff --git a/packages/cli/src/lib/role/packageRoles.ts b/packages/cli/src/lib/role/packageRoles.ts index 7c855ee0fd..a33a7d715a 100644 --- a/packages/cli/src/lib/role/packageRoles.ts +++ b/packages/cli/src/lib/role/packageRoles.ts @@ -22,7 +22,7 @@ import { PackageRole, PackageRoleInfo } from './types'; const packageRoleInfos: PackageRoleInfo[] = [ { - role: 'app', + role: 'frontend', platform: 'web', output: ['bundle'], }, @@ -52,22 +52,22 @@ const packageRoleInfos: PackageRoleInfo[] = [ output: ['types', 'esm', 'cjs'], }, { - role: 'plugin-frontend', + role: 'frontend-plugin', platform: 'web', output: ['types', 'esm'], }, { - role: 'plugin-frontend-module', + role: 'frontend-plugin-module', platform: 'web', output: ['types', 'esm'], }, { - role: 'plugin-backend', + role: 'backend-plugin', platform: 'node', output: ['types', 'cjs'], }, { - role: 'plugin-backend-module', + role: 'backend-plugin-module', platform: 'node', output: ['types', 'cjs'], }, @@ -147,22 +147,22 @@ export function detectRoleFromPackage( const pkg = detectionSchema.parse(pkgJson); if (pkg.scripts?.start?.includes('app:serve')) { - return 'app'; + return 'frontend'; } if (pkg.scripts?.build?.includes('backend:bundle')) { return 'backend'; } if (pkg.name?.includes('plugin-') && pkg.name?.includes('-backend-module-')) { - return 'plugin-backend-module'; + return 'backend-plugin-module'; } if (pkg.name?.includes('plugin-') && pkg.name?.includes('-module-')) { - return 'plugin-frontend-module'; + return 'frontend-plugin-module'; } if (pkg.scripts?.start?.includes('plugin:serve')) { - return 'plugin-frontend'; + return 'frontend-plugin'; } if (pkg.scripts?.start?.includes('backend:dev')) { - return 'plugin-backend'; + return 'backend-plugin'; } const mainEntry = pkg.publishConfig?.main || pkg.main; diff --git a/packages/cli/src/lib/role/types.ts b/packages/cli/src/lib/role/types.ts index 6a89f7bdaf..a3961544f4 100644 --- a/packages/cli/src/lib/role/types.ts +++ b/packages/cli/src/lib/role/types.ts @@ -15,16 +15,16 @@ */ export type PackageRole = - | 'app' + | 'frontend' | 'backend' | 'cli' | 'web-library' | 'node-library' | 'common-library' - | 'plugin-frontend' - | 'plugin-frontend-module' - | 'plugin-backend' - | 'plugin-backend-module'; + | 'frontend-plugin' + | 'frontend-plugin-module' + | 'backend-plugin' + | 'backend-plugin-module'; export type PackagePlatform = 'node' | 'web' | 'common'; export type PackageOutputType = 'bundle' | 'types' | 'esm' | 'cjs'; diff --git a/packages/cli/src/lib/svgrTemplate.ts b/packages/cli/src/lib/svgrTemplate.ts index ca2a8c38fa..6af1b95e71 100644 --- a/packages/cli/src/lib/svgrTemplate.ts +++ b/packages/cli/src/lib/svgrTemplate.ts @@ -20,28 +20,23 @@ * them with .icon.svg */ export function svgrTemplate( - { template }: any, - _opts: any, - { imports, interfaces, componentName, jsx }: any, + { imports, interfaces, componentName, props, jsx }: any, + { tpl }: any, ) { - const iconName = { - ...componentName, - name: `${componentName.name.replace(/icon$/, '')}Icon`, - }; + const name = `${componentName.replace(/icon$/, '')}Icon`; const defaultExport = { type: 'ExportDefaultDeclaration', - declaration: iconName, + declaration: { type: 'Identifier', name }, }; - const typeScriptTemplate = template.smart({ plugins: ['typescript'] }); - return typeScriptTemplate.ast` + return tpl` ${imports} import SvgIcon from '@material-ui/core/SvgIcon'; ${interfaces} -const ${iconName} = props => React.createElement(SvgIcon, props, ${jsx.children}); +const ${name} = (${props}) => React.createElement(SvgIcon, ${props}, ${jsx.children}); ${defaultExport}`; } diff --git a/packages/codemods/.snyk b/packages/codemods/.snyk new file mode 100644 index 0000000000..7926925d71 --- /dev/null +++ b/packages/codemods/.snyk @@ -0,0 +1,12 @@ +# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. +version: v1.22.1 +# ignores vulnerabilities until expiry date; change duration by modifying expiry date +ignore: + SNYK-JS-UNSETVALUE-2400660: + - '*': + reason: >- + The pollution in this vulnerability does not allow injection of + arbitrary values and is therefore a negligible risk for a CLI. + expires: 2022-08-17T21:54:43.658Z + created: 2022-02-15T21:54:43.668Z +patch: {} diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index c7283f5d8c..603fa1075e 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/codemods +## 0.1.35 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.8 + +## 0.1.34 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-app-api@0.5.3 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/cli-common@0.1.7 + ## 0.1.33 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index dc1fe6a537..c4d2eedb4d 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,12 +1,15 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.33", + "version": "0.1.34", "private": false, "publishConfig": { "access": "public", "main": "dist/index.cjs.js" }, + "backstage": { + "role": "cli" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -20,18 +23,18 @@ "main": "src/index.ts", "scripts": { "start": "nodemon --", - "build": "backstage-cli build --outputs cjs", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "clean": "backstage-cli package clean" }, "bin": { "backstage-codemods": "bin/backstage-codemods" }, "dependencies": { - "@backstage/cli-common": "0.1.6", + "@backstage/cli-common": "^0.1.8", "@backstage/core-app-api": "*", "@backstage/core-components": "*", "@backstage/core-plugin-api": "*", diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index cf953948da..d9bd7be8ba 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/config-loader +## 0.9.6 + +### Patch Changes + +- c3a1300f79: Include any files included in configuration via $include or $file directives when watching for configuration changes. + +## 0.9.5 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/cli-common@0.1.8 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/types@0.1.3 + +## 0.9.4 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/errors@0.2.1 + - @backstage/cli-common@0.1.7 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.9.3 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index b374a02fbf..d9c9b551b4 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.9.3", + "version": "0.9.6", "private": false, "publishConfig": { "access": "public", @@ -9,6 +9,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "node-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,18 +25,19 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/cli-common": "^0.1.6", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/types": "^0.1.1", + "@backstage/cli-common": "^0.1.8", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/types": "^0.1.3", "@types/json-schema": "^7.0.6", "ajv": "^7.0.3", "chokidar": "^3.5.2", diff --git a/packages/config-loader/src/loader.test.ts b/packages/config-loader/src/loader.test.ts index 9faeda64ea..058f59aef1 100644 --- a/packages/config-loader/src/loader.test.ts +++ b/packages/config-loader/src/loader.test.ts @@ -333,6 +333,66 @@ describe('loadConfig', () => { stopSignal.resolve(); }); + it('watches included files', async () => { + const onChange = defer(); + const stopSignal = defer(); + + await expect( + loadConfig({ + configRoot: '/root', + configTargets: [{ path: '/root/app-config.development.yaml' }], + watch: { + onChange: onChange.resolve, + stopSignal: stopSignal.promise, + }, + }), + ).resolves.toEqual({ + appConfigs: [ + { + context: 'app-config.development.yaml', + data: { + app: { + sessionKey: 'development-key', + }, + backend: { + foo: { + bar: 'token is-secret', + }, + }, + other: { + secret: 'abc123', + }, + }, + }, + ], + }); + + // session-key is indirectly included in app-config.development.yaml + // via included.yaml + await fs.writeFile('/root/secrets/session-key.txt', 'abc234'); + + await expect(onChange.promise).resolves.toEqual([ + { + context: 'app-config.development.yaml', + data: { + app: { + sessionKey: 'development-key', + }, + backend: { + foo: { + bar: 'token is-secret', + }, + }, + other: { + secret: 'abc234', + }, + }, + }, + ]); + + stopSignal.resolve(); + }); + it('watches remote config urls', async () => { server.use(initialLoaderHandler); diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 03a6805da0..9a3287e285 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -140,7 +140,8 @@ export async function loadConfig( const env = envFunc ?? (async (name: string) => process.env[name]); const loadConfigFiles = async () => { - const configs = []; + const fileConfigs = []; + const loadedPaths = new Set(); for (const configPath of configPaths) { if (!isAbsolute(configPath)) { @@ -148,8 +149,15 @@ export async function loadConfig( } const dir = dirname(configPath); - const readFile = (path: string) => - fs.readFile(resolvePath(dir, path), 'utf8'); + const readFile = (path: string) => { + const fullPath = resolvePath(dir, path); + // if we read a file when building configuration, + // we should include that file when watching for + // changes, too. + loadedPaths.add(fullPath); + + return fs.readFile(fullPath, 'utf8'); + }; const input = yaml.parse(await readFile(configPath)); const substitutionTransform = createSubstitutionTransform(env); @@ -158,10 +166,10 @@ export async function loadConfig( substitutionTransform, ]); - configs.push({ data, context: basename(configPath) }); + fileConfigs.push({ data, context: basename(configPath) }); } - return configs; + return { fileConfigs, loadedPaths }; }; const loadRemoteConfigFiles = async () => { @@ -199,8 +207,9 @@ export async function loadConfig( }; let fileConfigs: AppConfig[]; + let loadedPaths: Set; try { - fileConfigs = await loadConfigFiles(); + ({ fileConfigs, loadedPaths } = await loadConfigFiles()); } catch (error) { throw new ForwardedError('Failed to read static configuration file', error); } @@ -220,14 +229,24 @@ export async function loadConfig( const envConfigs = await readEnvConfig(process.env); const watchConfigFile = (watchProp: LoadConfigOptionsWatch) => { - const watcher = chokidar.watch(configPaths, { + let watchedFiles = Array.from(loadedPaths); + + const watcher = chokidar.watch(watchedFiles, { usePolling: process.env.NODE_ENV === 'test', }); let currentSerializedConfig = JSON.stringify(fileConfigs); watcher.on('change', async () => { try { - const newConfigs = await loadConfigFiles(); + const { fileConfigs: newConfigs, loadedPaths: newLoadedPaths } = + await loadConfigFiles(); + + // Replace watches to handle any added or removed + // $include or $file expressions. + watcher.unwatch(watchedFiles); + watchedFiles = Array.from(newLoadedPaths); + watcher.add(watchedFiles); + const newSerializedConfig = JSON.stringify(newConfigs); if (currentSerializedConfig === newSerializedConfig) { diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 2d3e90d1b7..2af4d3763b 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/config +## 0.1.15 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/types@0.1.3 + +## 0.1.14 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/types@0.1.2 + ## 0.1.13 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index 02c63af2b5..63edfe0360 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "0.1.13", + "version": "0.1.15", "private": false, "publishConfig": { "access": "public", @@ -9,6 +9,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,19 +25,19 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/types": "^0.1.1", + "@backstage/types": "^0.1.3", "lodash": "^4.17.21" }, "devDependencies": { - "@backstage/test-utils": "^0.2.4-next.0", + "@backstage/test-utils": "^0.2.5", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index cc115a7da2..f40ea23f73 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/core-app-api +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@0.7.0 + +## 0.5.3 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-plugin-api@0.6.1 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + - @backstage/version-bridge@0.1.2 + ## 0.5.2 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 04e87b5d0c..6f74bf95d6 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,13 +1,16 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "0.5.2", + "version": "0.5.4", "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,18 +24,19 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/types": "^0.1.1", - "@backstage/version-bridge": "^0.1.1", + "@backstage/config": "^0.1.15", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/types": "^0.1.3", + "@backstage/version-bridge": "^0.1.2", "@types/prop-types": "^15.7.3", "prop-types": "^15.7.2", "react-router-dom": "6.0.0-beta.0", @@ -45,8 +49,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index fe51935ab0..5d8211d303 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/core-components +## 0.8.10 + +### Patch Changes + +- d91d22bb19: When clicking on a log line the URL will be updated from `/task/uid` to + `/task/uid/#line-1`. This URL are also sharable, meaning that the UI will + highlight the log line in the hash of the URL. +- Updated dependencies + - @backstage/core-plugin-api@0.7.0 + +## 0.8.9 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 126074a04b: Port supported react-use functions to react-hookz. +- Updated dependencies + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + ## 0.8.8 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index ecb910c50b..d8d8328f9b 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,13 +1,16 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.8.8", + "version": "0.8.10", "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,18 +24,19 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/theme": "^0.2.14", + "@backstage/config": "^0.1.15", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/theme": "^0.2.15", "@material-table/core": "^3.1.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -60,7 +64,8 @@ "react-sparklines": "^1.7.0", "react-syntax-highlighter": "^15.4.5", "react-text-truncate": "^0.17.0", - "react-use": "^17.2.4", + "react-use": "^17.3.2", + "@react-hookz/web": "^12.3.0", "react-virtualized-auto-sizer": "^1.0.6", "react-window": "^1.8.6", "remark-gfm": "^3.0.1", @@ -73,9 +78,9 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/core-app-api": "^0.5.2", - "@backstage/cli": "^0.13.2", - "@backstage/test-utils": "^0.2.4", + "@backstage/core-app-api": "^0.5.4", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-components/src/components/LogViewer/RealLogViewer.tsx b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx index cd30e96c92..ac66d7cdf5 100644 --- a/packages/core-components/src/components/LogViewer/RealLogViewer.tsx +++ b/packages/core-components/src/components/LogViewer/RealLogViewer.tsx @@ -15,6 +15,7 @@ */ import React, { useEffect, useMemo, useRef } from 'react'; +import { useLocation } from 'react-router-dom'; import IconButton from '@material-ui/core/IconButton'; import CopyIcon from '@material-ui/icons/FileCopy'; import AutoSizer from 'react-virtualized-auto-sizer'; @@ -42,6 +43,7 @@ export function RealLogViewer(props: RealLogViewerProps) { const search = useLogViewerSearch(lines); const selection = useLogViewerSelection(lines); + const location = useLocation(); useEffect(() => { if (search.resultLine !== undefined && listRef.current) { @@ -49,11 +51,18 @@ export function RealLogViewer(props: RealLogViewerProps) { } }, [search.resultLine]); + useEffect(() => { + if (location.hash) { + // #line-6 -> 6 + const line = parseInt(location.hash.replace(/\D/g, ''), 10); + selection.setSelection(line, false); + } + }, []); // eslint-disable-line react-hooks/exhaustive-deps + const handleSelectLine = ( line: number, event: { shiftKey: boolean; preventDefault: () => void }, ) => { - event.preventDefault(); selection.setSelection(line, event.shiftKey); }; diff --git a/packages/core-components/src/components/LogViewer/useLogViewerSearch.tsx b/packages/core-components/src/components/LogViewer/useLogViewerSearch.tsx index 7f0069bc44..14f71984f4 100644 --- a/packages/core-components/src/components/LogViewer/useLogViewerSearch.tsx +++ b/packages/core-components/src/components/LogViewer/useLogViewerSearch.tsx @@ -15,7 +15,7 @@ */ import { useMemo, useState } from 'react'; -import useToggle from 'react-use/lib/useToggle'; +import { useToggle } from '@react-hookz/web'; import { AnsiLine } from './AnsiProcessor'; export function applySearchFilter(lines: AnsiLine[], searchText: string) { diff --git a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx index 69952d487a..da489725e4 100644 --- a/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx +++ b/packages/core-components/src/components/OverflowTooltip/OverflowTooltip.tsx @@ -18,7 +18,7 @@ import { makeStyles } from '@material-ui/core/styles'; import Tooltip, { TooltipProps } from '@material-ui/core/Tooltip'; import React, { useState } from 'react'; import TextTruncate, { TextTruncateProps } from 'react-text-truncate'; -import useMountedState from 'react-use/lib/useMountedState'; +import { useIsMounted } from '@react-hookz/web'; type Props = { text: TextTruncateProps['text']; @@ -41,7 +41,7 @@ const useStyles = makeStyles( export function OverflowTooltip(props: Props) { const [hover, setHover] = useState(false); - const isMounted = useMountedState(); + const isMounted = useIsMounted(); const classes = useStyles(); const handleToggled = (truncated: boolean) => { diff --git a/packages/core-components/src/hooks/useQueryParamState.ts b/packages/core-components/src/hooks/useQueryParamState.ts index 72ccafd99f..16cd9e18c8 100644 --- a/packages/core-components/src/hooks/useQueryParamState.ts +++ b/packages/core-components/src/hooks/useQueryParamState.ts @@ -18,7 +18,7 @@ import { isEqual } from 'lodash'; import qs from 'qs'; import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; -import useDebounce from 'react-use/lib/useDebounce'; +import { useDebouncedEffect } from '@react-hookz/web'; function stringify(queryParams: any): string { // Even though these setting don't look nice (e.g. escaped brackets), we should keep @@ -75,7 +75,7 @@ export function useQueryParamState( ); }, [searchParamsString, setQueryParamState, stateName]); - useDebounce( + useDebouncedEffect( () => { const queryString = joinQueryString( searchParamsString, @@ -87,8 +87,8 @@ export function useQueryParamState( setSearchParams(queryString, { replace: true }); } }, - debounceTime, [setSearchParams, queryParamState, searchParamsString, stateName], + debounceTime, ); return [queryParamState, setQueryParamState]; diff --git a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx index adcd6ced8f..13a61d259f 100644 --- a/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx +++ b/packages/core-components/src/layout/ProxiedSignInPage/ProxiedSignInPage.tsx @@ -20,7 +20,7 @@ import { useApi, } from '@backstage/core-plugin-api'; import React from 'react'; -import useAsync from 'react-use/lib/useAsync'; +import { useAsync, useMountEffect } from '@react-hookz/web'; import { ErrorPanel } from '../../components/ErrorPanel'; import { Progress } from '../../components/Progress'; import { ProxiedSignInIdentity } from './ProxiedSignInIdentity'; @@ -56,7 +56,7 @@ export type ProxiedSignInPageProps = SignInPageProps & { export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => { const discoveryApi = useApi(discoveryApiRef); - const { loading, error } = useAsync(async () => { + const [{ status, error }, { execute }] = useAsync(async () => { const identity = new ProxiedSignInIdentity({ provider: props.provider, discoveryApi, @@ -65,9 +65,11 @@ export const ProxiedSignInPage = (props: ProxiedSignInPageProps) => { await identity.start(); props.onSignInSuccess(identity); - }, []); + }); - if (loading) { + useMountEffect(execute); + + if (status === 'loading') { return ; } else if (error) { return ( diff --git a/packages/core-components/src/layout/Sidebar/Bar.test.tsx b/packages/core-components/src/layout/Sidebar/Bar.test.tsx index 89133997f9..fc42e7a94e 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.test.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.test.tsx @@ -46,6 +46,11 @@ async function renderScalableSidebar() { {}} text="Catalog"> + @@ -112,5 +121,22 @@ describe('Sidebar', () => { '/dropdownitemlink', ); }); + + it('Submenu item renders an external link when `to` value is provided', async () => { + userEvent.hover(screen.getByTestId('item-with-submenu')); + expect(screen.getByText('External Link').closest('a')).toHaveAttribute( + 'href', + 'https://backstage.io/', + ); + }); + + it('Dropdown item in submenu renders an external link when `to` value is provided', async () => { + userEvent.hover(screen.getByTestId('item-with-submenu')); + userEvent.click(screen.getByText('Misc')); + expect(screen.getByText('dropdown item 3').closest('a')).toHaveAttribute( + 'href', + 'https://backstage.io/', + ); + }); }); }); diff --git a/packages/core-components/src/layout/Sidebar/Intro.tsx b/packages/core-components/src/layout/Sidebar/Intro.tsx index 7b2c5fa4d7..1fcc54188f 100644 --- a/packages/core-components/src/layout/Sidebar/Intro.tsx +++ b/packages/core-components/src/layout/Sidebar/Intro.tsx @@ -21,7 +21,7 @@ import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; import CloseIcon from '@material-ui/icons/Close'; import React, { useContext, useState } from 'react'; -import useLocalStorage from 'react-use/lib/useLocalStorage'; +import { useLocalStorageValue } from '@react-hookz/web'; import { sidebarConfig, SidebarContext, @@ -155,7 +155,7 @@ export function SidebarIntro(_props: {}) { recentlyViewedItemsDismissed: false, }; const [dismissedIntro, setDismissedIntro] = - useLocalStorage(SIDEBAR_INTRO_LOCAL_STORAGE); + useLocalStorageValue(SIDEBAR_INTRO_LOCAL_STORAGE); const { starredItemsDismissed, recentlyViewedItemsDismissed } = dismissedIntro ?? {}; diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx index 1ede5f887e..d204728bcf 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx @@ -14,15 +14,10 @@ * limitations under the License. */ import React, { useContext, useState } from 'react'; -import { - NavLink, - resolvePath, - useLocation, - useResolvedPath, -} from 'react-router-dom'; +import { resolvePath, useLocation, useResolvedPath } from 'react-router-dom'; import { makeStyles } from '@material-ui/core/styles'; import Typography from '@material-ui/core/Typography'; -import Link from '@material-ui/core/Link'; +import { Link } from '../../components/Link'; import { IconComponent } from '@backstage/core-plugin-api'; import classnames from 'classnames'; import { BackstageTheme } from '@backstage/theme'; @@ -163,7 +158,6 @@ export const SidebarSubmenuItem = (props: SidebarSubmenuItemProps) => {
{dropdownItems.map((object, key) => ( { return (
login({ checkExisting: true })); + useMountEffect(() => login({ checkExisting: true })); return showLoginPage ? ( diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 21668577a1..bba39bf2cf 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/core-plugin-api +## 0.7.0 + +### Minor Changes + +- 33cd215b11: **BREAKING**: Removed deprecated `AnyAnalyticsContext` type which is replaced by `AnalyticsContextValue` + +## 0.6.1 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 2714145cf5: Removes unused react-use dependency. +- Updated dependencies + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + - @backstage/version-bridge@0.1.2 + ## 0.6.0 ### Minor Changes diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md index 5ac07e195c..1647d1c2cb 100644 --- a/packages/core-plugin-api/api-report.md +++ b/packages/core-plugin-api/api-report.md @@ -76,11 +76,6 @@ export type AnalyticsTracker = { ) => void; }; -// @public @deprecated -export type AnyAnalyticsContext = { - [param in string]: string | boolean | number | undefined; -}; - // @public export type AnyApiFactory = ApiFactory< unknown, diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index c9f110b432..4e9198557b 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,13 +1,16 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "0.6.0", + "version": "0.7.0", "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,21 +24,21 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/types": "^0.1.1", - "@backstage/version-bridge": "^0.1.1", + "@backstage/config": "^0.1.15", + "@backstage/types": "^0.1.3", + "@backstage/version-bridge": "^0.1.2", "history": "^5.0.0", "prop-types": "^15.7.2", "react-router-dom": "6.0.0-beta.0", - "react-use": "^17.2.4", "zen-observable": "^0.8.15" }, "peerDependencies": { @@ -43,9 +46,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", - "@backstage/core-app-api": "^0.5.2-next.0", - "@backstage/test-utils": "^0.2.4-next.0", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/packages/core-plugin-api/src/analytics/index.ts b/packages/core-plugin-api/src/analytics/index.ts index 942df56ef8..651e8e105e 100644 --- a/packages/core-plugin-api/src/analytics/index.ts +++ b/packages/core-plugin-api/src/analytics/index.ts @@ -15,9 +15,5 @@ */ export { AnalyticsContext } from './AnalyticsContext'; -export type { - AnalyticsContextValue, - AnyAnalyticsContext, - CommonAnalyticsContext, -} from './types'; +export type { AnalyticsContextValue, CommonAnalyticsContext } from './types'; export { useAnalytics } from './useAnalytics'; diff --git a/packages/core-plugin-api/src/analytics/types.ts b/packages/core-plugin-api/src/analytics/types.ts index 431e453a61..1a60572082 100644 --- a/packages/core-plugin-api/src/analytics/types.ts +++ b/packages/core-plugin-api/src/analytics/types.ts @@ -36,16 +36,6 @@ export type CommonAnalyticsContext = { extension: string; }; -/** - * Allows arbitrary scalar values as context attributes too. - * - * @public - * @deprecated Will be removed, use `AnalyticsContextValue` instead - */ -export type AnyAnalyticsContext = { - [param in string]: string | boolean | number | undefined; -}; - /** * Analytics context envelope. * diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 886d873740..52dad1bfc9 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,82 @@ # @backstage/create-app +## 0.4.21 + +### Patch Changes + +- a686702dbe: Update the template to reflect the renaming of `CatalogResultListItem` to `CatalogSearchResultListItem` from `@backstage/plugin-catalog`. + + To apply this change to an existing app, make the following change to `packages/app/src/components/search/SearchPage.tsx`: + + ```diff + -import { CatalogResultListItem } from '@backstage/plugin-catalog'; + +import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; + ``` + + ```diff + case 'software-catalog': + return ( + - + ``` + +- f39c1e6036: To reflect the updated `knex` and `@vscode/sqlite3` dependencies introduced with [v0.4.19](https://github.com/backstage/backstage/blob/master/packages/create-app/CHANGELOG.md#0419), we update our example `Dockerfile`, adding `@vscode/sqlite3` build dependencies to the image. Further on, we updated it to the `node:16-bullseye-slim` base image. + + To apply this update to an existing app, make the following change to `packages/backend/Dockerfile`: + + ```diff + -FROM node:14-buster-slim + +FROM node:16-bullseye-slim + ``` + + and, _only if you are using sqlite3 in your app_: + + ```diff + RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz + + + +# install sqlite3 dependencies + +RUN apt-get update && \ + + apt-get install -y libsqlite3-dev python3 cmake g++ && \ + + rm -rf /var/lib/apt/lists/* && \ + + yarn config set python /usr/bin/python3 + + RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" + ``` + + If you are using a multi-stage Docker build for your app, please refer to the [updated examples](https://github.com/backstage/backstage/blob/master/docs/deployment/docker.md#multi-stage-build) in the documentation. + +## 0.4.20 + +### Patch Changes + +- e725bb812f: Remove SearchContextProvider from `` + + The `SidebarSearchModal` exported from `plugin-search` internally renders `SearchContextProvider`, so it can be removed from `Root.tsx`: + + ```diff + -import { + - SidebarSearchModal, + - SearchContextProvider, + -} from '@backstage/plugin-search'; + +import { SidebarSearchModal } from '@backstage/plugin-search'; + + ... omitted ... + + } to="/search"> + - + - + - + + + + ``` + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/cli-common@0.1.7 + ## 0.4.19 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 4ab263abfd..a240e95cac 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,11 +1,14 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.4.19", + "version": "0.4.21", "private": false, "publishConfig": { "access": "public" }, + "backstage": { + "role": "cli" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,16 +24,16 @@ "backstage-create-app": "bin/backstage-create-app" }, "scripts": { - "build": "backstage-cli build --outputs cjs", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "clean": "backstage-cli clean", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", "prepack": "node scripts/prepack.js", "postpack": "node scripts/postpack.js", "start": "nodemon --" }, "dependencies": { - "@backstage/cli-common": "^0.1.6", + "@backstage/cli-common": "^0.1.8", "chalk": "^4.0.0", "commander": "^6.1.0", "fs-extra": "9.1.0", diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 9b6518620d..6f00993273 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -15,7 +15,6 @@ import { orgPlugin } from '@backstage/plugin-org'; import { SearchPage } from '@backstage/plugin-search'; import { TechRadarPage } from '@backstage/plugin-tech-radar'; import { - DefaultTechDocsHome, TechDocsIndexPage, techdocsPlugin, TechDocsReaderPage, @@ -65,9 +64,7 @@ const routes = ( > {entityPage} - }> - - + } /> } diff --git a/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx index a88e7250e9..cd4603ecd3 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/search/SearchPage.tsx @@ -1,12 +1,12 @@ import React from 'react'; import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core'; -import { CatalogResultListItem } from '@backstage/plugin-catalog'; +import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; import { catalogApiRef, CATALOG_FILTER_EXISTS, } from '@backstage/plugin-catalog-react'; -import { DocsResultListItem } from '@backstage/plugin-techdocs'; +import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; import { SearchBar, @@ -116,14 +116,14 @@ const SearchPage = () => { switch (type) { case 'software-catalog': return ( - ); case 'techdocs': return ( - diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index 31231a3a4a..dd35d4ddbc 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -9,7 +9,7 @@ # # Once the commands have been run, you can build the image using `yarn build-image` -FROM node:14-buster-slim +FROM node:16-bullseye-slim WORKDIR /app @@ -19,6 +19,12 @@ WORKDIR /app COPY yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz +# install sqlite3 dependencies +RUN apt-get update && \ + apt-get install -y libsqlite3-dev python3 cmake g++ && \ + rm -rf /var/lib/apt/lists/* && \ + yarn config set python /usr/bin/python3 + RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" # Then copy the rest of the backend bundle, along with any other files we might want. diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 8061f5fb98..4d2354297e 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/dev-utils +## 0.2.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/app-defaults@0.1.9 + - @backstage/core-app-api@0.5.4 + - @backstage/integration-react@0.1.23 + - @backstage/test-utils@0.2.6 + +## 0.2.22 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-app-api@0.5.3 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/integration-react@0.1.22 + - @backstage/test-utils@0.2.5 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/app-defaults@0.1.8 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.2.21 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 9d55d17592..5c29c7dd0f 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,13 +1,16 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.2.21", + "version": "0.2.23", "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,23 +24,24 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/app-defaults": "^0.1.7", - "@backstage/core-app-api": "^0.5.2", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/integration-react": "^0.1.21", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/test-utils": "^0.2.4", - "@backstage/theme": "^0.2.14", + "@backstage/app-defaults": "^0.1.9", + "@backstage/core-app-api": "^0.5.4", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/catalog-model": "^0.11.0", + "@backstage/integration-react": "^0.1.23", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/test-utils": "^0.2.6", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^5.10.1", @@ -55,7 +59,7 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index bbf55fac17..b17b0c14ce 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -3,6 +3,9 @@ "description": "E2E test for verifying Backstage packages", "version": "0.2.0", "private": true, + "backstage": { + "role": "cli" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -16,16 +19,18 @@ "main": "src/index.ts", "scripts": { "start": "nodemon --", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "test:e2e": "yarn start" + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "test:e2e": "yarn start", + "build": "backstage-cli package build", + "clean": "backstage-cli package clean" }, "bin": { "e2e-test": "bin/e2e-test" }, "devDependencies": { - "@backstage/cli-common": "^0.1.1", - "@backstage/errors": "^0.2.0", + "@backstage/cli-common": "^0.1.8", + "@backstage/errors": "^0.2.2", "@types/fs-extra": "^9.0.1", "@types/node": "^14.14.32", "@types/puppeteer": "^5.4.4", diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index 110863d347..0c9295532a 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/errors +## 0.2.2 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/types@0.1.3 + +## 0.2.1 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/types@0.1.2 + ## 0.2.0 ### Minor Changes diff --git a/packages/errors/package.json b/packages/errors/package.json index f97adef8e1..b1c90f0371 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/errors", "description": "Common utilities for error handling within Backstage", - "version": "0.2.0", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -12,6 +12,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,20 +25,20 @@ "backstage" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/types": "^0.1.1", + "@backstage/types": "^0.1.3", "cross-fetch": "^3.1.5", "serialize-error": "^8.0.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", + "@backstage/cli": "^0.14.0", "@types/jest": "^26.0.7" }, "files": [ diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index ec0457dc1e..1b8f22bcfc 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/integration-react +## 0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration@0.7.5 + +## 0.1.22 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- f0e2f7a56a: Updated the `ScmAuth` error message for missing provider configurations to link to `ScmAuthApi` setup documentation. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/integration@0.7.3 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + ## 0.1.21 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 8413442b3d..27e4492e65 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration-react", "description": "Frontend package for managing integrations towards external systems", - "version": "0.1.21", + "version": "0.1.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,21 +11,24 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/integration": "^0.7.2", - "@backstage/theme": "^0.2.14", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/integration": "^0.7.5", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -35,9 +38,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index f8f53e959b..af764c3b69 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/integration +## 0.7.5 + +### Patch Changes + +- 4e1384884f: Fixed bug in integration package where Self Hosted GitLab instances with custom ports weren't supported (because of the lack of an option to add the port in the integration configs. Now users can add the port directly in the host) + +## 0.7.4 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/config@0.1.15 + +## 0.7.3 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/config@0.1.14 + ## 0.7.2 ### Patch Changes @@ -212,7 +235,7 @@ - 0fd4ea443: Updates the `GithubCredentialsProvider` to return the token type, it can either be `token` or `app` depending on the authentication method. - Update the `GithubOrgReaderProcessor` NOT to query for email addresses if GitHub Apps is used for authentication, this is due to inconsistencies in the GitHub API when using server to server communications and installation tokens. https://github.community/t/api-v4-unable-to-retrieve-email-resource-not-accessible-by-integration/13831/4 for more info. + Update the `GithubOrgReaderProcessor` NOT to query for email addresses if GitHub Apps is used for authentication, this is due to inconsistencies in the GitHub API when using server to server communications and installation tokens. See [this community discussion](https://github.community/t/api-v4-unable-to-retrieve-email-resource-not-accessible-by-integration/13831/4) for more info. **Removes** deprecated GithubOrgReaderProcessor provider configuration(`catalog.processors.githubOrg`). If you're using the deprecated config section make sure to migrate to [integrations](https://backstage.io/docs/integrations/github/locations) instead. diff --git a/packages/integration/package.json b/packages/integration/package.json index d6d0edd77a..bf7c167bfa 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration", "description": "Helpers for managing integrations towards external systems", - "version": "0.7.2", + "version": "0.7.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -12,6 +12,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,15 +25,15 @@ "backstage" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.13", + "@backstage/config": "^0.1.15", "cross-fetch": "^3.1.5", "git-url-parse": "^11.6.0", "@octokit/rest": "^18.5.3", @@ -39,9 +42,9 @@ "lodash": "^4.17.21" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", - "@backstage/config-loader": "^0.9.3", - "@backstage/test-utils": "^0.2.4-next.0", + "@backstage/cli": "^0.14.1", + "@backstage/config-loader": "^0.9.6", + "@backstage/test-utils": "^0.2.6", "@types/jest": "^26.0.7", "@types/luxon": "^2.0.4", "msw": "^0.35.0" diff --git a/packages/integration/src/bitbucket/BitbucketIntegration.test.ts b/packages/integration/src/bitbucket/BitbucketIntegration.test.ts index 81b9be4e25..6bbe1be27b 100644 --- a/packages/integration/src/bitbucket/BitbucketIntegration.test.ts +++ b/packages/integration/src/bitbucket/BitbucketIntegration.test.ts @@ -45,8 +45,10 @@ describe('BitbucketIntegration', () => { expect(integration.title).toBe('h.com'); }); - it('resolves url line number correctly', () => { - const integration = new BitbucketIntegration({ host: 'h.com' } as any); + it('resolves url line number correctly for Bitbucket Cloud', () => { + const integration = new BitbucketIntegration({ + host: 'bitbucket.org', + } as any); expect( integration.resolveUrl({ @@ -55,10 +57,22 @@ describe('BitbucketIntegration', () => { lineNumber: 14, }), ).toBe( - 'https://bitbucket.org/my-owner/my-project/src/master/a.yaml#a.yaml-14', + 'https://bitbucket.org/my-owner/my-project/src/master/a.yaml#lines-14', ); }); + it('resolves url line number correctly for Bitbucket Server', () => { + const integration = new BitbucketIntegration({ host: 'h.com' } as any); + + expect( + integration.resolveUrl({ + url: './a.yaml', + base: 'https://bitbucket.org/my-owner/my-project/src/master/README.md', + lineNumber: 14, + }), + ).toBe('https://bitbucket.org/my-owner/my-project/src/master/a.yaml#14'); + }); + it('resolve edit URL', () => { const integration = new BitbucketIntegration({ host: 'h.com' } as any); diff --git a/packages/integration/src/bitbucket/BitbucketIntegration.ts b/packages/integration/src/bitbucket/BitbucketIntegration.ts index 6c21d716b7..0162463e3c 100644 --- a/packages/integration/src/bitbucket/BitbucketIntegration.ts +++ b/packages/integration/src/bitbucket/BitbucketIntegration.ts @@ -60,17 +60,21 @@ export class BitbucketIntegration implements ScmIntegration { lineNumber?: number; }): string { const resolved = defaultScmResolveUrl(options); - - // Bitbucket line numbers use the syntax #example.txt-42, rather than #L42 - if (options.lineNumber) { - const url = new URL(resolved); - - const filename = url.pathname.split('/').slice(-1)[0]; - url.hash = `${filename}-${options.lineNumber}`; - return url.toString(); + if (!options.lineNumber) { + return resolved; } - return resolved; + const url = new URL(resolved); + + if (this.integrationConfig.host === 'bitbucket.org') { + // Bitbucket Cloud uses the syntax #lines-{start}[:{end}][,...] + url.hash = `lines-${options.lineNumber}`; + } else { + // Bitbucket Server uses the syntax #{start}[-{end}][,...] + url.hash = `${options.lineNumber}`; + } + + return url.toString(); } resolveEditUrl(url: string): string { diff --git a/packages/integration/src/gitlab/config.ts b/packages/integration/src/gitlab/config.ts index f74b849c74..71b4cd868f 100644 --- a/packages/integration/src/gitlab/config.ts +++ b/packages/integration/src/gitlab/config.ts @@ -82,11 +82,7 @@ export function readGitLabIntegrationConfig( baseUrl = `https://${host}`; } - if (host.includes(':')) { - throw new Error( - `Invalid GitLab integration config, host '${host}' should just be the host name (e.g. "github.com"), not a URL`, - ); - } else if (!isValidHost(host)) { + if (!isValidHost(host)) { throw new Error( `Invalid GitLab integration config, '${host}' is not a valid host`, ); diff --git a/packages/integration/src/gitlab/core.ts b/packages/integration/src/gitlab/core.ts index c5b0713340..b379d3d129 100644 --- a/packages/integration/src/gitlab/core.ts +++ b/packages/integration/src/gitlab/core.ts @@ -138,7 +138,7 @@ export async function getProjectId( // Convert // to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FsubgroupA%2FteamA%2Frepo const repoIDLookup = new URL( - `${url.protocol + url.hostname}/api/v4/projects/${encodeURIComponent( + `${url.origin}/api/v4/projects/${encodeURIComponent( repo.replace(/^\//, ''), )}`, ); diff --git a/packages/release-manifests/CHANGELOG.md b/packages/release-manifests/CHANGELOG.md index 61e6c45992..61113e1c76 100644 --- a/packages/release-manifests/CHANGELOG.md +++ b/packages/release-manifests/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/release-manifests +## 0.0.2 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` + ## 0.0.1 ### Patch Changes diff --git a/packages/release-manifests/package.json b/packages/release-manifests/package.json index 8ddbb73bca..7ec69a35e9 100644 --- a/packages/release-manifests/package.json +++ b/packages/release-manifests/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/release-manifests", "description": "Helper library for receiving release manifests", - "version": "0.0.1", + "version": "0.0.2", "private": false, "main": "src/index.ts", "types": "src/index.ts", @@ -11,6 +11,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,18 +25,18 @@ ], "license": "Apache-2.0", "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { "cross-fetch": "^3.1.5" }, "devDependencies": { - "@backstage/test-utils": "^0.2.3", + "@backstage/test-utils": "^0.2.5", "msw": "^0.35.0", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" diff --git a/packages/search-common/CHANGELOG.md b/packages/search-common/CHANGELOG.md index 4460374646..71f2952d90 100644 --- a/packages/search-common/CHANGELOG.md +++ b/packages/search-common/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/search-common +## 0.2.4 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/types@0.1.3 + - @backstage/plugin-permission-common@0.5.1 + +## 0.2.3 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/plugin-permission-common@0.5.0 + - @backstage/types@0.1.2 + ## 0.2.2 ### Patch Changes diff --git a/packages/search-common/package.json b/packages/search-common/package.json index 10b03ab663..ef56017405 100644 --- a/packages/search-common/package.json +++ b/packages/search-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/search-common", "description": "Common functionalities for Search, to be shared between various search-enabled plugins", - "version": "0.2.2", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -25,22 +28,22 @@ "dist" ], "scripts": { - "build": "backstage-cli build --outputs cjs,types", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "bugs": { "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/types": "^0.1.1", - "@backstage/plugin-permission-common": "^0.4.0-next.0" + "@backstage/types": "^0.1.3", + "@backstage/plugin-permission-common": "^0.5.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0" + "@backstage/cli": "^0.14.0" }, "jest": { "roots": [ diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 38726ce10e..1302d1ca9d 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,39 @@ # techdocs-cli-embedded-app +## 0.2.65 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@0.9.0 + - @backstage/cli@0.14.1 + - @backstage/core-components@0.8.10 + - @backstage/plugin-techdocs@0.14.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/app-defaults@0.1.9 + - @backstage/core-app-api@0.5.4 + - @backstage/integration-react@0.1.23 + - @backstage/test-utils@0.2.6 + +## 0.2.64 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.14.0 + - @backstage/core-app-api@0.5.3 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/integration-react@0.1.22 + - @backstage/test-utils@0.2.5 + - @backstage/plugin-catalog@0.8.0 + - @backstage/plugin-techdocs@0.13.4 + - @backstage/app-defaults@0.1.8 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + ## 0.2.63 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 975bfef43b..6719701378 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,21 +1,24 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.63", + "version": "0.2.65", "private": true, + "backstage": { + "role": "frontend" + }, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^0.1.7", - "@backstage/catalog-model": "^0.9.10", - "@backstage/cli": "^0.13.2", - "@backstage/config": "^0.1.13", - "@backstage/core-app-api": "^0.5.2", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/integration-react": "^0.1.21", - "@backstage/plugin-catalog": "^0.7.12", - "@backstage/plugin-techdocs": "^0.13.3", - "@backstage/test-utils": "^0.2.4", - "@backstage/theme": "^0.2.14", + "@backstage/app-defaults": "^0.1.9", + "@backstage/catalog-model": "^0.11.0", + "@backstage/cli": "^0.14.1", + "@backstage/config": "^0.1.15", + "@backstage/core-app-api": "^0.5.4", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/integration-react": "^0.1.23", + "@backstage/plugin-catalog": "^0.9.0", + "@backstage/plugin-techdocs": "^0.14.0", + "@backstage/test-utils": "^0.2.6", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "history": "^5.0.0", @@ -26,7 +29,7 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", @@ -34,16 +37,16 @@ "@types/node": "^14.14.32", "@types/react-dom": "*", "cross-env": "^7.0.0", - "cypress": "^7.3.0", + "cypress": "^9.5.0", "eslint-plugin-cypress": "^2.10.3", "start-server-and-test": "^1.10.11" }, "scripts": { - "start": "backstage-cli app:serve --config ./app-config.yaml", - "build": "backstage-cli app:build --config ./app-config.yaml", - "clean": "backstage-cli clean", - "test": "backstage-cli test", - "lint": "backstage-cli lint", + "start": "backstage-cli package start --config ./app-config.yaml", + "build": "backstage-cli package build --config ./app-config.yaml", + "clean": "backstage-cli package clean", + "test": "backstage-cli package test", + "lint": "backstage-cli package lint", "test:e2e": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:dev", "test:e2e:ci": "cross-env PORT=3001 start-server-and-test start http://localhost:3001 cy:run", "cy:dev": "cypress open", diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx index 65e75a488a..249394fdc4 100644 --- a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx @@ -14,18 +14,20 @@ * limitations under the License. */ -import React, { useContext, PropsWithChildren } from 'react'; +import React, { PropsWithChildren, useContext } from 'react'; + import { Link, makeStyles } from '@material-ui/core'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; import LogoFull from './LogoFull'; import LogoIcon from './LogoIcon'; + import { Sidebar, + SidebarItem, SidebarPage, sidebarConfig, - SidebarContext, - SidebarItem, SidebarDivider, + SidebarContext, } from '@backstage/core-components'; import { NavLink } from 'react-router-dom'; @@ -70,7 +72,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( {/* Global nav, not org-specific */} {/* End global nav */} diff --git a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx index b90a49fb22..d9a518c165 100644 --- a/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/TechDocsPage/TechDocsPage.tsx @@ -14,7 +14,22 @@ * limitations under the License. */ -import React from 'react'; +import React, { + FC, + createContext, + useContext, + useState, + useCallback, +} from 'react'; + +import { Theme, makeStyles } from '@material-ui/core'; + +import { ThemeProvider, Box, Tooltip, IconButton } from '@material-ui/core'; +import LightIcon from '@material-ui/icons/Brightness7'; +import DarkIcon from '@material-ui/icons/Brightness4'; + +import { lightTheme, darkTheme } from '@backstage/theme'; +import { EntityName } from '@backstage/catalog-model'; import { Content } from '@backstage/core-components'; @@ -24,6 +39,106 @@ import { TechDocsPageHeader, } from '@backstage/plugin-techdocs'; +const useStyles = makeStyles((theme: Theme) => ({ + headerIcon: { + color: theme.palette.common.white, + width: '32px', + height: '32px', + }, + content: { + backgroundColor: theme.palette.background.default, + }, + contentToolbar: { + display: 'flex', + justifyContent: 'flex-end', + padding: 0, + }, +})); + +enum Themes { + LIGHT = 'light', + DARK = 'dark', +} + +type TechDocsThemeValue = { + theme: Themes; + toggleTheme: () => void; +}; + +const TechDocsThemeContext = createContext({ + theme: Themes.LIGHT, + toggleTheme: () => {}, +}); + +const TechdocsThemeProvider: FC = ({ children }) => { + const [theme, setTheme] = useState(Themes.LIGHT); + + const toggleTheme = useCallback(() => { + setTheme(prevTheme => + prevTheme === Themes.LIGHT ? Themes.DARK : Themes.LIGHT, + ); + }, [setTheme]); + + const value = { theme, toggleTheme }; + + const themes = { + [Themes.LIGHT]: lightTheme, + [Themes.DARK]: darkTheme, + }; + + return ( + + {children} + + ); +}; + +const useTechDocsTheme = () => useContext(TechDocsThemeContext); + +const TechDocsThemeToggle = () => { + const classes = useStyles(); + const { theme, toggleTheme } = useTechDocsTheme(); + + const themes = { + [Themes.LIGHT]: { + icon: DarkIcon, + title: 'Dark theme', + }, + [Themes.DARK]: { + icon: LightIcon, + title: 'Light theme', + }, + }; + + const { title, icon: Icon } = themes[theme]; + + return ( + + + + + + + + ); +}; + +const TechDocsPageContent = ({ + onReady, + entityRef, +}: { + entityRef: EntityName; + onReady: () => void; +}) => { + const classes = useStyles(); + + return ( + + + + ); +}; + const DefaultTechDocsPage = () => { const techDocsMetadata = { site_name: 'Live preview environment', @@ -35,20 +150,20 @@ const DefaultTechDocsPage = () => { {({ entityRef, onReady }) => ( <> - - - + techDocsMetadata={techDocsMetadata} + > + + + )} ); }; -export const techDocsPage = ; +export const techDocsPage = ( + + + +); diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 98015f766a..bc23499662 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,31 @@ # @techdocs/cli +## 0.8.15 + +### Patch Changes + +- ed78516480: chore(deps-dev): bump `cypress` from 7.3.0 to 9.5.0 +- 209fd128e6: Updated usage of `github:` location types in docs to use `url:` instead. +- 61ff215e08: - Adds `cypress` and `cypress-plugin-snapshots` as dependencies for integration and visual regression tests. + - Updates README documentation with instructions for how to run tests. + - Clarifies output text for prepack script. +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/techdocs-common@0.11.10 + +## 0.8.14 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/catalog-model@0.10.0 + - @backstage/cli-common@0.1.7 + - @backstage/config@0.1.14 + - @backstage/techdocs-common@0.11.8 + ## 0.8.13 ### Patch Changes diff --git a/packages/techdocs-cli/README.md b/packages/techdocs-cli/README.md index 3ac423cb38..ae296d2c26 100644 --- a/packages/techdocs-cli/README.md +++ b/packages/techdocs-cli/README.md @@ -17,7 +17,7 @@ bundle into the `packages/techdocs-cli/dist` which is then published with the ```sh # From the root of this repository run # NOTE: This will build the techdocs-cli-embedded-app and copy the output into the cli dist directory -yarn build --scope @techdocs/cli +yarn workspace @techdocs/cli build # Now execute the binary packages/techdocs-cli/bin/techdocs-cli @@ -40,8 +40,25 @@ yarn start yarn techdocs-cli:dev [...options] ``` +### Using an example docs project + +For the purpose of local development, we have created an example documentation project. You are of course also free to create your own local test site - all it takes is a `docs/index.md` and an `mkdocs.yml` in a directory. + +```sh + +cd packages/techdocs-cli/src/example-docs + +# To get a view of your docs in Backstage, use: +techdocs-cli serve + +# To view the raw mkdocs site (without Backstage), use: +techdocs-cli serve:mkdocs +``` + ### Testing +#### E2E tests + Running unit tests requires mkdocs to be installed locally: ```sh @@ -51,18 +68,37 @@ pip install mkdocs-techdocs-core Then run `yarn test`. -### Use an example docs project +#### Cypress (Integration and Visual regression) tests -We have created an [example documentation project](https://github.com/backstage/techdocs-container/tree/main/mock-docs) and it's shipped with [techdocs-container](https://github.com/backstage/techdocs-container) repository, for the purpose of local development. But you are free to create your own local test site. All it takes is a `docs/index.md` and `mkdocs.yml` in a directory. +Running cypress tests requires you to run the CLI locally against our example docs. + +Run the local version of techdocs-cli against the example docs: ```sh -git clone https://github.com/backstage/techdocs-container.git +# From the root of this repository run +# NOTE: This will build the techdocs-cli-embedded-app and copy the output into the cli dist directory +yarn build --scope @techdocs/cli -cd techdocs-container/mock-docs +# Navigate to the example project +cd packages/techdocs-cli/src/example-docs -# To get a view of your docs in Backstage, use: -techdocs-cli serve - -# To view the raw mkdocs site (without Backstage), use: -techdocs-cli serve:mkdocs +# Now execute the techdocs-cli serve command +../../bin/techdocs-cli serve ``` + +In another shell, run the cypress tests: + +```sh +# From the root of the project, navigate to the techdocs-cli package +cd packages/techdocs-cli + +# Run tests +yarn test:cypress +``` + +This will launch a cypress app where you can run the two different tests: + +- `backstage_serve` - will run against the backstage server +- `mkdocs_serve` - will run test against the mkdocs server + +> If its the first time you run Cypress, it will run a "Verifying Cypress can run" step. This step can result in a "Cypress verification timed out" error. If that is the case, let the verification step run and then run the command again and it should succeed. diff --git a/packages/techdocs-cli/cypress.json b/packages/techdocs-cli/cypress.json new file mode 100644 index 0000000000..b6f3b96019 --- /dev/null +++ b/packages/techdocs-cli/cypress.json @@ -0,0 +1,17 @@ +{ + "env": { + "mkDocsBaseUrl": "http://localhost:8000", + "backstageBaseUrl": "http://localhost:3000", + "cypress-plugin-snapshots": { + "autoCleanUp": false, + "imageConfig": { + "resizeDevicePixelRatio": true, + "threshold": 0.01 + } + } + }, + "viewportWidth": 1920, + "viewportHeight": 1080, + "includeShadowDom": true, + "ignoreTestFiles": ["**/__snapshots__/*", "**/__image_snapshots__/*"] +} diff --git a/packages/techdocs-cli/cypress/.eslintrc.json b/packages/techdocs-cli/cypress/.eslintrc.json new file mode 100644 index 0000000000..2481ac5715 --- /dev/null +++ b/packages/techdocs-cli/cypress/.eslintrc.json @@ -0,0 +1,21 @@ +{ + "plugins": ["cypress"], + "extends": ["plugin:cypress/recommended"], + "rules": { + "jest/expect-expect": [ + "error", + { + "assertFunctionNames": ["expect", "cy.contains", "cy.document"] + } + ], + "import/no-extraneous-dependencies": [ + "error", + { + "devDependencies": true, + "optionalDependencies": true, + "peerDependencies": true, + "bundledDependencies": true + } + ] + } +} diff --git a/packages/techdocs-cli/cypress/fixtures/example.json b/packages/techdocs-cli/cypress/fixtures/example.json new file mode 100644 index 0000000000..02e4254378 --- /dev/null +++ b/packages/techdocs-cli/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png b/packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png new file mode 100644 index 0000000000..99fc1ea483 Binary files /dev/null and b/packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png differ diff --git a/packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - MkDocs Serve toMatchImageSnapshot - MkDocs Page #0.png b/packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - MkDocs Serve toMatchImageSnapshot - MkDocs Page #0.png new file mode 100644 index 0000000000..e6c63d5d57 Binary files /dev/null and b/packages/techdocs-cli/cypress/integration/__image_snapshots__/TechDocs Live Preview - MkDocs Serve toMatchImageSnapshot - MkDocs Page #0.png differ diff --git a/packages/techdocs-cli/cypress/integration/backstage_serve.js b/packages/techdocs-cli/cypress/integration/backstage_serve.js new file mode 100644 index 0000000000..15fc03f8f4 --- /dev/null +++ b/packages/techdocs-cli/cypress/integration/backstage_serve.js @@ -0,0 +1,58 @@ +/* + * Copyright 2022 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. + */ +/// +describe('TechDocs Live Preview - Backstage Serve', () => { + it('successfully serves documentation', () => { + cy.visit(`${Cypress.env('backstageBaseUrl')}/docs/default/component/local`); + cy.contains('hello mock docs'); + }); + + it('successfully navigates to sub page of documentation', () => { + cy.contains('SubDocs').click(); + cy.contains('Home 2').click(); + cy.contains( + 'This is an md file in another docs folder using the MkDocs Monorepo Plugin', + ); + }); + + it('successfully renders all Backstage main elements', () => { + cy.contains('header', 'Live preview environment'); + cy.get('[data-testid="sidebar-root"]') + .children() + .should('have.length.gt', 0); + }); + + it('successfully renders all extracted MkDocs main elements', () => { + // as it gets replaced by Backstage header + cy.get('.md-header').should('have.length', 0); + cy.get('.md-main').should('have.length', 1); + cy.contains( + '.md-main', + 'This is an md file in another docs folder using the MkDocs Monorepo Plugin', + ); + cy.get('.md-sidebar.md-sidebar--primary').should('have.length', 1); + cy.get('.md-sidebar.md-sidebar--primary').should('have.length', 1); + cy.get('.md-footer').should('have.length', 1); + }); + + it('toMatchImageSnapshot - Backstage TechDocs Page', () => { + cy.visit( + `${Cypress.env('backstageBaseUrl')}/docs/default/component/local`, + ).then(() => { + cy.document().toMatchImageSnapshot(); + }); + }); +}); diff --git a/packages/techdocs-cli/cypress/integration/mkdocs_serve.js b/packages/techdocs-cli/cypress/integration/mkdocs_serve.js new file mode 100644 index 0000000000..1ace7298ce --- /dev/null +++ b/packages/techdocs-cli/cypress/integration/mkdocs_serve.js @@ -0,0 +1,48 @@ +/* + * Copyright 2022 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. + */ +/// +describe('TechDocs Live Preview - MkDocs Serve', () => { + it('successfully serves documentation', () => { + cy.visit(Cypress.env('mkDocsBaseUrl')); + cy.contains('hello mock docs'); + }); + + it('successfully navigates to sub page of documentation', () => { + cy.contains('SubDocs').click(); + cy.contains('Home 2').click(); + cy.contains( + 'This is an md file in another docs folder using the MkDocs Monorepo Plugin', + ); + }); + + it('successfully renders all main elements', () => { + cy.get('.md-header').should('have.length', 1); + cy.get('.md-main').should('have.length', 1); + cy.contains( + '.md-main', + 'This is an md file in another docs folder using the MkDocs Monorepo Plugin', + ); + cy.get('.md-sidebar.md-sidebar--primary').should('have.length', 1); + cy.get('.md-sidebar.md-sidebar--primary').should('have.length', 1); + cy.get('.md-footer').should('have.length', 1); + }); + + it('toMatchImageSnapshot - MkDocs Page', () => { + cy.visit(Cypress.env('mkDocsBaseUrl')).then(() => { + cy.document().toMatchImageSnapshot(); + }); + }); +}); diff --git a/packages/techdocs-cli/cypress/plugins/index.js b/packages/techdocs-cli/cypress/plugins/index.js new file mode 100644 index 0000000000..92faabb6ad --- /dev/null +++ b/packages/techdocs-cli/cypress/plugins/index.js @@ -0,0 +1,41 @@ +/* + * Copyright 2022 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. + */ + +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +const { initPlugin } = require('cypress-plugin-snapshots/plugin'); + +/** + * @type {Cypress.PluginConfig} + */ +// eslint-disable-next-line no-unused-vars +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config + initPlugin(on, config); + return config; +}; diff --git a/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage Header #0.png b/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage Header #0.png new file mode 100644 index 0000000000..867e8ddd81 Binary files /dev/null and b/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage Header #0.png differ diff --git a/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0 (1).png b/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0 (1).png new file mode 100644 index 0000000000..3f1078345d Binary files /dev/null and b/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0 (1).png differ diff --git a/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0 (2).png b/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0 (2).png new file mode 100644 index 0000000000..c576be51c5 Binary files /dev/null and b/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0 (2).png differ diff --git a/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png b/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png new file mode 100644 index 0000000000..3f1078345d Binary files /dev/null and b/packages/techdocs-cli/cypress/screenshots/backstage_serve.js/TechDocs Live Preview - Backstage Serve toMatchImageSnapshot - Backstage TechDocs Page #0.png differ diff --git a/packages/techdocs-cli/cypress/support/commands.js b/packages/techdocs-cli/cypress/support/commands.js new file mode 100644 index 0000000000..a8d6a5fdb7 --- /dev/null +++ b/packages/techdocs-cli/cypress/support/commands.js @@ -0,0 +1,40 @@ +/* + * Copyright 2022 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. + */ + +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) diff --git a/packages/techdocs-cli/cypress/support/index.js b/packages/techdocs-cli/cypress/support/index.js new file mode 100644 index 0000000000..a1a5b10ea4 --- /dev/null +++ b/packages/techdocs-cli/cypress/support/index.js @@ -0,0 +1,36 @@ +/* + * Copyright 2022 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. + */ + +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; +import 'cypress-plugin-snapshots/commands'; + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 19514b63d2..929564e01c 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,11 +1,14 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "0.8.13", + "version": "0.8.15", "private": false, "publishConfig": { "access": "public" }, + "backstage": { + "role": "cli" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,19 +24,20 @@ "types": "", "scripts": { "start": "nodemon --", - "build": "backstage-cli build --outputs cjs", - "clean": "backstage-cli clean", - "lint": "backstage-cli lint", - "test": "backstage-cli test --testPathIgnorePatterns=src/e2e.test.ts", + "build": "backstage-cli package build", + "clean": "backstage-cli package clean", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test --testPathIgnorePatterns=src/e2e.test.ts", "test:e2e": "backstage-cli test src/e2e.test.ts", "test:e2e:ci": "backstage-cli test --watchAll=false --ci src/e2e.test.ts", + "test:cypress": "cypress open", "prepack": "./scripts/prepack.sh" }, "bin": { "techdocs-cli": "bin/techdocs-cli" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/commander": "^2.12.2", "@types/fs-extra": "^9.0.6", "@types/http-proxy": "^1.17.4", @@ -41,9 +45,11 @@ "@types/node": "^14.14.32", "@types/serve-handler": "^6.1.0", "@types/webpack-env": "^1.15.3", - "techdocs-cli-embedded-app": "link:../techdocs-cli-embedded-app", + "cypress": "^9.5.0", + "cypress-plugin-snapshots": "^1.4.4", "find-process": "^1.4.5", "nodemon": "^2.0.2", + "techdocs-cli-embedded-app": "link:../techdocs-cli-embedded-app", "ts-node": "^10.0.0" }, "files": [ @@ -56,11 +62,11 @@ "ext": "ts" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-model": "^0.9.10", - "@backstage/cli-common": "^0.1.6", - "@backstage/config": "^0.1.13", - "@backstage/techdocs-common": "^0.11.7", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-model": "^0.11.0", + "@backstage/cli-common": "^0.1.8", + "@backstage/config": "^0.1.15", + "@backstage/techdocs-common": "^0.11.10", "@types/dockerode": "^3.3.0", "commander": "^6.1.0", "dockerode": "^3.3.1", diff --git a/packages/techdocs-cli/scripts/prepack.sh b/packages/techdocs-cli/scripts/prepack.sh index 6b769de37c..bfd62cfd1b 100755 --- a/packages/techdocs-cli/scripts/prepack.sh +++ b/packages/techdocs-cli/scripts/prepack.sh @@ -23,4 +23,4 @@ TECHDOCS_CLI_EMBEDDED_APP_DIR="$TECHDOCS_CLI_DIR"/../techdocs-cli-embedded-app echo "🚚 Copying embedded app into dist/embedded-app" rm -rf "$TECHDOCS_CLI_DIR"/dist/embedded-app cp -r "$TECHDOCS_CLI_EMBEDDED_APP_DIR"/dist "$TECHDOCS_CLI_DIR"/dist/embedded-app -echo "🏁 Ready!" +echo "🏁 Finished copying embedded app into dist/embedded-app!" diff --git a/packages/techdocs-cli/src/commands/index.ts b/packages/techdocs-cli/src/commands/index.ts index d4f41cf7a3..115fe8e364 100644 --- a/packages/techdocs-cli/src/commands/index.ts +++ b/packages/techdocs-cli/src/commands/index.ts @@ -45,7 +45,7 @@ export function registerCommands(program: CommanderStatic) { ) .option( '--techdocs-ref ', - 'The repository hosting documentation source files e.g. github:https://ghe.mycompany.net.com/org/repo.' + + 'The repository hosting documentation source files e.g. url:https://ghe.mycompany.net.com/org/repo.' + '\nThis value is same as the backstage.io/techdocs-ref annotation of the corresponding Backstage entity.' + '\nIt is completely fine to skip this as it is only being used to set repo_url in mkdocs.yml if not found.\n', ) diff --git a/packages/techdocs-cli/src/e2e.test.ts b/packages/techdocs-cli/src/e2e.test.ts index 088dbc44b8..7cdd867d0f 100644 --- a/packages/techdocs-cli/src/e2e.test.ts +++ b/packages/techdocs-cli/src/e2e.test.ts @@ -58,7 +58,7 @@ const timeout = 25000; jest.setTimeout(timeout * 2); describe('end-to-end', () => { - const cwd = path.resolve(__dirname, 'fixture'); + const cwd = path.resolve(__dirname, 'example-docs'); afterEach(async () => { // On Windows the pid of a spawned process may be wrong diff --git a/packages/techdocs-cli/src/example-docs/.gitignore b/packages/techdocs-cli/src/example-docs/.gitignore new file mode 100644 index 0000000000..45ddf0ae39 --- /dev/null +++ b/packages/techdocs-cli/src/example-docs/.gitignore @@ -0,0 +1 @@ +site/ diff --git a/packages/techdocs-cli/src/example-docs/docs/index.md b/packages/techdocs-cli/src/example-docs/docs/index.md new file mode 100644 index 0000000000..074d3ccb3e --- /dev/null +++ b/packages/techdocs-cli/src/example-docs/docs/index.md @@ -0,0 +1,92 @@ +## hello mock docs + +!!! test +Testing something + +Abbreviations: +Some text about MOCDOC + +This is a paragraph. +{: #test_id .test_class } + +Apple +: A fruit. + +```javascript +import { test } from 'something'; + +const addThingToThing = (a, b) a + b; +``` + +- [abc](#abc) +- [xyz](#xyz) + +## abc + +This is a b c. + +## xyz + +This is x y z. + +# Emojis + +:bulb: :smile: + +# Code blocks + +```javascript +import { test } from 'something'; + +const addThingToThing = (a, b) a + b; +``` + +# Grouped Code blocks + +=== "JavaScript" + + ```javascript + import { test } from 'something'; + + const addThingToThing = (a, b) a + b; + ``` + +=== "Java" + + ```java + public void function() { + test(); + } + ``` + +```java tab="java" + public void function() { + test(); + } +``` + +```java tab="java 2" + public void function() { + test(); + } +``` + +# MDX truly sane lists + +- attributes + +- customer + - first_name + - test + - family_name + - email +- person + - first_name + - family_name + - birth_date +- subscription_id + +- request + + +*[MOCDOC]: Mock Documentation diff --git a/packages/techdocs-cli/src/fixture/mkdocs.yml b/packages/techdocs-cli/src/example-docs/mkdocs.yml similarity index 50% rename from packages/techdocs-cli/src/fixture/mkdocs.yml rename to packages/techdocs-cli/src/example-docs/mkdocs.yml index 5d5c2a02ae..9c9406f6e6 100644 --- a/packages/techdocs-cli/src/fixture/mkdocs.yml +++ b/packages/techdocs-cli/src/example-docs/mkdocs.yml @@ -2,7 +2,9 @@ site_name: docs-test-fixture site_description: Documentation site test fixture nav: - - HOME: README.md + - Home: index.md + - SubDocs: '!include ./sub-docs/mkdocs.yml' + - Plugins: '*include ./plugins/*/mkdocs.yml' plugins: - techdocs-core diff --git a/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md b/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md new file mode 100644 index 0000000000..bd8bc3a4ea --- /dev/null +++ b/packages/techdocs-cli/src/example-docs/plugins/plugin-a/docs/index.md @@ -0,0 +1,4 @@ +# Plugin A + +This is a description of Plugin A. This file exists to prove that glob'd +includes using the `*include` syntax work as expected. diff --git a/packages/techdocs-cli/src/example-docs/plugins/plugin-a/mkdocs.yml b/packages/techdocs-cli/src/example-docs/plugins/plugin-a/mkdocs.yml new file mode 100644 index 0000000000..66407d5391 --- /dev/null +++ b/packages/techdocs-cli/src/example-docs/plugins/plugin-a/mkdocs.yml @@ -0,0 +1,5 @@ +site_name: Plugin A +site_description: A description of Plugin A + +nav: + - Introduction: index.md diff --git a/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md b/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md new file mode 100644 index 0000000000..93e545252f --- /dev/null +++ b/packages/techdocs-cli/src/example-docs/plugins/plugin-b/docs/index.md @@ -0,0 +1,4 @@ +# Plugin B + +This is a description of Plugin B. This file exists to prove that glob'd +includes using the `*include` syntax work as expected. diff --git a/packages/techdocs-cli/src/example-docs/plugins/plugin-b/mkdocs.yml b/packages/techdocs-cli/src/example-docs/plugins/plugin-b/mkdocs.yml new file mode 100644 index 0000000000..81580c33af --- /dev/null +++ b/packages/techdocs-cli/src/example-docs/plugins/plugin-b/mkdocs.yml @@ -0,0 +1,5 @@ +site_name: Plugin B +site_description: A description of Plugin B + +nav: + - Introduction: index.md diff --git a/packages/techdocs-cli/src/example-docs/sub-docs/docs/index.md b/packages/techdocs-cli/src/example-docs/sub-docs/docs/index.md new file mode 100644 index 0000000000..65c6644ef8 --- /dev/null +++ b/packages/techdocs-cli/src/example-docs/sub-docs/docs/index.md @@ -0,0 +1 @@ +### This is an md file in another docs folder using the [MkDocs Monorepo Plugin](https://github.com/spotify/mkdocs-monorepo-plugin) diff --git a/packages/techdocs-cli/src/example-docs/sub-docs/mkdocs.yml b/packages/techdocs-cli/src/example-docs/sub-docs/mkdocs.yml new file mode 100644 index 0000000000..4490ddbefa --- /dev/null +++ b/packages/techdocs-cli/src/example-docs/sub-docs/mkdocs.yml @@ -0,0 +1,4 @@ +site_name: subdocs + +nav: + - Home 2: 'index.md' diff --git a/packages/techdocs-cli/src/fixture/docs/README.md b/packages/techdocs-cli/src/fixture/docs/README.md deleted file mode 100644 index c32f73f6f4..0000000000 --- a/packages/techdocs-cli/src/fixture/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -# Test Fixture diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 44fbec4171..c99295e8db 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/techdocs-common +## 0.11.10 + +### Patch Changes + +- 209fd128e6: Updated usage of `github:` location types in docs to use `url:` instead. +- 13ef228d03: Clean up the API interface for TechDocs common library. +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/integration@0.7.5 + +## 0.11.9 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/integration@0.7.4 + - @backstage/search-common@0.2.4 + +## 0.11.8 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 216725b434: Updated to use new names for `parseLocationRef` and `stringifyLocationRef` +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/search-common@0.2.3 + ## 0.11.7 ### Patch Changes diff --git a/packages/techdocs-common/README.md b/packages/techdocs-common/README.md index e4889d6f79..bbaac85e9b 100644 --- a/packages/techdocs-common/README.md +++ b/packages/techdocs-common/README.md @@ -39,7 +39,7 @@ Currently the build process is split up in these three stages. - Generators - Publishers -Preparers read your entity data and creates a working directory with your documentation source code. For example if you have set your `backstage.io/techdocs-ref` to `github:https://github.com/backstage/backstage.git` it will clone that repository to a temp folder and pass that on to the generator. +Preparers read your entity data and creates a working directory with your documentation source code. For example if you have set your `backstage.io/techdocs-ref` to `url:https://github.com/backstage/backstage.git` it will clone that repository to a temp folder and pass that on to the generator. Generators takes the prepared source and runs the `techdocs-container` on it. It then passes on the output folder of that build to the publisher. diff --git a/packages/techdocs-common/api-report.md b/packages/techdocs-common/api-report.md index 281accca30..7c4fc95abb 100644 --- a/packages/techdocs-common/api-report.md +++ b/packages/techdocs-common/api-report.md @@ -17,38 +17,37 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { UrlReader } from '@backstage/backend-common'; import { Writable } from 'stream'; -// Warning: (ae-missing-release-tag) "DirectoryPreparer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class DirectoryPreparer implements PreparerBase { - constructor(config: Config, _logger: Logger_2, reader: UrlReader); - // Warning: (ae-forgotten-export) The symbol "PreparerResponse" needs to be exported by the entry point index.d.ts - // - // (undocumented) - prepare( - entity: Entity, - options?: { - logger?: Logger_2; - etag?: string; - }, - ): Promise; + // @deprecated + constructor(config: Config, _logger: Logger_2 | null, reader: UrlReader); + static fromConfig( + config: Config, + { logger, reader }: PreparerConfig, + ): DirectoryPreparer; + prepare(entity: Entity, options?: PreparerOptions): Promise; } -// Warning: (ae-missing-release-tag) "GeneratorBase" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type ETag = string; + +// @public export type GeneratorBase = { run(opts: GeneratorRunOptions): Promise; }; -// Warning: (ae-missing-release-tag) "GeneratorBuilder" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type GeneratorBuilder = { register(protocol: SupportedGeneratorKey, generator: GeneratorBase): void; get(entity: Entity): GeneratorBase; }; +// @public +export type GeneratorOptions = { + containerRunner: ContainerRunner; + logger: Logger_2; +}; + // @public export type GeneratorRunOptions = { inputDir: string; @@ -59,11 +58,8 @@ export type GeneratorRunOptions = { logStream?: Writable; }; -// Warning: (ae-missing-release-tag) "Generators" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class Generators implements GeneratorBuilder { - // (undocumented) static fromConfig( config: Config, options: { @@ -71,15 +67,11 @@ export class Generators implements GeneratorBuilder { containerRunner: ContainerRunner; }, ): Promise; - // (undocumented) get(entity: Entity): GeneratorBase; - // (undocumented) register(generatorKey: SupportedGeneratorKey, generator: GeneratorBase): void; } -// Warning: (ae-missing-release-tag) "getDocFilesFromRepository" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const getDocFilesFromRepository: ( reader: UrlReader, entity: Entity, @@ -91,78 +83,74 @@ export const getDocFilesFromRepository: ( | undefined, ) => Promise; -// Warning: (ae-missing-release-tag) "getLocationForEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const getLocationForEntity: ( entity: Entity, scmIntegration: ScmIntegrationRegistry, ) => ParsedLocationAnnotation; -// Warning: (ae-missing-release-tag) "ParsedLocationAnnotation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type MigrateRequest = { + removeOriginal?: boolean; + concurrency?: number; +}; + +// @public export type ParsedLocationAnnotation = { type: RemoteProtocol; target: string; }; -// Warning: (ae-missing-release-tag) "parseReferenceAnnotation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const parseReferenceAnnotation: ( annotationName: string, entity: Entity, ) => ParsedLocationAnnotation; -// Warning: (ae-missing-release-tag) "PreparerBase" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type PreparerBase = { - prepare( - entity: Entity, - options?: { - logger?: Logger_2; - etag?: string; - }, - ): Promise; + prepare(entity: Entity, options?: PreparerOptions): Promise; }; -// Warning: (ae-missing-release-tag) "PreparerBuilder" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type PreparerBuilder = { register(protocol: RemoteProtocol, preparer: PreparerBase): void; get(entity: Entity): PreparerBase; }; -// Warning: (ae-missing-release-tag) "Preparers" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type PreparerConfig = { + logger: Logger_2; + reader: UrlReader; +}; + +// @public +export type PreparerOptions = { + logger?: Logger_2; + etag?: ETag; +}; + +// @public +export type PreparerResponse = { + preparedDir: string; + etag: ETag; +}; + +// @public export class Preparers implements PreparerBuilder { - // Warning: (ae-forgotten-export) The symbol "factoryOptions" needs to be exported by the entry point index.d.ts - // - // (undocumented) static fromConfig( - config: Config, - { logger, reader }: factoryOptions, + backstageConfig: Config, + { logger, reader }: PreparerConfig, ): Promise; - // (undocumented) get(entity: Entity): PreparerBase; - // (undocumented) register(protocol: RemoteProtocol, preparer: PreparerBase): void; } -// Warning: (ae-missing-release-tag) "Publisher" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class Publisher { - // Warning: (ae-forgotten-export) The symbol "factoryOptions" needs to be exported by the entry point index.d.ts - // - // (undocumented) static fromConfig( config: Config, - { logger, discovery }: factoryOptions_2, + { logger, discovery }: PublisherFactory, ): Promise; } @@ -172,15 +160,16 @@ export interface PublisherBase { fetchTechDocsMetadata(entityName: EntityName): Promise; getReadiness(): Promise; hasDocsBeenGenerated(entityName: Entity): Promise; - // Warning: (ae-forgotten-export) The symbol "MigrateRequest" needs to be exported by the entry point index.d.ts migrateDocsCase?(migrateRequest: MigrateRequest): Promise; - // Warning: (ae-forgotten-export) The symbol "PublishRequest" needs to be exported by the entry point index.d.ts - // Warning: (ae-forgotten-export) The symbol "PublishResponse" needs to be exported by the entry point index.d.ts publish(request: PublishRequest): Promise; } -// Warning: (ae-missing-release-tag) "PublisherType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public +export type PublisherFactory = { + logger: Logger_2; + discovery: PluginEndpointDiscovery; +}; + // @public export type PublisherType = | 'local' @@ -189,37 +178,40 @@ export type PublisherType = | 'azureBlobStorage' | 'openStackSwift'; +// @public +export type PublishRequest = { + entity: Entity; + directory: string; +}; + +// @public +export type PublishResponse = { + remoteUrl?: string; + objects?: string[]; +} | void; + // @public export type ReadinessResponse = { isAvailable: boolean; }; -// Warning: (ae-missing-release-tag) "RemoteProtocol" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type RemoteProtocol = 'url' | 'dir'; -// Warning: (ae-missing-release-tag) "TechDocsDocument" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type SupportedGeneratorKey = 'techdocs' | string; + +// @public export interface TechDocsDocument extends IndexableDocument { - // (undocumented) kind: string; - // (undocumented) lifecycle: string; - // (undocumented) name: string; - // (undocumented) namespace: string; - // (undocumented) owner: string; - // (undocumented) path: string; } -// Warning: (ae-missing-release-tag) "TechdocsGenerator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class TechdocsGenerator implements GeneratorBase { constructor(options: { logger: Logger_2; @@ -227,21 +219,14 @@ export class TechdocsGenerator implements GeneratorBase { config: Config; scmIntegrations: ScmIntegrationRegistry; }); - static readonly defaultDockerImage = 'spotify/techdocs:v0.3.6'; - // (undocumented) + static readonly defaultDockerImage = 'spotify/techdocs:v0.3.7'; static fromConfig( config: Config, - options: { - containerRunner: ContainerRunner; - logger: Logger_2; - }, + options: GeneratorOptions, ): TechdocsGenerator; - // (undocumented) run(options: GeneratorRunOptions): Promise; } -// Warning: (ae-missing-release-tag) "TechDocsMetadata" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type TechDocsMetadata = { site_name: string; @@ -251,8 +236,6 @@ export type TechDocsMetadata = { files?: string[]; }; -// Warning: (ae-missing-release-tag) "transformDirLocation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const transformDirLocation: ( entity: Entity, @@ -263,21 +246,11 @@ export const transformDirLocation: ( target: string; }; -// Warning: (ae-missing-release-tag) "UrlPreparer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class UrlPreparer implements PreparerBase { + // @deprecated constructor(reader: UrlReader, logger: Logger_2); - // (undocumented) - prepare( - entity: Entity, - options?: { - etag?: string; - }, - ): Promise; + static fromConfig({ reader, logger }: PreparerConfig): UrlPreparer; + prepare(entity: Entity, options?: PreparerOptions): Promise; } - -// Warnings were encountered during analysis: -// -// src/stages/generate/types.d.ts:45:5 - (ae-forgotten-export) The symbol "SupportedGeneratorKey" needs to be exported by the entry point index.d.ts ``` diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index f45f842100..731e967637 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.11.7", + "version": "0.11.10", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "node-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -25,12 +28,13 @@ "dist" ], "scripts": { - "build": "backstage-cli build --outputs cjs,types", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "bugs": { "url": "https://github.com/backstage/backstage/issues" @@ -38,12 +42,12 @@ "dependencies": { "@azure/identity": "^2.0.1", "@azure/storage-blob": "^12.5.0", - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/search-common": "^0.2.2", - "@backstage/integration": "^0.7.2", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/search-common": "^0.2.4", + "@backstage/integration": "^0.7.5", "@google-cloud/storage": "^5.6.0", "@trendyol-js/openstack-swift-sdk": "^0.0.5", "@types/express": "^4.17.6", @@ -60,7 +64,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/fs-extra": "^9.0.5", "@types/js-yaml": "^4.0.0", "@types/mime-types": "^2.1.0", diff --git a/packages/techdocs-common/src/helpers.ts b/packages/techdocs-common/src/helpers.ts index ccf1256381..39a1f4c666 100644 --- a/packages/techdocs-common/src/helpers.ts +++ b/packages/techdocs-common/src/helpers.ts @@ -26,11 +26,21 @@ import path from 'path'; import { Logger } from 'winston'; import { PreparerResponse, RemoteProtocol } from './stages/prepare/types'; +/** + * Parsed location annotation + * @public + */ export type ParsedLocationAnnotation = { type: RemoteProtocol; target: string; }; +/** + * Returns a parset locations annotation + * @public + * @param annotationName - The name of the annotation in the entity metadata + * @param entity - A TechDocs entity instance + */ export const parseReferenceAnnotation = ( annotationName: string, entity: Entity, @@ -56,7 +66,7 @@ export const parseReferenceAnnotation = ( * location, it returns a `url` location with a resolved target that points to the * targeted subfolder. If the entity was registered by a `file` location, it returns * an absolute `dir` location. - * + * @public * @param entity - the entity with annotations * @param dirAnnotation - the parsed techdocs-ref annotation of type 'dir' * @param scmIntegrations - access to the scmIntegration to do url transformations @@ -101,6 +111,12 @@ export const transformDirLocation = ( } }; +/** + * Returns a entity reference based on the TechDocs annotation type + * @public + * @param entity - A TechDocs instance + * @param scmIntegration - An implementation for SCM integration API + */ export const getLocationForEntity = ( entity: Entity, scmIntegration: ScmIntegrationRegistry, @@ -120,6 +136,13 @@ export const getLocationForEntity = ( } }; +/** + * Returns a preparer response {@link PreparerResponse} + * @public + * @param reader - Read a tree of files from a repository + * @param entity - A TechDocs entity instance + * @param opts - Options for configuring the reader, e.g. logger, etag, etc. + */ export const getDocFilesFromRepository = async ( reader: UrlReader, entity: Entity, diff --git a/packages/techdocs-common/src/stages/generate/generators.ts b/packages/techdocs-common/src/stages/generate/generators.ts index de39d4e7c3..630fccfa44 100644 --- a/packages/techdocs-common/src/stages/generate/generators.ts +++ b/packages/techdocs-common/src/stages/generate/generators.ts @@ -26,9 +26,18 @@ import { SupportedGeneratorKey, } from './types'; +/** + * Collection of docs generators + * @public + */ export class Generators implements GeneratorBuilder { private generatorMap = new Map(); + /** + * Returns a generators instance containing a generator for TechDocs + * @param config - A Backstage configuration + * @param options - Options to configure the TechDocs generator + */ static async fromConfig( config: Config, options: { logger: Logger; containerRunner: ContainerRunner }, @@ -41,10 +50,19 @@ export class Generators implements GeneratorBuilder { return generators; } + /** + * Register a generator in the generators collection + * @param generatorKey - Unique identifier for the generator + * @param generator - The generator instance to register + */ register(generatorKey: SupportedGeneratorKey, generator: GeneratorBase) { this.generatorMap.set(generatorKey, generator); } + /** + * Returns the generator for a given TechDocs entity + * @param entity - A TechDocs entity instance + */ get(entity: Entity): GeneratorBase { const generatorKey = getGeneratorKey(entity); const generator = this.generatorMap.get(generatorKey); diff --git a/packages/techdocs-common/src/stages/generate/index.ts b/packages/techdocs-common/src/stages/generate/index.ts index eb015a5c2d..1c20c58887 100644 --- a/packages/techdocs-common/src/stages/generate/index.ts +++ b/packages/techdocs-common/src/stages/generate/index.ts @@ -16,7 +16,9 @@ export { TechdocsGenerator } from './techdocs'; export { Generators } from './generators'; export type { - GeneratorBuilder, GeneratorBase, + GeneratorOptions, + GeneratorBuilder, GeneratorRunOptions, + SupportedGeneratorKey, } from './types'; diff --git a/packages/techdocs-common/src/stages/generate/techdocs.ts b/packages/techdocs-common/src/stages/generate/techdocs.ts index 5cf992092c..baa2e008a1 100644 --- a/packages/techdocs-common/src/stages/generate/techdocs.ts +++ b/packages/techdocs-common/src/stages/generate/techdocs.ts @@ -34,26 +34,33 @@ import { import { GeneratorBase, GeneratorConfig, + GeneratorOptions, GeneratorRunInType, GeneratorRunOptions, } from './types'; import { ForwardedError } from '@backstage/errors'; +/** + * Generates documentation files + * @public + */ export class TechdocsGenerator implements GeneratorBase { /** * The default docker image (and version) used to generate content. Public * and static so that techdocs-common consumers can use the same version. */ - public static readonly defaultDockerImage = 'spotify/techdocs:v0.3.6'; + public static readonly defaultDockerImage = 'spotify/techdocs:v0.3.7'; private readonly logger: Logger; private readonly containerRunner: ContainerRunner; private readonly options: GeneratorConfig; private readonly scmIntegrations: ScmIntegrationRegistry; - static fromConfig( - config: Config, - options: { containerRunner: ContainerRunner; logger: Logger }, - ) { + /** + * Returns a instance of TechDocs generator + * @param config - A Backstage configuration + * @param options - Options to configure the generator + */ + static fromConfig(config: Config, options: GeneratorOptions) { const { containerRunner, logger } = options; const scmIntegrations = ScmIntegrations.fromConfig(config); return new TechdocsGenerator({ @@ -76,6 +83,7 @@ export class TechdocsGenerator implements GeneratorBase { this.scmIntegrations = options.scmIntegrations; } + /** {@inheritDoc GeneratorBase.run} */ public async run(options: GeneratorRunOptions): Promise { const { inputDir, diff --git a/packages/techdocs-common/src/stages/generate/types.ts b/packages/techdocs-common/src/stages/generate/types.ts index 2a090d5584..2ff3064991 100644 --- a/packages/techdocs-common/src/stages/generate/types.ts +++ b/packages/techdocs-common/src/stages/generate/types.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { ContainerRunner } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Writable } from 'stream'; import { Logger } from 'winston'; @@ -22,6 +23,15 @@ import { ParsedLocationAnnotation } from '../../helpers'; // Determines where the generator will be run export type GeneratorRunInType = 'docker' | 'local'; +/** + * Options for building generators + * @public + */ +export type GeneratorOptions = { + containerRunner: ContainerRunner; + logger: Logger; +}; + /** * The techdocs generator configurations options. */ @@ -51,18 +61,27 @@ export type GeneratorRunOptions = { logStream?: Writable; }; +/** + * Generates documentation files + * @public + */ export type GeneratorBase = { - // Runs the generator with the values + /** + * Runs the generator with the values + * @public + */ run(opts: GeneratorRunOptions): Promise; }; /** * List of supported generator options + * @public */ export type SupportedGeneratorKey = 'techdocs' | string; /** * The generator builder holds the generator ready for run time + * @public */ export type GeneratorBuilder = { register(protocol: SupportedGeneratorKey, generator: GeneratorBase): void; diff --git a/packages/techdocs-common/src/stages/prepare/dir.test.ts b/packages/techdocs-common/src/stages/prepare/dir.test.ts index 019bf51fe9..2bf3fe29fc 100644 --- a/packages/techdocs-common/src/stages/prepare/dir.test.ts +++ b/packages/techdocs-common/src/stages/prepare/dir.test.ts @@ -86,7 +86,7 @@ describe('directory preparer', () => { ); }); - it('should reject when managed-by-location is a git repository', async () => { + it('should reject when managed-by-location has an unknown type', async () => { const directoryPreparer = new DirectoryPreparer( mockConfig, logger, @@ -95,12 +95,12 @@ describe('directory preparer', () => { const mockEntity = createMockEntity({ 'backstage.io/managed-by-location': - 'github:https://github.com/backstage/backstage/blob/master/catalog-info.yaml', + 'does-not-exist:https://github.com/backstage/backstage/blob/master/catalog-info.yaml', 'backstage.io/techdocs-ref': 'dir:./docs', }); await expect(directoryPreparer.prepare(mockEntity)).rejects.toThrow( - /Unable to resolve location type github/, + /Unable to resolve location type does-not-exist/, ); }); }); diff --git a/packages/techdocs-common/src/stages/prepare/dir.ts b/packages/techdocs-common/src/stages/prepare/dir.ts index ade0b00162..42bfa5b903 100644 --- a/packages/techdocs-common/src/stages/prepare/dir.ts +++ b/packages/techdocs-common/src/stages/prepare/dir.ts @@ -24,20 +24,43 @@ import { } from '@backstage/integration'; import { Logger } from 'winston'; import { parseReferenceAnnotation, transformDirLocation } from '../../helpers'; -import { PreparerBase, PreparerResponse } from './types'; +import { + PreparerBase, + PreparerConfig, + PreparerOptions, + PreparerResponse, +} from './types'; +/** + * Preparer used to retrieve documentation files from a local directory + * @public + */ export class DirectoryPreparer implements PreparerBase { private readonly scmIntegrations: ScmIntegrationRegistry; private readonly reader: UrlReader; - constructor(config: Config, _logger: Logger, reader: UrlReader) { + /** @deprecated use static fromConfig method instead */ + constructor(config: Config, _logger: Logger | null, reader: UrlReader) { this.reader = reader; this.scmIntegrations = ScmIntegrations.fromConfig(config); } + /** + * Returns a directory preparer instance + * @param config - A backstage config + * @param options - A directory preparer options containing a logger and reader + */ + static fromConfig( + config: Config, + { logger, reader }: PreparerConfig, + ): DirectoryPreparer { + return new DirectoryPreparer(config, logger, reader); + } + + /** {@inheritDoc PreparerBase.prepare} */ async prepare( entity: Entity, - options?: { logger?: Logger; etag?: string }, + options?: PreparerOptions, ): Promise { const annotation = parseReferenceAnnotation( 'backstage.io/techdocs-ref', diff --git a/packages/techdocs-common/src/stages/prepare/index.ts b/packages/techdocs-common/src/stages/prepare/index.ts index f129bf5315..3593b49c86 100644 --- a/packages/techdocs-common/src/stages/prepare/index.ts +++ b/packages/techdocs-common/src/stages/prepare/index.ts @@ -16,4 +16,12 @@ export { DirectoryPreparer } from './dir'; export { UrlPreparer } from './url'; export { Preparers } from './preparers'; -export type { PreparerBuilder, PreparerBase, RemoteProtocol } from './types'; +export type { + PreparerBase, + PreparerBuilder, + PreparerConfig, + PreparerOptions, + PreparerResponse, + RemoteProtocol, + ETag, +} from './types'; diff --git a/packages/techdocs-common/src/stages/prepare/preparers.ts b/packages/techdocs-common/src/stages/prepare/preparers.ts index 75c9a7f23b..575294e91a 100644 --- a/packages/techdocs-common/src/stages/prepare/preparers.ts +++ b/packages/techdocs-common/src/stages/prepare/preparers.ts @@ -13,26 +13,34 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { Logger } from 'winston'; import { parseReferenceAnnotation } from '../../helpers'; import { DirectoryPreparer } from './dir'; import { UrlPreparer } from './url'; -import { PreparerBase, PreparerBuilder, RemoteProtocol } from './types'; - -type factoryOptions = { - logger: Logger; - reader: UrlReader; -}; +import { + PreparerBase, + PreparerBuilder, + PreparerConfig, + RemoteProtocol, +} from './types'; +/** + * Collection of docs preparers (dir and url) + * @public + */ export class Preparers implements PreparerBuilder { private preparerMap = new Map(); + /** + * Returns a generators instance containing a generator for TechDocs + * @public + * @param backstageConfig - A Backstage configuration + * @param preparerConfig - Options to configure preparers + */ static async fromConfig( - config: Config, - { logger, reader }: factoryOptions, + backstageConfig: Config, + { logger, reader }: PreparerConfig, ): Promise { const preparers = new Preparers(); @@ -43,16 +51,30 @@ export class Preparers implements PreparerBuilder { * Dir preparer is a syntactic sugar for users to define techdocs-ref annotation. * When using dir preparer, the docs will be fetched using URL Reader. */ - const directoryPreparer = new DirectoryPreparer(config, logger, reader); + const directoryPreparer = new DirectoryPreparer( + backstageConfig, + logger, + reader, + ); preparers.register('dir', directoryPreparer); return preparers; } + /** + * Register a preparer in the preparers collection + * @param protocol - url or dir to associate with preparer + * @param preparer - The preparer instance to set + */ register(protocol: RemoteProtocol, preparer: PreparerBase) { this.preparerMap.set(protocol, preparer); } + /** + * Returns the preparer for a given TechDocs entity + * @param entity - A TechDocs entity instance + * @returns + */ get(entity: Entity): PreparerBase { const { type } = parseReferenceAnnotation( 'backstage.io/techdocs-ref', diff --git a/packages/techdocs-common/src/stages/prepare/types.ts b/packages/techdocs-common/src/stages/prepare/types.ts index 5c219752fa..aee9d216ab 100644 --- a/packages/techdocs-common/src/stages/prepare/types.ts +++ b/packages/techdocs-common/src/stages/prepare/types.ts @@ -15,19 +15,58 @@ */ import type { Entity } from '@backstage/catalog-model'; +import { UrlReader } from '@backstage/backend-common'; import { Logger } from 'winston'; +/** + * A unique identifier of the tree blob, usually the commit SHA or etag from the target. + * @public + */ +export type ETag = string; + +/** + * Options for building preparers + * @public + */ +export type PreparerConfig = { + logger: Logger; + reader: UrlReader; +}; + +/** + * Options for configuring the content preparation process. + * @public + */ +export type PreparerOptions = { + /** + * An instance of the logger + */ + logger?: Logger; + /** + * see {@link ETag} + */ + etag?: ETag; +}; + +/** + * Result of the preparation step. + * @public + */ export type PreparerResponse = { /** * The path to directory where the tree is downloaded. */ preparedDir: string; /** - * A unique identifier of the tree blob, usually the commit SHA or etag from the target. + * see {@link ETag} */ - etag: string; + etag: ETag; }; +/** + * Definition of a TechDocs preparer + * @public + */ export type PreparerBase = { /** * Given an Entity definition from the Software Catalog, go and prepare a directory @@ -38,15 +77,20 @@ export type PreparerBase = { * updated since the last build. * @throws `NotModifiedError` when the prepared directory has not been changed since the last build. */ - prepare( - entity: Entity, - options?: { logger?: Logger; etag?: string }, - ): Promise; + prepare(entity: Entity, options?: PreparerOptions): Promise; }; +/** + * Definition for a TechDocs preparer builder + * @public + */ export type PreparerBuilder = { register(protocol: RemoteProtocol, preparer: PreparerBase): void; get(entity: Entity): PreparerBase; }; +/** + * Location where documentation files are stored + * @public + */ export type RemoteProtocol = 'url' | 'dir'; diff --git a/packages/techdocs-common/src/stages/prepare/url.ts b/packages/techdocs-common/src/stages/prepare/url.ts index cd4a8860e4..8026dca41e 100644 --- a/packages/techdocs-common/src/stages/prepare/url.ts +++ b/packages/techdocs-common/src/stages/prepare/url.ts @@ -19,20 +19,39 @@ import { UrlReader } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; import { Logger } from 'winston'; import { getDocFilesFromRepository } from '../../helpers'; -import { PreparerBase, PreparerResponse } from './types'; +import { + PreparerBase, + PreparerConfig, + PreparerOptions, + PreparerResponse, +} from './types'; +/** + * Preparer used to retrieve documentation files from a remote repository + * @public + */ export class UrlPreparer implements PreparerBase { private readonly logger: Logger; private readonly reader: UrlReader; + /** @deprecated use static fromConfig method instead */ constructor(reader: UrlReader, logger: Logger) { this.logger = logger; this.reader = reader; } + /** + * Returns a directory preparer instance + * @param config - A URL preparer config containing the a logger and reader + */ + static fromConfig({ reader, logger }: PreparerConfig): UrlPreparer { + return new UrlPreparer(reader, logger); + } + + /** {@inheritDoc PreparerBase.prepare} */ async prepare( entity: Entity, - options?: { etag?: string }, + options?: PreparerOptions, ): Promise { try { return await getDocFilesFromRepository(this.reader, entity, { diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.ts b/packages/techdocs-common/src/stages/publish/googleStorage.ts index ba70c36e27..c3d9d3330b 100644 --- a/packages/techdocs-common/src/stages/publish/googleStorage.ts +++ b/packages/techdocs-common/src/stages/publish/googleStorage.ts @@ -83,7 +83,7 @@ export class GoogleGCSPublish implements PublisherBase { const credentials = config.getOptionalString( 'techdocs.publisher.googleGcs.credentials', ); - let credentialsJson = {}; + let credentialsJson: any = {}; if (credentials) { try { credentialsJson = JSON.parse(credentials); @@ -96,6 +96,7 @@ export class GoogleGCSPublish implements PublisherBase { const storageClient = new Storage({ ...(credentials && { + projectId: credentialsJson.project_id, credentials: credentialsJson, }), }); diff --git a/packages/techdocs-common/src/stages/publish/index.ts b/packages/techdocs-common/src/stages/publish/index.ts index 083cf2b8ff..c6f86f6bc1 100644 --- a/packages/techdocs-common/src/stages/publish/index.ts +++ b/packages/techdocs-common/src/stages/publish/index.ts @@ -17,6 +17,10 @@ export { Publisher } from './publish'; export type { PublisherBase, PublisherType, - TechDocsMetadata, + PublisherFactory, + PublishRequest, + PublishResponse, + MigrateRequest, ReadinessResponse, + TechDocsMetadata, } from './types'; diff --git a/packages/techdocs-common/src/stages/publish/publish.ts b/packages/techdocs-common/src/stages/publish/publish.ts index eb9e3104bf..bc7f50b5a3 100644 --- a/packages/techdocs-common/src/stages/publish/publish.ts +++ b/packages/techdocs-common/src/stages/publish/publish.ts @@ -14,29 +14,28 @@ * limitations under the License. */ -import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { Config } from '@backstage/config'; -import { Logger } from 'winston'; import { AwsS3Publish } from './awsS3'; import { AzureBlobStoragePublish } from './azureBlobStorage'; import { GoogleGCSPublish } from './googleStorage'; import { LocalPublish } from './local'; import { OpenStackSwiftPublish } from './openStackSwift'; -import { PublisherBase, PublisherType } from './types'; - -type factoryOptions = { - logger: Logger; - discovery: PluginEndpointDiscovery; -}; +import { PublisherFactory, PublisherBase, PublisherType } from './types'; /** * Factory class to create a TechDocs publisher based on defined publisher type in app config. * Uses `techdocs.publisher.type`. + * @public */ export class Publisher { + /** + * Returns a instance of TechDocs publisher + * @param config - A Backstage configuration + * @param options - Options for configuring the publisher factory + */ static async fromConfig( config: Config, - { logger, discovery }: factoryOptions, + { logger, discovery }: PublisherFactory, ): Promise { const publisherType = (config.getOptionalString( 'techdocs.publisher.type', diff --git a/packages/techdocs-common/src/stages/publish/types.ts b/packages/techdocs-common/src/stages/publish/types.ts index 37075a35a4..c6ad032484 100644 --- a/packages/techdocs-common/src/stages/publish/types.ts +++ b/packages/techdocs-common/src/stages/publish/types.ts @@ -14,10 +14,22 @@ * limitations under the License. */ import { Entity, EntityName } from '@backstage/catalog-model'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { Logger } from 'winston'; import express from 'express'; +/** + * Options for building publishers + * @public + */ +export type PublisherFactory = { + logger: Logger; + discovery: PluginEndpointDiscovery; +}; + /** * Key for all the different types of TechDocs publishers that are supported. + * @public */ export type PublisherType = | 'local' @@ -26,6 +38,10 @@ export type PublisherType = | 'azureBlobStorage' | 'openStackSwift'; +/** + * Request publish definition + * @public + */ export type PublishRequest = { entity: Entity; /* The Path to the directory where the generated files are stored. */ @@ -35,6 +51,7 @@ export type PublishRequest = { /** * Response containing metadata about where files were published and what may * have been published or updated. + * @public */ export type PublishResponse = { /** @@ -51,7 +68,6 @@ export type PublishResponse = { /** * Result for the validation check. - * * @public */ export type ReadinessResponse = { @@ -62,6 +78,7 @@ export type ReadinessResponse = { /** * Type to hold metadata found in techdocs_metadata.json and associated with each site * @param etag - ETag of the resource used to generate the site. Usually the latest commit sha of the source repository. + * @public */ export type TechDocsMetadata = { site_name: string; @@ -71,6 +88,10 @@ export type TechDocsMetadata = { files?: string[]; }; +/** + * TechDocs entity triplet migration request + * @public + */ export type MigrateRequest = { /** * Whether or not to remove the source file. Defaults to false (acting like a diff --git a/packages/techdocs-common/src/techdocsTypes.ts b/packages/techdocs-common/src/techdocsTypes.ts index 4aa3972b31..0b722f4da5 100644 --- a/packages/techdocs-common/src/techdocsTypes.ts +++ b/packages/techdocs-common/src/techdocsTypes.ts @@ -16,11 +16,33 @@ import { IndexableDocument } from '@backstage/search-common'; +/** + * TechDocs indexable document interface + * @public + */ export interface TechDocsDocument extends IndexableDocument { + /** + * Entity kind + */ kind: string; + /** + * Entity metadata namespace + */ namespace: string; + /** + * Entity metadata name + */ name: string; + /** + * Entity lifecycle + */ lifecycle: string; + /** + * Entity owner + */ owner: string; + /** + * Entity path + */ path: string; } diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 35c9ebfb35..4de90a4aa7 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/test-utils +## 0.2.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@0.7.0 + - @backstage/core-app-api@0.5.4 + - @backstage/plugin-permission-react@0.3.2 + +## 0.2.5 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-app-api@0.5.3 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/plugin-permission-react@0.3.1 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + - @backstage/types@0.1.2 + ## 0.2.4 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 67b68e08cc..5465f7783a 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,13 +1,16 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.2.4", + "version": "0.2.6", "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,21 +24,22 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/core-app-api": "^0.5.2", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/plugin-permission-react": "^0.3.0", - "@backstage/theme": "^0.2.14", - "@backstage/types": "^0.1.1", + "@backstage/config": "^0.1.15", + "@backstage/core-app-api": "^0.5.4", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-permission-common": "^0.5.1", + "@backstage/plugin-permission-react": "^0.3.2", + "@backstage/theme": "^0.2.15", + "@backstage/types": "^0.1.3", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.11.2", "@testing-library/jest-dom": "^5.10.1", @@ -51,7 +55,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", + "@backstage/cli": "^0.14.1", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "msw": "^0.35.0" diff --git a/packages/theme/CHANGELOG.md b/packages/theme/CHANGELOG.md index cfa6ee3633..5c3e8cf5d6 100644 --- a/packages/theme/CHANGELOG.md +++ b/packages/theme/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/theme +## 0.2.15 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` + ## 0.2.14 ### Patch Changes diff --git a/packages/theme/package.json b/packages/theme/package.json index 2846db2805..17404e8db0 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,13 +1,16 @@ { "name": "@backstage/theme", "description": "material-ui theme for use with Backstage.", - "version": "0.2.14", + "version": "0.2.15", "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,17 +24,19 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start", + "test": "backstage-cli package test" }, "dependencies": { "@material-ui/core": "^4.12.2" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0" + "@backstage/cli": "^0.14.0" }, "files": [ "dist" diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md new file mode 100644 index 0000000000..238c61f68f --- /dev/null +++ b/packages/types/CHANGELOG.md @@ -0,0 +1,13 @@ +# @backstage/types + +## 0.1.3 + +### Patch Changes + +- Fix for the previous release with missing type declarations. + +## 0.1.2 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` diff --git a/packages/types/package.json b/packages/types/package.json index ae18fe519d..ab0740d568 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/types", "description": "Common TypeScript types used within Backstage", - "version": "0.1.1", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -12,6 +12,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,16 +25,16 @@ "backstage" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": {}, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", + "@backstage/cli": "^0.14.0", "@types/zen-observable": "^0.8.0", "zen-observable": "^0.8.15" }, diff --git a/packages/version-bridge/CHANGELOG.md b/packages/version-bridge/CHANGELOG.md index e079cd6c61..fe131f7987 100644 --- a/packages/version-bridge/CHANGELOG.md +++ b/packages/version-bridge/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/version-bridge +## 0.1.2 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` + ## 0.1.1 ### Patch Changes diff --git a/packages/version-bridge/package.json b/packages/version-bridge/package.json index 6989aec3cf..485b0417cf 100644 --- a/packages/version-bridge/package.json +++ b/packages/version-bridge/package.json @@ -1,13 +1,16 @@ { "name": "@backstage/version-bridge", "description": "Utilities used by @backstage packages to support multiple concurrent versions", - "version": "0.1.1", + "version": "0.1.2", "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,19 +24,20 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", + "@backstage/cli": "^0.14.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2" diff --git a/plugins/airbrake-backend/.eslintrc.js b/plugins/airbrake-backend/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/airbrake-backend/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md new file mode 100644 index 0000000000..148dbdeb07 --- /dev/null +++ b/plugins/airbrake-backend/CHANGELOG.md @@ -0,0 +1,25 @@ +# @backstage/plugin-airbrake-backend + +## 0.2.0 + +### Minor Changes + +- da78e79a94: This marks the first release where the Airbrake plugin is useable. Airbrake frontend and Airbrake backend work with each other. Currently just a summary of the latest Airbrakes is shown on Backstage. + +### Patch Changes + +- 0045270527: Make config optional +- Updated dependencies + - @backstage/backend-common@0.11.0 + +## 0.1.0 + +### Minor Changes + +- a5aebe3e30: Created the Airbrake backend plugin that proxies requests to the Airbrake API + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/config@0.1.14 diff --git a/plugins/airbrake-backend/README.md b/plugins/airbrake-backend/README.md new file mode 100644 index 0000000000..9c9b56ade4 --- /dev/null +++ b/plugins/airbrake-backend/README.md @@ -0,0 +1,32 @@ +# airbrake-backend + +The Airbrake backend plugin provides a simple proxy to the Airbrake API while hiding away the secret API key from the frontend. + +## How to use + +See the [Airbrake plugin instructions](../airbrake/README.md#how-to-use). + +## Local Development + +This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. + +1. Add the required config to your `app-config.local.yaml`: + + ```yaml + airbrake: + apiKey: ${AIRBRAKE_API_KEY} + ``` + +2. Set the environment variable `AIRBRAKE_API_KEY` with your [API + key](https://airbrake.io/docs/api/#authentication). You can also write it + directly into the config file above for convenience - but beware of + accidentally leaking the key. + +3. Go into the plugin's directory and run it in standalone mode by running `yarn start`. + +Access it from http://localhost:7007/api/airbrake. Or use the Airbrake plugin which will talk to it automatically. + +Here are some example endpoints: + +- http://localhost:7007/api/airbrake/health +- http://localhost:7007/api/airbrake/api/v4/projects diff --git a/plugins/airbrake-backend/api-report.md b/plugins/airbrake-backend/api-report.md new file mode 100644 index 0000000000..b9e9a50e0b --- /dev/null +++ b/plugins/airbrake-backend/api-report.md @@ -0,0 +1,26 @@ +## API Report File for "@backstage/plugin-airbrake-backend" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { Config } from '@backstage/config'; +import express from 'express'; +import { Logger as Logger_2 } from 'winston'; + +// @public +export interface AirbrakeConfig { + apiKey: string; +} + +// @public +export function createRouter(options: RouterOptions): Promise; + +// @public +export function extractAirbrakeConfig(config: Config): AirbrakeConfig; + +// @public +export interface RouterOptions { + airbrakeConfig: AirbrakeConfig; + logger: Logger_2; +} +``` diff --git a/plugins/airbrake-backend/config.d.ts b/plugins/airbrake-backend/config.d.ts new file mode 100644 index 0000000000..a21f609550 --- /dev/null +++ b/plugins/airbrake-backend/config.d.ts @@ -0,0 +1,28 @@ +/* + * 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 interface Config { + /** Configuration options for the Airbrake plugin */ + airbrake?: { + /** + * The API Key to authenticate requests. More details on how to get this + * here: https://airbrake.io/docs/api/#authentication + * + * @visibility secret + */ + apiKey: string; + }; +} diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json new file mode 100644 index 0000000000..2ee36eefb7 --- /dev/null +++ b/plugins/airbrake-backend/package.json @@ -0,0 +1,44 @@ +{ + "name": "@backstage/plugin-airbrake-backend", + "version": "0.2.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@types/express": "*", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "http-proxy-middleware": "^2.0.0", + "winston": "^3.2.1", + "cross-fetch": "^3.0.6", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.14.1", + "@types/http-proxy-middleware": "^0.19.3", + "@types/supertest": "^2.0.8", + "supertest": "^6.1.6", + "msw": "^0.35.0" + }, + "files": [ + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.test.ts b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.test.ts new file mode 100644 index 0000000000..53d744b9ee --- /dev/null +++ b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2022 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 { ConfigReader } from '@backstage/config'; +import { extractAirbrakeConfig } from './ExtractAirbrakeConfig'; + +describe('ExtractAirbrakeConfig', () => { + it('gets the API key', () => { + const config = new ConfigReader({ + airbrake: { + apiKey: 'fakeApiKey', + }, + }); + + const airbrakeConfig = extractAirbrakeConfig(config); + expect(airbrakeConfig.apiKey).toStrictEqual('fakeApiKey'); + }); + + it('fails for missing keys', () => { + expect(() => extractAirbrakeConfig(new ConfigReader({}))).toThrow(); + expect(() => + extractAirbrakeConfig(new ConfigReader({ airbrake: {} })), + ).toThrow(); + }); +}); diff --git a/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts new file mode 100644 index 0000000000..de6a31feec --- /dev/null +++ b/plugins/airbrake-backend/src/config/ExtractAirbrakeConfig.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2022 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 { Config } from '@backstage/config'; + +/** + * The configuration needed for the airbrake-backend plugin + * + * @public + */ +export interface AirbrakeConfig { + /** + * The API Key to authenticate requests. More details on how to get this here: https://airbrake.io/docs/api/#authentication + */ + apiKey: string; +} + +/** + * Extract the Airbrake config from a config object + * + * @public + * + * @param config - The config object to extract from + */ +export function extractAirbrakeConfig(config: Config): AirbrakeConfig { + return { + apiKey: config.getString('airbrake.apiKey'), + }; +} diff --git a/plugins/scaffolder/src/components/fields/TextValuePicker/index.ts b/plugins/airbrake-backend/src/config/index.ts similarity index 85% rename from plugins/scaffolder/src/components/fields/TextValuePicker/index.ts rename to plugins/airbrake-backend/src/config/index.ts index 8ab810b6ed..60d60c2761 100644 --- a/plugins/scaffolder/src/components/fields/TextValuePicker/index.ts +++ b/plugins/airbrake-backend/src/config/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * Copyright 2022 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. @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { TextValuePicker } from './TextValuePicker'; + +export * from './ExtractAirbrakeConfig'; diff --git a/plugins/airbrake-backend/src/index.ts b/plugins/airbrake-backend/src/index.ts new file mode 100644 index 0000000000..f4ec6008c2 --- /dev/null +++ b/plugins/airbrake-backend/src/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * The Airbrake Backend plugin used by \@backstage/plugin-airbrake + * + * @packageDocumentation + */ + +export * from './service/router'; +export * from './config'; diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.test.tsx b/plugins/airbrake-backend/src/run.ts similarity index 51% rename from plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.test.tsx rename to plugins/airbrake-backend/src/run.ts index a621eb281c..0a3ed2b7f0 100644 --- a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.test.tsx +++ b/plugins/airbrake-backend/src/run.ts @@ -14,19 +14,20 @@ * limitations under the License. */ -import React from 'react'; -import { EntityAirbrakeContent } from './EntityAirbrakeContent'; -import exampleData from './example-data.json'; -import { renderInTestApp } from '@backstage/test-utils'; +import { getRootLogger } from '@backstage/backend-common'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; -describe('EntityAirbrakeContent', () => { - it('renders all errors sent from Airbrake', async () => { - const table = await renderInTestApp(); - expect(exampleData.groups.length).toBeGreaterThan(0); - for (const group of exampleData.groups) { - expect( - await table.getByText(group.errors[0].message), - ).toBeInTheDocument(); - } - }); +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); }); diff --git a/plugins/airbrake-backend/src/service/router.test.ts b/plugins/airbrake-backend/src/service/router.test.ts new file mode 100644 index 0000000000..7ccafd7eba --- /dev/null +++ b/plugins/airbrake-backend/src/service/router.test.ts @@ -0,0 +1,92 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import express from 'express'; +import request from 'supertest'; +import { ConfigReader } from '@backstage/config'; +import { + createRouter, + generateAirbrakePathRewrite, + RouterOptions, +} from './router'; +import { AirbrakeConfig, extractAirbrakeConfig } from '../config'; +import * as winston from 'winston'; + +describe('createRouter', () => { + let app: express.Express; + let airbrakeConfig: AirbrakeConfig; + let voidLogger: winston.Logger; + + beforeEach(async () => { + jest.resetAllMocks(); + + voidLogger = getVoidLogger(); + const config = new ConfigReader({ + airbrake: { + apiKey: 'fakeApiKey', + }, + }); + airbrakeConfig = extractAirbrakeConfig(config); + + const router = await createRouter({ + logger: voidLogger, + airbrakeConfig, + }); + app = express().use(router); + }); + + describe('GET /health', () => { + it('returns ok', async () => { + const response = await request(app).get('/health'); + + expect(response.status).toEqual(200); + expect(response.body).toEqual({ status: 'ok' }); + }); + }); + + describe('GET /api', () => { + it('appends the API Key properly with no other url parameters', () => { + const options: RouterOptions = { + logger: voidLogger, + airbrakeConfig, + }; + const pathRewrite = generateAirbrakePathRewrite(options) as ( + path: string, + ) => string; + + expect(pathRewrite('/airbrake-backend/api/v4/random/endpoint')).toBe( + '/v4/random/endpoint?key=fakeApiKey', + ); + }); + + it('appends the API Key properly despite there being other URL parameters', () => { + const options: RouterOptions = { + logger: voidLogger, + airbrakeConfig, + }; + const pathRewrite = generateAirbrakePathRewrite(options) as ( + path: string, + ) => string; + + expect( + pathRewrite( + '/airbrake-backend/api/v4/random/endpoint?param1=123¶m2=abc', + ), + ).toBe('/v4/random/endpoint?param1=123¶m2=abc&key=fakeApiKey'); + }); + }); +}); diff --git a/plugins/airbrake-backend/src/service/router.ts b/plugins/airbrake-backend/src/service/router.ts new file mode 100644 index 0000000000..b870eb5c13 --- /dev/null +++ b/plugins/airbrake-backend/src/service/router.ts @@ -0,0 +1,96 @@ +/* + * 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 { errorHandler } from '@backstage/backend-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; +import { createProxyMiddleware } from 'http-proxy-middleware'; +import { AirbrakeConfig } from '../config'; +import { Options } from 'http-proxy-middleware/dist/types'; + +/** + * The router options that are needed when creating a router. + * + * @public + */ +export interface RouterOptions { + /** + * A logger object + */ + logger: Logger; + + /** + * The Airbrake config obtained from {@link extractAirbrakeConfig} + */ + airbrakeConfig: AirbrakeConfig; +} + +/** + * Mainly used internally to generate the path. + * + * @internal + * + * @param options - Router options + */ +export const generateAirbrakePathRewrite = ( + options: RouterOptions, +): Options['pathRewrite'] => { + const apiKey = options.airbrakeConfig.apiKey; + + return path => { + let newPath = path.replace(/.+?(\/api)/g, ''); + if (newPath.includes('?')) { + newPath += `&key=${apiKey}`; + } else { + newPath += `?key=${apiKey}`; + } + return newPath; + }; +}; + +/** + * Create the Airbrake Router, used for making API calls to the Airbrake API. + * + * @public + * + * @param options - Router options + */ +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger } = options; + + const router = Router(); + router.use(express.json()); + + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.send({ status: 'ok' }); + }); + + router.use( + '/api', + createProxyMiddleware({ + target: 'https://api.airbrake.io/api', + changeOrigin: true, + pathRewrite: generateAirbrakePathRewrite(options), + }), + ); + + router.use(errorHandler()); + return router; +} diff --git a/plugins/airbrake-backend/src/service/standaloneServer.ts b/plugins/airbrake-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..6825e60a0a --- /dev/null +++ b/plugins/airbrake-backend/src/service/standaloneServer.ts @@ -0,0 +1,61 @@ +/* + * 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 { + createServiceBuilder, + loadBackendConfig, +} from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; +import { extractAirbrakeConfig } from '../config'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'airbrake-backend' }); + const config = await loadBackendConfig({ logger, argv: process.argv }); + const airbrakeConfig = extractAirbrakeConfig(config); + logger.debug('Starting application server...'); + const router = await createRouter({ + logger, + airbrakeConfig, + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/api/airbrake', router); + if (options.enableCors) { + logger.info('CORS is enabled, limiting to localhost with port 3000'); + service = service.enableCors({ origin: 'http://localhost:3000' }); + } else { + logger.info('CORS is disabled, allowing all origins'); + service = service.enableCors({ origin: '*' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/airbrake-backend/src/setupTests.ts b/plugins/airbrake-backend/src/setupTests.ts new file mode 100644 index 0000000000..d3232290a7 --- /dev/null +++ b/plugins/airbrake-backend/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md index df784d0030..685ef4c0e7 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,40 @@ # @backstage/plugin-airbrake +## 0.3.0 + +### Minor Changes + +- da78e79a94: This marks the first release where the Airbrake plugin is useable. Airbrake frontend and Airbrake backend work with each other. Currently just a summary of the latest Airbrakes is shown on Backstage. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/dev-utils@0.2.23 + - @backstage/test-utils@0.2.6 + +## 0.2.0 + +### Minor Changes + +- 9e505d20a3: API connectivity has added, but currently will only work by running it standalone on a browser with CORS disabled. + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/test-utils@0.2.5 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/dev-utils@0.2.22 + - @backstage/theme@0.2.15 + ## 0.1.3 ### Patch Changes diff --git a/plugins/airbrake/README.md b/plugins/airbrake/README.md index de041fb3ec..d5ea9bb006 100644 --- a/plugins/airbrake/README.md +++ b/plugins/airbrake/README.md @@ -1,13 +1,75 @@ # Airbrake -Welcome to the Airbrake plugin! +The Airbrake plugin provides connectivity between Backstage and Airbrake (https://airbrake.io/). -This is a plugin providing connectivity between Backstage and Airbrake (https://airbrake.io/). +## How to use -_This plugin is currently not fit for use as it is work in progress_ +1. Install the Frontend plugin: -## Getting started + ```bash + # From your Backstage root directory + cd packages/app + yarn add @backstage/plugin-airbrake + ``` -You can serve the plugin in isolation by running `yarn start` in the plugin directory. -This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads. -It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory. +2. Install the Backend plugin: + + ```bash + # From your Backstage root directory + cd packages/backend + yarn add @backstage/plugin-airbrake-backend + ``` + +3. Add the `EntityAirbrakeContent` to `packages/app/src/components/catalog/EntityPage.tsx`: + + ```typescript jsx + import { EntityAirbrakeContent } from '@backstage/plugin-airbrake'; + + const serviceEntityPage = ( + + + + + + ); + ``` + +4. Setup the Backend code in `packages/backend/src/index.ts`: + + ```typescript + import { + createRouter as createAirbrakeRouter, + extractAirbrakeConfig, + } from '@backstage/plugin-airbrake-backend'; + + async function main() { + //... After const config = await loadBackendConfig({ ... + + const airbrakeRouter = await createAirbrakeRouter({ + logger, + airbrakeConfig: extractAirbrakeConfig(config), + }); + + const service = createServiceBuilder(module) + // ... Add the airbrakeRouter here + .addRouter('/api/airbrake', airbrakeRouter); + } + ``` + +5. Add this config as a top level section in your `app-config.yaml`: + + ```yaml + airbrake: + apiKey: ${AIRBRAKE_API_KEY} + ``` + +6. Set an environment variable `AIRBRAKE_API_KEY` with your [API key](https://airbrake.io/docs/api/#authentication) + before starting Backstage backend. + +## Local Development + +Start this plugin in standalone mode by running `yarn start` inside the plugin directory. This method of serving the plugin provides quicker +iteration speed and a faster startup and hot reloads. It is only meant for local development, and the setup for it can +be found inside the [/dev](./dev) directory. + +> A mock API will be used to run it in standalone. If you want to talk to the real API [follow the instructions to start up Airbrake Backend in standalone](../airbrake-backend/README.md#local-development). diff --git a/plugins/airbrake/api-report.md b/plugins/airbrake/api-report.md index c01aa938ae..bd274e604d 100644 --- a/plugins/airbrake/api-report.md +++ b/plugins/airbrake/api-report.md @@ -8,9 +8,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { RouteRef } from '@backstage/core-plugin-api'; -// Warning: (ae-missing-release-tag) "airbrakePlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const airbrakePlugin: BackstagePlugin< { root: RouteRef; @@ -18,10 +16,6 @@ export const airbrakePlugin: BackstagePlugin< {} >; -// Warning: (ae-missing-release-tag) "EntityAirbrakeContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const EntityAirbrakeContent: () => JSX.Element; - -// (No @packageDocumentation comment for this package) ``` diff --git a/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx new file mode 100644 index 0000000000..7a4ee42f14 --- /dev/null +++ b/plugins/airbrake/dev/components/ApiBar/ApiBar.tsx @@ -0,0 +1,79 @@ +/* + * Copyright 2022 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 { + createTheme, + makeStyles, + MuiThemeProvider, + TextField, +} from '@material-ui/core'; +import { Context } from '../ContextProvider'; + +const useStyles = makeStyles({ + root: { + display: 'flex', + gap: '1em', + flexWrap: 'wrap', + }, +}); + +const textFieldTheme = createTheme({ + palette: { + type: 'dark', + primary: { + light: '#fff', + main: '#fff', + dark: '#fff', + contrastText: '#fff', + }, + secondary: { + light: '#fff', + main: '#fff', + dark: '#fff', + contrastText: '#fff', + }, + action: { + disabled: '#fff', + }, + text: { + primary: '#fff', + secondary: '#fff', + }, + }, +}); + +export const ApiBar = () => { + const classes = useStyles(); + + return ( + + {value => ( +
+ + + value.setProjectId?.(parseInt(e.target.value, 10) || undefined) + } + /> + +
+ )} +
+ ); +}; diff --git a/plugins/scaffolder-backend/src/lib/catalog/index.ts b/plugins/airbrake/dev/components/ApiBar/index.ts similarity index 90% rename from plugins/scaffolder-backend/src/lib/catalog/index.ts rename to plugins/airbrake/dev/components/ApiBar/index.ts index ea693b00b3..ff6e80d3d8 100644 --- a/plugins/scaffolder-backend/src/lib/catalog/index.ts +++ b/plugins/airbrake/dev/components/ApiBar/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { CatalogEntityClient } from './CatalogEntityClient'; + +export { ApiBar } from './ApiBar'; diff --git a/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx b/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx new file mode 100644 index 0000000000..d46366d8f6 --- /dev/null +++ b/plugins/airbrake/dev/components/ContextProvider/ContextProvider.tsx @@ -0,0 +1,43 @@ +/* + * Copyright 2022 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, { + Dispatch, + PropsWithChildren, + SetStateAction, + useState, +} from 'react'; + +interface ContextInterface { + projectId?: number; + setProjectId?: Dispatch>; +} + +export const Context = React.createContext({}); + +export const ContextProvider = ({ children }: PropsWithChildren<{}>) => { + const [projectId, setProjectId] = useState(); + + return ( + + {children} + + ); +}; diff --git a/plugins/airbrake/dev/components/ContextProvider/index.ts b/plugins/airbrake/dev/components/ContextProvider/index.ts new file mode 100644 index 0000000000..f1e350868a --- /dev/null +++ b/plugins/airbrake/dev/components/ContextProvider/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { ContextProvider, Context } from './ContextProvider'; diff --git a/plugins/airbrake/dev/index.tsx b/plugins/airbrake/dev/index.tsx index e74ce68ee5..a844dfdfdc 100644 --- a/plugins/airbrake/dev/index.tsx +++ b/plugins/airbrake/dev/index.tsx @@ -15,39 +15,74 @@ */ import React from 'react'; import { createDevApp } from '@backstage/dev-utils'; -import { EntityAirbrakeContent, airbrakePlugin } from '../src/plugin'; +import { TestApiProvider } from '@backstage/test-utils'; +import { airbrakePlugin, EntityAirbrakeContent } from '../src'; import { - Content, - ContentHeader, - Header, - HeaderLabel, - Page, - SupportButton, -} from '@backstage/core-components'; + airbrakeApiRef, + localDiscoveryApi, + MockAirbrakeApi, + ProductionAirbrakeApi, +} from '../src/api'; +import { ApiBar } from './components/ApiBar'; +import { Content, Header, Page } from '@backstage/core-components'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { createEntity } from '../src/api'; +import CloudOffIcon from '@material-ui/icons/CloudOff'; +import CloudIcon from '@material-ui/icons/Cloud'; +import { Context, ContextProvider } from './components/ContextProvider'; createDevApp() .registerPlugin(airbrakePlugin) .addPage({ element: ( -
- - -
+
- - - A description of your plugin goes here. - - - + + + + + ), - title: 'Root Page', - path: '/airbrake', + title: 'Mock API', + path: '/airbrake-mock-api', + icon: CloudOffIcon, + }) + .addPage({ + element: ( + + +
+ +
+ + + {value => ( + + + + + + )} + + +
+
+ ), + title: 'Real API', + path: '/airbrake-real-api', + icon: CloudIcon, }) .render(); diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 40b4787dd5..7342fdd839 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.1.3", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -9,55 +9,52 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/dev-utils": "^0.2.23", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/test-utils": "^0.2.6", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", - "react-use": "^17.2.4", - "object-hash": "^2.2.0" + "object-hash": "^2.2.0", + "react-use": "^17.2.4" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@types/object-hash": "^2.2.1", - "@backstage/app-defaults": "^0.1.7", - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/app-defaults": "^0.1.9", + "@backstage/cli": "^0.14.1", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", - "msw": "^0.35.0", + "@types/object-hash": "^2.2.1", "cross-fetch": "^3.1.5", + "msw": "^0.35.0", "react-router": "6.0.0-beta.0" }, "files": [ "dist" - ], - "jest": { - "coverageThreshold": { - "global": { - "functions": 100, - "lines": 100, - "statements": 100 - } - } - } + ] } diff --git a/plugins/airbrake/src/api/AirbrakeApi.ts b/plugins/airbrake/src/api/AirbrakeApi.ts new file mode 100644 index 0000000000..a29aa3eca7 --- /dev/null +++ b/plugins/airbrake/src/api/AirbrakeApi.ts @@ -0,0 +1,26 @@ +/* + * 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 { Groups } from './airbrakeGroups'; +import { createApiRef } from '@backstage/core-plugin-api'; + +export const airbrakeApiRef = createApiRef({ + id: 'plugin.airbrake.service', +}); + +export interface AirbrakeApi { + fetchGroups(projectId: string): Promise; +} diff --git a/plugins/airbrake/src/api/ProductionApi.test.ts b/plugins/airbrake/src/api/ProductionApi.test.ts new file mode 100644 index 0000000000..f09127b4c2 --- /dev/null +++ b/plugins/airbrake/src/api/ProductionApi.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright 2022 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 { ProductionAirbrakeApi } from './ProductionApi'; +import { rest } from 'msw'; +import mockGroupsData from './mock/airbrakeGroupsApiMock.json'; +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { localDiscoveryApi } from './mock'; + +describe('The production Airbrake API', () => { + const productionApi = new ProductionAirbrakeApi(localDiscoveryApi); + const worker = setupServer(); + setupRequestMockHandlers(worker); + + it('fetches groups using the provided project ID', async () => { + worker.use( + rest.get( + 'http://localhost:7007/api/airbrake/api/v4/projects/123456/groups', + (_, res, ctx) => { + return res(ctx.status(200), ctx.json(mockGroupsData)); + }, + ), + ); + + const groups = await productionApi.fetchGroups('123456'); + + expect(groups).toStrictEqual(mockGroupsData); + }); + + it('throws if fetching groups was unsuccessful', async () => { + worker.use( + rest.get( + 'http://localhost:7007/api/airbrake/api/v4/projects/123456/groups', + (_, res, ctx) => { + return res(ctx.status(500)); + }, + ), + ); + + await expect(productionApi.fetchGroups('123456')).rejects.toThrow(); + }); +}); diff --git a/plugins/airbrake/src/api/ProductionApi.ts b/plugins/airbrake/src/api/ProductionApi.ts new file mode 100644 index 0000000000..21ca19ee7d --- /dev/null +++ b/plugins/airbrake/src/api/ProductionApi.ts @@ -0,0 +1,36 @@ +/* + * 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 { Groups } from './airbrakeGroups'; +import { AirbrakeApi } from './AirbrakeApi'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; + +export class ProductionAirbrakeApi implements AirbrakeApi { + constructor(private readonly discoveryApi: DiscoveryApi) {} + + async fetchGroups(projectId: string): Promise { + const baseUrl = await this.discoveryApi.getBaseUrl('airbrake'); + const apiUrl = `${baseUrl}/api/v4/projects/${projectId}/groups`; + + const response = await fetch(apiUrl); + + if (response.status >= 400 && response.status < 600) { + throw new Error('Failed fetching Airbrake groups'); + } + + return (await response.json()) as Groups; + } +} diff --git a/plugins/airbrake/src/api/airbrakeGroups.d.ts b/plugins/airbrake/src/api/airbrakeGroups.d.ts new file mode 100644 index 0000000000..e1887eca9b --- /dev/null +++ b/plugins/airbrake/src/api/airbrakeGroups.d.ts @@ -0,0 +1,65 @@ +/* + * Copyright 2022 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 interface Groups { + count: number; + end: string; + groups?: Group[] | null; + page: number; + resolvedCount: number; + unresolvedCount: number; +} + +export interface Group { + id: string; + projectId: number; + resolved: boolean; + muted: boolean; + mutedBy: number; + mutedAt?: string | null; + errors?: Error[]; + attributes?: string | null; + context: Context; + lastDeployId: string; + lastDeployAt?: string | null; + lastNoticeId: string; + lastNoticeAt: string; + noticeCount: number; + noticeTotalCount: number; + commentCount: number; + createdAt: string; +} + +export interface Error { + type: string; + message: string; + backtrace?: Backtrace[]; +} + +export interface Backtrace { + file: string; + function: string; + line: number; + column: number; + code?: string | null; +} + +export interface Context { + action: string; + component: string; + environment: string; + severity: string; +} diff --git a/plugins/airbrake/src/api/index.ts b/plugins/airbrake/src/api/index.ts new file mode 100644 index 0000000000..2d92789653 --- /dev/null +++ b/plugins/airbrake/src/api/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './mock'; +export type { AirbrakeApi } from './AirbrakeApi'; +export { airbrakeApiRef } from './AirbrakeApi'; +export type { Groups } from './airbrakeGroups'; +export { ProductionAirbrakeApi } from './ProductionApi'; diff --git a/plugins/airbrake/src/api/mock/LocalDiscoveryApi.ts b/plugins/airbrake/src/api/mock/LocalDiscoveryApi.ts new file mode 100644 index 0000000000..c3080a8104 --- /dev/null +++ b/plugins/airbrake/src/api/mock/LocalDiscoveryApi.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2022 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 { DiscoveryApi } from '@backstage/core-plugin-api'; + +export const localDiscoveryApi: DiscoveryApi = { + async getBaseUrl(pluginId: string): Promise { + return `http://localhost:7007/api/${pluginId}`; + }, +}; diff --git a/plugins/catalog-backend/src/util/timing.ts b/plugins/airbrake/src/api/mock/MockApi.ts similarity index 53% rename from plugins/catalog-backend/src/util/timing.ts rename to plugins/airbrake/src/api/mock/MockApi.ts index b5f9eb258f..dbb1d5c049 100644 --- a/plugins/catalog-backend/src/util/timing.ts +++ b/plugins/airbrake/src/api/mock/MockApi.ts @@ -14,18 +14,20 @@ * limitations under the License. */ -/** - * Returns a string with the elapsed time since the start of an operation, - * with some human friendly precision, e.g. "133ms" or "14.5s". - * - * @param startTimestamp - The timestamp (from process.hrtime()) at the start ot - * the operation - */ -export function durationText(startTimestamp: [number, number]): string { - const delta = process.hrtime(startTimestamp); - const seconds = delta[0] + delta[1] / 1e9; - if (seconds > 1) { - return `${seconds.toFixed(1)}s`; +import { Groups } from '../airbrakeGroups'; +import { AirbrakeApi } from '../AirbrakeApi'; +import mockGroupsData from './airbrakeGroupsApiMock.json'; + +export class MockAirbrakeApi implements AirbrakeApi { + waitTimeInMillis: number; + + constructor(waitTimeInMillis = 10) { + this.waitTimeInMillis = waitTimeInMillis; + } + + fetchGroups(): Promise { + return new Promise(resolve => { + setTimeout(() => resolve(mockGroupsData), this.waitTimeInMillis); + }); } - return `${(seconds * 1000).toFixed(0)}ms`; } diff --git a/plugins/airbrake/src/api/mock/MockEntity.ts b/plugins/airbrake/src/api/mock/MockEntity.ts new file mode 100644 index 0000000000..13534f2458 --- /dev/null +++ b/plugins/airbrake/src/api/mock/MockEntity.ts @@ -0,0 +1,32 @@ +/* + * Copyright 2022 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 { AIRBRAKE_PROJECT_ID_ANNOTATION } from '../../components/useProjectId'; +import { Entity } from '@backstage/catalog-model'; + +export const createEntity = (projectId?: number) => { + const projectIdString = projectId?.toString(); + + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + annotations: { + [AIRBRAKE_PROJECT_ID_ANNOTATION]: projectIdString, + }, + name: projectIdString, + }, + } as Entity; +}; diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/example-data.json b/plugins/airbrake/src/api/mock/airbrakeGroupsApiMock.json similarity index 100% rename from plugins/airbrake/src/components/EntityAirbrakeContent/example-data.json rename to plugins/airbrake/src/api/mock/airbrakeGroupsApiMock.json diff --git a/plugins/airbrake/src/api/mock/index.ts b/plugins/airbrake/src/api/mock/index.ts new file mode 100644 index 0000000000..4e0ab5fabf --- /dev/null +++ b/plugins/airbrake/src/api/mock/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './MockApi'; +export * from './MockEntity'; +export * from './LocalDiscoveryApi'; diff --git a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx b/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx deleted file mode 100644 index 062d9e898a..0000000000 --- a/plugins/airbrake/src/components/EntityAirbrakeContent/EntityAirbrakeContent.tsx +++ /dev/null @@ -1,48 +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 { Grid, Typography } from '@material-ui/core'; -import { InfoCard } from '@backstage/core-components'; -import exampleData from './example-data.json'; -import hash from 'object-hash'; -import { makeStyles } from '@material-ui/core/styles'; -import { BackstageTheme } from '@backstage/theme'; - -const useStyles = makeStyles(() => ({ - multilineText: { - whiteSpace: 'pre-wrap', - }, -})); - -export const EntityAirbrakeContent = () => { - const classes = useStyles(); - - return ( - - {exampleData.groups.map(group => ( - - {group.errors.map(error => ( - - - {error.message} - - - ))} - - ))} - - ); -}; diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx new file mode 100644 index 0000000000..ee14a17f9a --- /dev/null +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.test.tsx @@ -0,0 +1,96 @@ +/* + * 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 { EntityAirbrakeWidget } from './EntityAirbrakeWidget'; +import exampleData from '../../api/mock/airbrakeGroupsApiMock.json'; +import { + MockErrorApi, + renderInTestApp, + setupRequestMockHandlers, + TestApiProvider, +} from '@backstage/test-utils'; +import { createEntity } from '../../api'; +import { + airbrakeApiRef, + localDiscoveryApi, + MockAirbrakeApi, + ProductionAirbrakeApi, +} from '../../api'; +import { errorApiRef } from '@backstage/core-plugin-api'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; + +describe('EntityAirbrakeContent', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + it('renders all errors sent from Airbrake', async () => { + const widget = await renderInTestApp( + + + , + ); + expect(exampleData.groups.length).toBeGreaterThan(0); + for (const group of exampleData.groups) { + expect( + await widget.getByText(group.errors[0].message), + ).toBeInTheDocument(); + } + }); + + it('states that the annotation is missing if no project ID annotation is provided', async () => { + const widget = await renderInTestApp( + + + , + ); + await expect( + widget.findByText('Missing Annotation'), + ).resolves.toBeInTheDocument(); + }); + + it('states that an error occurred if the API call fails', async () => { + worker.use( + rest.get( + 'http://localhost:7007/api/airbrake/api/v4/projects/123/groups', + (_, res, ctx) => { + return res(ctx.status(500)); + }, + ), + ); + const mockErrorApi = new MockErrorApi({ collect: true }); + + const widget = await renderInTestApp( + + + , + ); + + await expect( + widget.findByText(/.*there was an issue communicating with Airbrake.*/), + ).resolves.toBeInTheDocument(); + expect(mockErrorApi.getErrors().length).toBe(1); + expect(mockErrorApi.getErrors()[0].error.message).toStrictEqual( + 'Failed fetching Airbrake groups', + ); + }); +}); diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx new file mode 100644 index 0000000000..b11116aeef --- /dev/null +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/EntityAirbrakeWidget.tsx @@ -0,0 +1,94 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import React, { useEffect } from 'react'; +import { Grid, Typography } from '@material-ui/core'; +import { + EmptyState, + InfoCard, + MissingAnnotationEmptyState, + Progress, +} from '@backstage/core-components'; +import hash from 'object-hash'; +import { makeStyles } from '@material-ui/core/styles'; +import { BackstageTheme } from '@backstage/theme'; +import { ErrorApi, errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { airbrakeApiRef } from '../../api'; +import useAsync from 'react-use/lib/useAsync'; +import { AIRBRAKE_PROJECT_ID_ANNOTATION, useProjectId } from '../useProjectId'; + +const useStyles = makeStyles(() => ({ + multilineText: { + whiteSpace: 'pre-wrap', + }, +})); + +export const EntityAirbrakeWidget = ({ entity }: { entity: Entity }) => { + const classes = useStyles(); + + const projectId = useProjectId(entity); + const errorApi = useApi(errorApiRef); + const airbrakeApi = useApi(airbrakeApiRef); + + const { loading, value, error } = useAsync( + () => airbrakeApi.fetchGroups(projectId), + [airbrakeApi, projectId], + ); + + useEffect(() => { + if (error) { + errorApi.post(error); + } + }, [error, errorApi]); + + if (loading || !projectId || error) { + return ( + + {loading && } + + {!loading && !projectId && ( + + )} + + {!loading && error && ( + + )} + + ); + } + + return ( + + {value?.groups?.map(group => ( + + {group.errors?.map(groupError => ( + + + {groupError.message} + + + ))} + + ))} + + ); +}; diff --git a/plugins/airbrake/src/components/EntityAirbrakeWidget/index.ts b/plugins/airbrake/src/components/EntityAirbrakeWidget/index.ts new file mode 100644 index 0000000000..0a85263c5f --- /dev/null +++ b/plugins/airbrake/src/components/EntityAirbrakeWidget/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { EntityAirbrakeWidget } from './EntityAirbrakeWidget'; diff --git a/plugins/airbrake/src/components/useProjectId.ts b/plugins/airbrake/src/components/useProjectId.ts new file mode 100644 index 0000000000..3402778b7b --- /dev/null +++ b/plugins/airbrake/src/components/useProjectId.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; + +export const AIRBRAKE_PROJECT_ID_ANNOTATION = 'airbrake.io/project-id'; + +export const useProjectId = (entity: Entity) => { + return entity?.metadata.annotations?.[AIRBRAKE_PROJECT_ID_ANNOTATION] ?? ''; +}; diff --git a/plugins/airbrake/src/extensions.test.tsx b/plugins/airbrake/src/extensions.test.tsx new file mode 100644 index 0000000000..05afe8f1e3 --- /dev/null +++ b/plugins/airbrake/src/extensions.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { EntityAirbrakeContent } from './extensions'; +import { Route } from 'react-router'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { airbrakeApiRef, MockAirbrakeApi } from './api'; +import { createEntity } from './api'; +import { EntityProvider } from '@backstage/plugin-catalog-react'; + +describe('The Airbrake entity', () => { + it('should render the content properly', async () => { + const rendered = await renderInTestApp( + + + } /> + + , + ); + await expect( + rendered.findByText('ChunkLoadError'), + ).resolves.toBeInTheDocument(); + }); +}); diff --git a/plugins/airbrake/src/extensions.tsx b/plugins/airbrake/src/extensions.tsx new file mode 100644 index 0000000000..d4f890f44a --- /dev/null +++ b/plugins/airbrake/src/extensions.tsx @@ -0,0 +1,42 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { useEntity } from '@backstage/plugin-catalog-react'; +import React from 'react'; +import { airbrakePlugin } from './plugin'; +import { createRoutableExtension } from '@backstage/core-plugin-api'; +import { rootRouteRef } from './routes'; + +/** + * This is the widget that shows up on a component page + * + * @public + */ +export const EntityAirbrakeContent = airbrakePlugin.provide( + createRoutableExtension({ + name: 'EntityAirbrakeContent', + mountPoint: rootRouteRef, + component: () => + import('./components/EntityAirbrakeWidget').then( + ({ EntityAirbrakeWidget }) => { + return () => { + const { entity } = useEntity(); + return ; + }; + }, + ), + }), +); diff --git a/plugins/airbrake/src/index.ts b/plugins/airbrake/src/index.ts index 8f319e02fd..2b2406776b 100644 --- a/plugins/airbrake/src/index.ts +++ b/plugins/airbrake/src/index.ts @@ -13,4 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { airbrakePlugin, EntityAirbrakeContent } from './plugin'; + +/** + * The Airbrake plugin provides connectivity between Backstage and Airbrake (https://airbrake.io/). + * + * @packageDocumentation + */ + +export { airbrakePlugin } from './plugin'; +export { EntityAirbrakeContent } from './extensions'; diff --git a/plugins/airbrake/src/plugin.test.ts b/plugins/airbrake/src/plugin.test.ts index c8e9c30e83..ad7865713f 100644 --- a/plugins/airbrake/src/plugin.test.ts +++ b/plugins/airbrake/src/plugin.test.ts @@ -15,9 +15,16 @@ */ import { airbrakePlugin } from './plugin'; +import { ProductionAirbrakeApi } from './api'; describe('catalog', () => { it('should export plugin', () => { expect(airbrakePlugin).toBeDefined(); }); + + it('should have at least one API, the production API', () => { + const apiFactories = Array.from(airbrakePlugin.getApis()); + expect(apiFactories.length).toBe(1); + expect(apiFactories[0].factory({})).toBeInstanceOf(ProductionAirbrakeApi); + }); }); diff --git a/plugins/airbrake/src/plugin.ts b/plugins/airbrake/src/plugin.ts index 0a3679baa4..56f65ba110 100644 --- a/plugins/airbrake/src/plugin.ts +++ b/plugins/airbrake/src/plugin.ts @@ -14,26 +14,29 @@ * limitations under the License. */ import { + createApiFactory, createPlugin, - createRoutableExtension, + discoveryApiRef, } from '@backstage/core-plugin-api'; import { rootRouteRef } from './routes'; +import { airbrakeApiRef, ProductionAirbrakeApi } from './api'; +/** + * The Airbrake plugin instance + * + * @public + */ export const airbrakePlugin = createPlugin({ id: 'airbrake', + apis: [ + createApiFactory({ + api: airbrakeApiRef, + deps: { discoveryApi: discoveryApiRef }, + factory: ({ discoveryApi }) => new ProductionAirbrakeApi(discoveryApi), + }), + ], routes: { root: rootRouteRef, }, }); - -export const EntityAirbrakeContent = airbrakePlugin.provide( - createRoutableExtension({ - name: 'EntityAirbrakeContent', - component: () => - import('./components/EntityAirbrakeContent/EntityAirbrakeContent').then( - m => m.EntityAirbrakeContent, - ), - mountPoint: rootRouteRef, - }), -); diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md index 9f5d2cc730..43e83e2d1e 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-allure +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.15 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.1.14 ### Patch Changes diff --git a/plugins/allure/package.json b/plugins/allure/package.json index 5ea3129746..eaa3013da8 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-allure", "description": "A Backstage plugin that integrates with Allure", - "version": "0.1.14", + "version": "0.1.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,22 +11,25 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -37,10 +40,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md index e24ea89a0a..ccf5ebd98c 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-analytics-module-ga +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.10 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + ## 0.1.9 ### Patch Changes diff --git a/plugins/analytics-module-ga/README.md b/plugins/analytics-module-ga/README.md index 92cfd7e9ae..4b822ea3e3 100644 --- a/plugins/analytics-module-ga/README.md +++ b/plugins/analytics-module-ga/README.md @@ -47,6 +47,19 @@ app: trackingId: UA-0000000-0 ``` +4. Update CSP in your `app-config.yaml`: + +The following is the minimal content security policy required to load scripts from GA. + +```yaml +backend: + csp: + connect-src: ["'self'", 'http:', 'https:'] + # Add these two lines below + script-src: ["'self'", "'unsafe-eval'", 'https://www.google-analytics.com'] + img-src: ["'self'", 'data:', 'https://www.google-analytics.com'] +``` + ## Configuration In order to be able to analyze usage of your Backstage instance _by plugin_, we diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 0b0c3a6cdc..d71b66cd6f 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-ga", - "version": "0.1.9", + "version": "0.1.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,21 +10,24 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin-module" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/theme": "^0.2.14", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -35,10 +38,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md index 03bef4543a..e86a6cef5d 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-apache-airflow +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.7 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + ## 0.1.6 ### Patch Changes diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index e36e925fcb..b5045551bb 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apache-airflow", - "version": "0.1.6", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -9,19 +9,22 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -33,10 +36,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 73de279056..6bbbe9d056 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-api-docs +## 0.8.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- 306c366fc9: chore(deps): bump `@asyncapi/react-component` +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/plugin-catalog@0.9.0 + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.7.3 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog@0.8.0 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.7.2 ### Patch Changes diff --git a/plugins/api-docs/api-report.md b/plugins/api-docs/api-report.md index b626da89a5..b6683d9b40 100644 --- a/plugins/api-docs/api-report.md +++ b/plugins/api-docs/api-report.md @@ -9,7 +9,6 @@ import { ApiEntity } from '@backstage/catalog-model'; import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { CatalogTableRow } from '@backstage/plugin-catalog'; -import { Entity } from '@backstage/catalog-model'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -17,11 +16,10 @@ import { TableColumn } from '@backstage/core-components'; import { TableProps } from '@backstage/core-components'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ApiDefinitionCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ApiDefinitionCard: (_: Props) => JSX.Element; +export const ApiDefinitionCard: () => JSX.Element; // Warning: (ae-missing-release-tag) "ApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -92,13 +90,13 @@ export type AsyncApiDefinitionWidgetProps = { // Warning: (ae-missing-release-tag) "ConsumedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ConsumedApisCard: ({ variant }: Props_2) => JSX.Element; +export const ConsumedApisCard: ({ variant }: Props) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ConsumingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ConsumingComponentsCard: ({ variant }: Props_5) => JSX.Element; +export const ConsumingComponentsCard: ({ variant }: Props_4) => JSX.Element; // @public export const DefaultApiExplorerPage: ({ @@ -122,9 +120,7 @@ export function defaultDefinitionWidgets(): ApiDefinitionWidget[]; // Warning: (ae-missing-release-tag) "EntityApiDefinitionCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityApiDefinitionCard: (_: { - apiEntity?: ApiEntity | undefined; -}) => JSX.Element; +export const EntityApiDefinitionCard: () => JSX.Element; // Warning: (ae-missing-release-tag) "EntityConsumedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -132,7 +128,6 @@ export const EntityApiDefinitionCard: (_: { export const EntityConsumedApisCard: ({ variant, }: { - entity?: Entity | undefined; variant?: 'gridItem' | undefined; }) => JSX.Element; @@ -142,7 +137,6 @@ export const EntityConsumedApisCard: ({ export const EntityConsumingComponentsCard: ({ variant, }: { - entity?: Entity | undefined; variant?: 'gridItem' | undefined; }) => JSX.Element; @@ -161,7 +155,6 @@ export const EntityHasApisCard: ({ export const EntityProvidedApisCard: ({ variant, }: { - entity?: Entity | undefined; variant?: 'gridItem' | undefined; }) => JSX.Element; @@ -171,7 +164,6 @@ export const EntityProvidedApisCard: ({ export const EntityProvidingComponentsCard: ({ variant, }: { - entity?: Entity | undefined; variant?: 'gridItem' | undefined; }) => JSX.Element; @@ -193,7 +185,7 @@ export type GraphQlDefinitionWidgetProps = { // Warning: (ae-missing-release-tag) "HasApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const HasApisCard: ({ variant }: Props_3) => JSX.Element; +export const HasApisCard: ({ variant }: Props_2) => JSX.Element; // Warning: (ae-missing-release-tag) "OpenApiDefinitionWidget" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -228,11 +220,11 @@ export type PlainApiDefinitionWidgetProps = { // Warning: (ae-missing-release-tag) "ProvidedApisCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ProvidedApisCard: ({ variant }: Props_4) => JSX.Element; +export const ProvidedApisCard: ({ variant }: Props_3) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "ProvidingComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ProvidingComponentsCard: ({ variant }: Props_6) => JSX.Element; +export const ProvidingComponentsCard: ({ variant }: Props_5) => JSX.Element; ``` diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 3b92cdf17e..331845bd13 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-api-docs", "description": "A Backstage plugin that helps represent API entities in the frontend", - "version": "0.7.2", + "version": "0.8.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -20,23 +23,23 @@ "backstage" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@asyncapi/react-component": "1.0.0-next.32", - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog": "^0.7.12", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@asyncapi/react-component": "1.0.0-next.33", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog": "^0.9.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -53,10 +56,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx index 6f3e155816..353b973626 100644 --- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx +++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.tsx @@ -24,12 +24,7 @@ import { PlainApiDefinitionWidget } from '../PlainApiDefinitionWidget'; import { CardTab, TabbedCard } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - apiEntity?: ApiEntity; -}; - -export const ApiDefinitionCard = (_: Props) => { +export const ApiDefinitionCard = () => { const { entity } = useEntity(); const config = useApi(apiDocsConfigRef); const { getApiDefinitionWidget } = config; diff --git a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx index 9e4c861e24..16b335b0a7 100644 --- a/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx +++ b/plugins/api-docs/src/components/ApiExplorerPage/DefaultApiExplorerPage.test.tsx @@ -56,8 +56,8 @@ describe('DefaultApiExplorerPage', () => { }, ] as Entity[], }), - getLocationByEntity: () => - Promise.resolve({ id: 'id', type: 'github', target: 'url' }), + getLocationByRef: () => + Promise.resolve({ id: 'id', type: 'url', target: 'url' }), getEntityByName: async entityName => { return { apiVersion: 'backstage.io/v1alpha1', @@ -66,7 +66,8 @@ describe('DefaultApiExplorerPage', () => { relations: [ { type: RELATION_MEMBER_OF, - target: { namespace: 'default', kind: 'Group', name: 'tools' }, + targetRef: 'group:default/tools', + target: { namespace: 'default', kind: 'group', name: 'tools' }, }, ], }; diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx index 5e7b22524a..f84f66cee8 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx @@ -36,7 +36,7 @@ describe('', () => { getEntityByName: jest.fn(), getEntities: jest.fn(), addLocation: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), } as any; let Wrapper: React.ComponentType; @@ -95,10 +95,11 @@ describe('', () => { relations: [ { target: { - kind: 'API', + kind: 'api', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'api:my-namespace/target-name', type: RELATION_CONSUMES_API, }, ], diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx index 56b8559537..e810e06ff7 100644 --- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.tsx @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - ApiEntity, - Entity, - RELATION_CONSUMES_API, -} from '@backstage/catalog-model'; +import { ApiEntity, RELATION_CONSUMES_API } from '@backstage/catalog-model'; import { Typography } from '@material-ui/core'; import { EntityTable, @@ -36,8 +32,6 @@ import { } from '@backstage/core-components'; type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; variant?: 'gridItem'; }; diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx index 4102bebf0e..daa909c7f8 100644 --- a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx @@ -36,7 +36,7 @@ describe('', () => { getEntityByName: jest.fn(), getEntities: jest.fn(), addLocation: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), } as any; let Wrapper: React.ComponentType; @@ -95,10 +95,11 @@ describe('', () => { relations: [ { target: { - kind: 'API', + kind: 'api', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'api:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx index b29075aee9..852386140c 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx @@ -36,7 +36,7 @@ describe('', () => { getEntityByName: jest.fn(), getEntities: jest.fn(), addLocation: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), } as any; let Wrapper: React.ComponentType; @@ -95,10 +95,11 @@ describe('', () => { relations: [ { target: { - kind: 'API', + kind: 'api', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'api:my-namespace/target-name', type: RELATION_PROVIDES_API, }, ], diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx index efdc88b2e1..a63546176f 100644 --- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx +++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.tsx @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - ApiEntity, - Entity, - RELATION_PROVIDES_API, -} from '@backstage/catalog-model'; +import { ApiEntity, RELATION_PROVIDES_API } from '@backstage/catalog-model'; import { Typography } from '@material-ui/core'; import { EntityTable, @@ -36,8 +32,6 @@ import { } from '@backstage/core-components'; type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; variant?: 'gridItem'; }; diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx index 06022eec69..a62b2119c7 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx @@ -32,7 +32,7 @@ describe('', () => { getEntityByName: jest.fn(), getEntities: jest.fn(), addLocation: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), } as any; let Wrapper: React.ComponentType; @@ -98,10 +98,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_API_CONSUMED_BY, }, ], diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx index 20fa7021cc..5455f37f14 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.tsx @@ -16,7 +16,6 @@ import { ComponentEntity, - Entity, RELATION_API_CONSUMED_BY, } from '@backstage/catalog-model'; import { Typography } from '@material-ui/core'; @@ -35,8 +34,6 @@ import { } from '@backstage/core-components'; type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; variant?: 'gridItem'; }; diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx index 275338d2f9..1a0aae12c7 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx @@ -32,7 +32,7 @@ describe('', () => { getEntityByName: jest.fn(), getEntities: jest.fn(), addLocation: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), } as any; let Wrapper: React.ComponentType; @@ -98,10 +98,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_API_PROVIDED_BY, }, ], diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx index 286a360e9a..9bcace393e 100644 --- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx +++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.tsx @@ -16,7 +16,6 @@ import { ComponentEntity, - Entity, RELATION_API_PROVIDED_BY, } from '@backstage/catalog-model'; import { Typography } from '@material-ui/core'; @@ -35,8 +34,6 @@ import { } from '@backstage/core-components'; type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; variant?: 'gridItem'; }; diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 882979326e..baf9a928d8 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-app-backend +## 0.3.27 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/config-loader@0.9.6 + +## 0.3.26 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/config-loader@0.9.5 + - @backstage/types@0.1.3 + +## 0.3.25 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 0107c9aa08: chore(deps): bump `helmet` from 4.4.1 to 5.0.2 +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/config-loader@0.9.4 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.3.24 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 52da00fed3..79682ca9de 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.24", + "version": "0.3.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,25 +24,25 @@ "backstage" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config-loader": "^0.9.3", - "@backstage/config": "^0.1.13", - "@backstage/types": "^0.1.1", + "@backstage/backend-common": "^0.11.0", + "@backstage/config-loader": "^0.9.6", + "@backstage/config": "^0.1.15", + "@backstage/types": "^0.1.3", "@types/express": "^4.17.6", "globby": "^11.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", "fs-extra": "9.1.0", - "helmet": "^4.0.0", + "helmet": "^5.0.2", "knex": "^1.0.2", "lodash": "^4.17.21", "luxon": "^2.0.2", @@ -47,9 +50,9 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.17", - "@backstage/cli": "^0.13.2", - "@backstage/types": "^0.1.1", + "@backstage/backend-test-utils": "^0.1.19", + "@backstage/cli": "^0.14.1", + "@backstage/types": "^0.1.3", "@types/supertest": "^2.0.8", "mock-fs": "^5.1.0", "msw": "^0.35.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index a19153c4e9..5f85363fcc 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,68 @@ # @backstage/plugin-auth-backend +## 0.11.0 + +### Minor Changes + +- 3884bf0348: **BREAKING**: The default sign-in resolvers for all providers, if you choose to + use them, now emit the token `sub` and `ent` claims on the standard, + all-lowercase form, instead of the mixed-case form. The mixed-case form causes + problems for implementations that naively do string comparisons on refs. The end + result is that you may for example see your Backstage token `sub` claim now + become `'user:default/my-id'` instead of `'user:default/My-ID'`. + + On a related note, specifically the SAML provider now correctly issues both + `sub` and `ent` claims, and on the full entity ref form instead of the short + form with only the ID. + + **NOTE**: For a long time, it has been strongly recommended that you provide + your own sign-in resolver instead of using the builtin ones, and that will + become mandatory in the future. + +### Patch Changes + +- d64b8d3678: chore(deps): bump `minimatch` from 3.0.4 to 5.0.0 +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/plugin-auth-node@0.1.3 + +## 0.10.2 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/types@0.1.3 + - @backstage/plugin-auth-node@0.1.2 + +## 0.10.1 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- a31559d1f5: Bump `passport-oauth2` to version 1.6.1 +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- 1433045c08: Removed unused `helmet` dependency. +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/errors@0.2.1 + - @backstage/plugin-auth-node@0.1.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.10.0 ### Minor Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 5d40523895..7a370ee8a9 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.10.0", + "version": "0.11.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,22 +24,22 @@ "backstage" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/plugin-auth-node": "^0.1.0", - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/types": "^0.1.1", + "@backstage/plugin-auth-node": "^0.1.3", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/types": "^0.1.3", "@google-cloud/firestore": "^5.0.2", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", @@ -48,13 +51,12 @@ "express-session": "^1.17.1", "fs-extra": "9.1.0", "google-auth-library": "^7.6.1", - "helmet": "^4.0.0", "jose": "^1.27.1", "jwt-decode": "^3.1.0", "knex": "^1.0.2", "lodash": "^4.17.21", "luxon": "^2.0.2", - "minimatch": "^3.0.3", + "minimatch": "^5.0.0", "morgan": "^1.10.0", "node-fetch": "^2.6.7", "node-cache": "^5.1.2", @@ -74,8 +76,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts index 897233294f..d2cee7bd89 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.test.ts @@ -30,11 +30,11 @@ describe('CatalogIdentityClient', () => { getEntities: jest.fn(), addLocation: jest.fn(), removeLocationById: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; const tokenManager: jest.Mocked = { getToken: jest.fn(), @@ -79,8 +79,9 @@ describe('CatalogIdentityClient', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:default/team-a', target: { - kind: 'Group', + kind: 'group', namespace: 'default', name: 'team-a', }, @@ -100,8 +101,9 @@ describe('CatalogIdentityClient', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:reality/screen-actors-guild', target: { - kind: 'Group', + kind: 'group', namespace: 'reality', name: 'screen-actors-guild', }, diff --git a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts index aecf018975..d948af2e05 100644 --- a/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts +++ b/plugins/auth-backend/src/lib/catalog/CatalogIdentityClient.ts @@ -120,11 +120,11 @@ export class CatalogIdentityClient { e => e!.relations ?.filter(r => r.type === RELATION_MEMBER_OF) - .map(r => r.target) ?? [], + .map(r => r.targetRef) ?? [], ); const newEntityRefs = [ - ...new Set(resolvedEntityRefs.concat(memberOf).map(stringifyEntityRef)), + ...new Set(resolvedEntityRefs.map(stringifyEntityRef).concat(memberOf)), ]; logger?.debug(`Found catalog membership: ${newEntityRefs.join()}`); diff --git a/plugins/auth-backend/src/lib/catalog/helpers.ts b/plugins/auth-backend/src/lib/catalog/helpers.ts index 61dc428b3f..db9b38e2a6 100644 --- a/plugins/auth-backend/src/lib/catalog/helpers.ts +++ b/plugins/auth-backend/src/lib/catalog/helpers.ts @@ -27,11 +27,9 @@ export function getEntityClaims(entity: UserEntity): TokenParams['claims'] { const membershipRefs = entity.relations ?.filter( - r => - r.type === RELATION_MEMBER_OF && - r.target.kind.toLocaleLowerCase('en-US') === 'group', + r => r.type === RELATION_MEMBER_OF && r.targetRef.startsWith('group:'), ) - .map(r => stringifyEntityRef(r.target)) ?? []; + .map(r => r.targetRef) ?? []; return { sub: userRef, diff --git a/plugins/auth-backend/src/providers/github/provider.ts b/plugins/auth-backend/src/providers/github/provider.ts index ec7f8cafa6..29e8679fe0 100644 --- a/plugins/auth-backend/src/providers/github/provider.ts +++ b/plugins/auth-backend/src/providers/github/provider.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + DEFAULT_NAMESPACE, + stringifyEntityRef, +} from '@backstage/catalog-model'; import express from 'express'; import { Logger } from 'winston'; import { Profile as PassportProfile } from 'passport'; @@ -247,10 +251,16 @@ export const githubDefaultSignInResolver: SignInResolver< const userId = fullProfile.username || fullProfile.id; + const entityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: userId, + }); + const token = await ctx.tokenIssuer.issueToken({ claims: { - sub: `user:default/${userId}`, - ent: [`user:default/${userId}`], + sub: entityRef, + ent: [entityRef], }, }); diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 6689aba884..59f9a5a4cb 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -14,10 +14,13 @@ * limitations under the License. */ +import { + DEFAULT_NAMESPACE, + stringifyEntityRef, +} from '@backstage/catalog-model'; import express from 'express'; import { Strategy as GitlabStrategy } from 'passport-gitlab2'; import { Logger } from 'winston'; - import { executeRedirectStrategy, executeFrameHandlerStrategy, @@ -71,8 +74,17 @@ export const gitlabDefaultSignInResolver: SignInResolver = async ( id = profile.email.split('@')[0]; } + const entityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: id, + }); + const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: `user:default/${id}`, ent: [`user:default/${id}`] }, + claims: { + sub: entityRef, + ent: [entityRef], + }, }); return { id, token }; diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index fbaf8c7462..e5e5698ec4 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + DEFAULT_NAMESPACE, + stringifyEntityRef, +} from '@backstage/catalog-model'; import express from 'express'; import passport from 'passport'; import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; @@ -226,8 +230,17 @@ const googleDefaultSignInResolver: SignInResolver = async ( userId = profile.email.split('@')[0]; } + const entityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: userId, + }); + const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: `user:default/${userId}`, ent: [`user:default/${userId}`] }, + claims: { + sub: entityRef, + ent: [entityRef], + }, }); return { id: userId, token }; diff --git a/plugins/auth-backend/src/providers/microsoft/provider.ts b/plugins/auth-backend/src/providers/microsoft/provider.ts index 25c940547b..98f94811bd 100644 --- a/plugins/auth-backend/src/providers/microsoft/provider.ts +++ b/plugins/auth-backend/src/providers/microsoft/provider.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + DEFAULT_NAMESPACE, + stringifyEntityRef, +} from '@backstage/catalog-model'; import express from 'express'; import passport from 'passport'; import { Strategy as MicrosoftStrategy } from 'passport-microsoft'; @@ -231,10 +235,16 @@ export const microsoftDefaultSignInResolver: SignInResolver< const userId = profile.email.split('@')[0]; + const entityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: userId, + }); + const token = await ctx.tokenIssuer.issueToken({ claims: { - sub: `user:default/${userId}`, - ent: [`user:default/${userId}`], + sub: entityRef, + ent: [entityRef], }, }); diff --git a/plugins/auth-backend/src/providers/oauth2/index.ts b/plugins/auth-backend/src/providers/oauth2/index.ts index 767466f8f6..d68208a148 100644 --- a/plugins/auth-backend/src/providers/oauth2/index.ts +++ b/plugins/auth-backend/src/providers/oauth2/index.ts @@ -15,5 +15,4 @@ */ export { createOAuth2Provider } from './provider'; - export type { OAuth2ProviderOptions } from './provider'; diff --git a/plugins/auth-backend/src/providers/oauth2/provider.ts b/plugins/auth-backend/src/providers/oauth2/provider.ts index 0906005791..d09337f5ad 100644 --- a/plugins/auth-backend/src/providers/oauth2/provider.ts +++ b/plugins/auth-backend/src/providers/oauth2/provider.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + DEFAULT_NAMESPACE, + stringifyEntityRef, +} from '@backstage/catalog-model'; import express from 'express'; import passport from 'passport'; import { Strategy as OAuth2Strategy } from 'passport-oauth2'; @@ -210,8 +214,17 @@ export const oAuth2DefaultSignInResolver: SignInResolver = async ( const userId = profile.email.split('@')[0]; + const entityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: userId, + }); + const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: `user:default/${userId}`, ent: [`user:default/${userId}`] }, + claims: { + sub: entityRef, + ent: [entityRef], + }, }); return { id: userId, token }; diff --git a/plugins/auth-backend/src/providers/oidc/index.ts b/plugins/auth-backend/src/providers/oidc/index.ts index 39bd02928d..e63c9920df 100644 --- a/plugins/auth-backend/src/providers/oidc/index.ts +++ b/plugins/auth-backend/src/providers/oidc/index.ts @@ -13,5 +13,6 @@ * 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 9ccdf25f74..b4f34cd127 100644 --- a/plugins/auth-backend/src/providers/oidc/provider.ts +++ b/plugins/auth-backend/src/providers/oidc/provider.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + DEFAULT_NAMESPACE, + stringifyEntityRef, +} from '@backstage/catalog-model'; import express from 'express'; import { Client, @@ -211,21 +215,31 @@ export class OidcAuthProvider implements OAuthHandlers { } } -export const oAuth2DefaultSignInResolver: SignInResolver< - OidcAuthResult -> = async (info, ctx) => { +export const oidcDefaultSignInResolver: SignInResolver = async ( + info, + ctx, +) => { const { profile } = info; if (!profile.email) { throw new Error('Profile contained no email'); } + const userId = profile.email.split('@')[0]; + + const entityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: userId, + }); + const token = await ctx.tokenIssuer.issueToken({ claims: { - sub: `user:default/${userId}`, - ent: [`user:default/${userId}`], + sub: entityRef, + ent: [entityRef], }, }); + return { id: userId, token }; }; @@ -289,7 +303,7 @@ export const createOidcProvider = ( }, }); const signInResolverFn = - options?.signIn?.resolver ?? oAuth2DefaultSignInResolver; + options?.signIn?.resolver ?? oidcDefaultSignInResolver; const signInResolver: SignInResolver = info => signInResolverFn(info, { catalogIdentityClient, diff --git a/plugins/auth-backend/src/providers/okta/provider.ts b/plugins/auth-backend/src/providers/okta/provider.ts index 0ba7d241d3..26f586c578 100644 --- a/plugins/auth-backend/src/providers/okta/provider.ts +++ b/plugins/auth-backend/src/providers/okta/provider.ts @@ -13,6 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import { + DEFAULT_NAMESPACE, + stringifyEntityRef, +} from '@backstage/catalog-model'; import express from 'express'; import { OAuthAdapter, @@ -235,8 +240,17 @@ export const oktaDefaultSignInResolver: SignInResolver = async ( // TODO(Rugvip): Hardcoded to the local part of the email for now const userId = profile.email.split('@')[0]; + const entityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: userId, + }); + const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: `user:default/${userId}`, ent: [`user:default/${userId}`] }, + claims: { + sub: entityRef, + ent: [entityRef], + }, }); return { id: userId, token }; diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index 1ca8cf0d5d..4458d423ef 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + DEFAULT_NAMESPACE, + stringifyEntityRef, +} from '@backstage/catalog-model'; import express from 'express'; import { SamlConfig } from 'passport-saml/lib/passport-saml/types'; import { @@ -150,8 +154,17 @@ const samlDefaultSignInResolver: SignInResolver = async ( ) => { const id = info.result.fullProfile.nameID; + const entityRef = stringifyEntityRef({ + kind: 'User', + namespace: DEFAULT_NAMESPACE, + name: id, + }); + const token = await ctx.tokenIssuer.issueToken({ - claims: { sub: id }, + claims: { + sub: entityRef, + ent: [entityRef], + }, }); return { id, token }; diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index a5103ea034..7676ce7afa 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-auth-node +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + +## 0.1.2 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + +## 0.1.1 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + ## 0.1.0 ### Minor Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 4e059c5e19..2becee849d 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.1.0", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,25 +10,29 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "node-library" + }, "scripts": { - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", "jose": "^1.27.1", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "msw": "^0.35.0", "uuid": "^8.0.0" }, diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index 0b44be4e13..0156f92891 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-azure-devops-backend +## 0.3.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + +## 0.3.5 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/plugin-azure-devops-common@0.2.2 + +## 0.3.4 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/config@0.1.14 + - @backstage/plugin-azure-devops-common@0.2.1 + ## 0.3.3 ### Patch Changes diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index a87a48046e..206294c863 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.3.3", + "version": "0.3.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,19 +10,22 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", - "@backstage/plugin-azure-devops-common": "^0.2.0", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/plugin-azure-devops-common": "^0.2.2", "@types/express": "^4.17.6", "azure-devops-node-api": "^11.0.1", "express": "^4.17.1", @@ -32,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", "msw": "^0.35.0" diff --git a/plugins/azure-devops-common/CHANGELOG.md b/plugins/azure-devops-common/CHANGELOG.md index 74129ea19e..65eaa862fb 100644 --- a/plugins/azure-devops-common/CHANGELOG.md +++ b/plugins/azure-devops-common/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-azure-devops-common +## 0.2.2 + +### Patch Changes + +- Fix for the previous release with missing type declarations. + +## 0.2.1 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` + ## 0.2.0 ### Minor Changes diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json index 02faebca61..7810469bf9 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.2.0", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,15 +24,15 @@ "backstage" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test --passWithNoTests", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0" + "@backstage/cli": "^0.14.0" }, "files": [ "dist" diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 8b0331e75d..7c0a533dd3 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-azure-devops +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.15 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + - @backstage/plugin-azure-devops-common@0.2.1 + ## 0.1.14 ### Patch Changes diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index a34686d856..57c1654527 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.14", + "version": "0.1.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -17,23 +20,23 @@ "directory": "plugins/azure-devops" }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-azure-devops-common": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-azure-devops-common": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -46,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index f025515703..ae93cdbe5a 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-badges-backend +## 0.1.21 + +### Patch Changes + +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + +## 0.1.20 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + +## 0.1.19 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/errors@0.2.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + ## 0.1.18 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index b5fc38f289..94796ba665 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges-backend", "description": "A Backstage backend plugin that generates README badges for your entities", - "version": "0.1.18", + "version": "0.1.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,20 +25,20 @@ "badges" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", "@types/express": "^4.17.6", "badge-maker": "^3.3.0", "cors": "^2.8.5", @@ -45,7 +48,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/badges-backend/src/service/router.test.ts b/plugins/badges-backend/src/service/router.test.ts index 286a2335f1..56dbfb0655 100644 --- a/plugins/badges-backend/src/service/router.test.ts +++ b/plugins/badges-backend/src/service/router.test.ts @@ -61,13 +61,13 @@ describe('createRouter', () => { addLocation: jest.fn(), getEntities: jest.fn(), getEntityByName: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), getLocationById: jest.fn(), removeLocationById: jest.fn(), removeEntityByUid: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; config = new ConfigReader({ diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index 8f2a3b2e84..e624dc36e9 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-badges +## 0.2.24 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.23 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.2.22 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 5250318164..612b9f3b2d 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges", "description": "A Backstage plugin that generates README badges for your entities", - "version": "0.2.22", + "version": "0.2.24", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -17,22 +20,22 @@ "directory": "plugins/badges" }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -43,10 +46,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md index fdd63ff59b..50dc8b2ad3 100644 --- a/plugins/bazaar-backend/CHANGELOG.md +++ b/plugins/bazaar-backend/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-bazaar-backend +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/backend-test-utils@0.1.19 + +## 0.1.10 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/backend-test-utils@0.1.18 + - @backstage/config@0.1.14 + ## 0.1.9 ### Patch Changes diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 71980acea4..cb596770d4 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar-backend", - "version": "0.1.9", + "version": "0.1.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,19 +10,22 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/backend-test-utils": "^0.1.17", - "@backstage/config": "^0.1.13", + "@backstage/backend-common": "^0.11.0", + "@backstage/backend-test-utils": "^0.1.19", + "@backstage/config": "^0.1.15", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -31,7 +34,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2" + "@backstage/cli": "^0.14.1" }, "files": [ "dist", diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index 00f7605888..df72f76d6a 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-bazaar +## 0.1.15 + +### Patch Changes + +- 67a7c02d26: Remove usages of `EntityRef` and `parseEntityName` from `@backstage/catalog-model` +- Updated dependencies + - @backstage/plugin-catalog@0.9.0 + - @backstage/cli@0.14.1 + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.14 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/cli@0.14.0 + - @backstage/catalog-client@0.7.0 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog@0.8.0 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + ## 0.1.13 ### Patch Changes diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index bfdcb18ca3..a38cd1cd16 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.13", + "version": "0.1.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,24 +10,27 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/cli": "^0.13.2", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog": "^0.7.12", - "@backstage/plugin-catalog-react": "^0.6.14", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/cli": "^0.14.1", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog": "^0.9.0", + "@backstage/plugin-catalog-react": "^0.7.0", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -44,8 +47,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/dev-utils": "^0.2.21", + "@backstage/cli": "^0.14.1", + "@backstage/dev-utils": "^0.2.23", "@testing-library/jest-dom": "^5.10.1", "cross-fetch": "^3.1.5" }, diff --git a/plugins/bazaar/src/components/HomePageBazaarInfoCard/HomePageBazaarInfoCard.tsx b/plugins/bazaar/src/components/HomePageBazaarInfoCard/HomePageBazaarInfoCard.tsx index 5624f40765..ec29d1429b 100644 --- a/plugins/bazaar/src/components/HomePageBazaarInfoCard/HomePageBazaarInfoCard.tsx +++ b/plugins/bazaar/src/components/HomePageBazaarInfoCard/HomePageBazaarInfoCard.tsx @@ -47,7 +47,6 @@ import useAsyncFn from 'react-use/lib/useAsyncFn'; import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react'; import { - parseEntityName, stringifyEntityRef, Entity, parseEntityRef, @@ -150,7 +149,7 @@ export const HomePageBazaarInfoCard = ({ const getEntityPageLink = () => { if (bazaarProject?.value?.entityRef) { - const { name, kind, namespace } = parseEntityName( + const { name, kind, namespace } = parseEntityRef( bazaarProject.value.entityRef, ); return entityLink({ kind, namespace, name }); diff --git a/plugins/bazaar/src/components/SortView/SortView.tsx b/plugins/bazaar/src/components/SortView/SortView.tsx index fa97ac69ee..f5bb6facd7 100644 --- a/plugins/bazaar/src/components/SortView/SortView.tsx +++ b/plugins/bazaar/src/components/SortView/SortView.tsx @@ -107,7 +107,7 @@ export const SortView = () => { if (catalogEntityRefs) { bazaarProjects.value?.forEach(async (project: BazaarProject) => { if (project.entityRef) { - if (!catalogEntityRefs?.includes(project.entityRef as string)) { + if (!catalogEntityRefs?.includes(project.entityRef)) { await bazaarApi.updateProject({ ...project, entityRef: null, diff --git a/plugins/bazaar/src/types.ts b/plugins/bazaar/src/types.ts index 30c07286ac..ec3996d1e2 100644 --- a/plugins/bazaar/src/types.ts +++ b/plugins/bazaar/src/types.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { EntityRef } from '@backstage/catalog-model'; - export type Member = { itemId: number; userId: string; @@ -30,7 +28,7 @@ export type Size = 'small' | 'medium' | 'large'; export type BazaarProject = { name: string; id: number; - entityRef?: EntityRef; + entityRef?: string; community: string; status: Status; description: string; diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index 3fc26f7da7..8af11aa8a4 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-bitrise +## 0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.26 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.1.25 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 8b50459a84..228eed3e9b 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitrise", "description": "A Backstage plugin that integrates towards Bitrise", - "version": "0.1.25", + "version": "0.1.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,22 +10,25 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -40,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog-backend-module-aws/.eslintrc.js b/plugins/catalog-backend-module-aws/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/catalog-backend-module-aws/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md new file mode 100644 index 0000000000..749ab3a230 --- /dev/null +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -0,0 +1,41 @@ +# @backstage/plugin-catalog-backend-module-aws + +## 0.1.0 + +### Minor Changes + +- 25e97e7242: Added this new catalog module, initially containing only the + `AwsOrganizationCloudAccountProcessor`. + + Note that this was moved over from the catalog backend itself, and therefore is + no longer part of its builtin set of processors. If you were using this + processor, through making use of the location type `aws-cloud-accounts` and/or + using the configuration key `catalog.processors.awsOrganization`, you will from + now on have to add the processor manually to your catalog. + + First, add the `@backstage/plugin-catalog-backend-module-aws` dependency to your + `packages/backend` package. + + Then, in `packages/backend/src/plugins/catalog.ts`: + + ```diff + +import { AwsOrganizationCloudAccountProcessor } from '@backstage/plugin-catalog-backend-module-aws'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + + builder.addProcessor( + + AwsOrganizationCloudAccountProcessor.fromConfig( + + env.config, + + { logger: env.logger } + + ) + + ); + // ... + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@0.22.0 + - @backstage/catalog-model@0.11.0 diff --git a/plugins/catalog-backend-module-aws/README.md b/plugins/catalog-backend-module-aws/README.md new file mode 100644 index 0000000000..304fe7a080 --- /dev/null +++ b/plugins/catalog-backend-module-aws/README.md @@ -0,0 +1,10 @@ +# Catalog Backend Module for LDAP + +This is an extension module to the plugin-catalog-backend plugin, providing an +`AwsOrganizationCloudAccountProcessor` that can be used to ingest cloud accounts +as `Resource` kind entities. + +## Getting started + +See [Backstage documentation](https://backstage.io/docs/integrations/ldap/org) for details on how to install +and configure the plugin. diff --git a/plugins/catalog-backend-module-aws/api-report.md b/plugins/catalog-backend-module-aws/api-report.md new file mode 100644 index 0000000000..1cab6555fe --- /dev/null +++ b/plugins/catalog-backend-module-aws/api-report.md @@ -0,0 +1,30 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-aws" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; +import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; +import { Config } from '@backstage/config'; +import { LocationSpec } from '@backstage/plugin-catalog-backend'; +import { Logger as Logger_2 } from 'winston'; + +// @public +export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { + // (undocumented) + static fromConfig( + config: Config, + options: { + logger: Logger_2; + }, + ): AwsOrganizationCloudAccountProcessor; + // (undocumented) + getProcessorName(): string; + // (undocumented) + readLocation( + location: LocationSpec, + _optional: boolean, + emit: CatalogProcessorEmit, + ): Promise; +} +``` diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts new file mode 100644 index 0000000000..bb416a576e --- /dev/null +++ b/plugins/catalog-backend-module-aws/config.d.ts @@ -0,0 +1,36 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + catalog?: { + /** + * List of processor-specific options and attributes + */ + processors?: { + /** + * AwsOrganizationCloudAccountProcessor configuration + */ + awsOrganization?: { + provider: { + /** + * The role to be assumed by this processor + */ + roleArn?: string; + }; + }; + }; + }; +} diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json new file mode 100644 index 0000000000..ebe77263ac --- /dev/null +++ b/plugins/catalog-backend-module-aws/package.json @@ -0,0 +1,55 @@ +{ + "name": "@backstage/plugin-catalog-backend-module-aws", + "description": "A Backstage catalog backend module that helps integrate towards AWS", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-backend-module-aws" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" + }, + "dependencies": { + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-backend": "^0.22.0", + "@backstage/types": "^0.1.3", + "aws-sdk": "^2.840.0", + "lodash": "^4.17.21", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/cli": "^0.14.1", + "@types/lodash": "^4.14.151", + "aws-sdk-mock": "^5.2.1" + }, + "files": [ + "dist", + "config.d.ts" + ], + "configSchema": "config.d.ts" +} diff --git a/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts b/plugins/catalog-backend-module-aws/src/awsOrganization/config.test.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.test.ts rename to plugins/catalog-backend-module-aws/src/awsOrganization/config.test.ts diff --git a/plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts b/plugins/catalog-backend-module-aws/src/awsOrganization/config.ts similarity index 100% rename from plugins/catalog-backend/src/ingestion/processors/awsOrganization/config.ts rename to plugins/catalog-backend-module-aws/src/awsOrganization/config.ts diff --git a/plugins/catalog-backend-module-aws/src/index.ts b/plugins/catalog-backend-module-aws/src/index.ts new file mode 100644 index 0000000000..62c10a0f6d --- /dev/null +++ b/plugins/catalog-backend-module-aws/src/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A Backstage catalog backend module that helps integrate towards AWS + * + * @packageDocumentation + */ + +export * from './processors'; diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts similarity index 96% rename from plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts rename to plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts index 88b033c33f..a7da6d9ad3 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.test.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.test.ts @@ -15,13 +15,11 @@ */ import { AwsOrganizationCloudAccountProcessor } from './AwsOrganizationCloudAccountProcessor'; -import * as winston from 'winston'; describe('AwsOrganizationCloudAccountProcessor', () => { describe('readLocation', () => { - const processor = new AwsOrganizationCloudAccountProcessor({ + const processor = new (AwsOrganizationCloudAccountProcessor as any)({ provider: {}, - logger: winston.createLogger(), }); const location = { type: 'aws-cloud-accounts', target: '' }; const emit = jest.fn(); diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts similarity index 80% rename from plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts rename to plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts index 655f99d14a..e5e72d5fa8 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsOrganizationCloudAccountProcessor.ts +++ b/plugins/catalog-backend-module-aws/src/processors/AwsOrganizationCloudAccountProcessor.ts @@ -13,35 +13,41 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { LocationSpec, ResourceEntityV1alpha1 } from '@backstage/catalog-model'; + +import { ResourceEntityV1alpha1 } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { + CatalogProcessor, + CatalogProcessorEmit, + LocationSpec, + results, +} from '@backstage/plugin-catalog-backend'; import AWS, { Credentials, Organizations } from 'aws-sdk'; import { Account, ListAccountsResponse } from 'aws-sdk/clients/organizations'; - -import * as results from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; -import { Config } from '@backstage/config'; import { Logger } from 'winston'; import { AwsOrganizationProviderConfig, readAwsOrganizationConfig, -} from './awsOrganization/config'; +} from '../awsOrganization/config'; const AWS_ORGANIZATION_REGION = 'us-east-1'; const LOCATION_TYPE = 'aws-cloud-accounts'; -const ACCOUNTID_ANNOTATION: string = 'amazonaws.com/account-id'; -const ARN_ANNOTATION: string = 'amazonaws.com/arn'; -const ORGANIZATION_ANNOTATION: string = 'amazonaws.com/organization-id'; +const ACCOUNTID_ANNOTATION = 'amazonaws.com/account-id'; +const ARN_ANNOTATION = 'amazonaws.com/arn'; +const ORGANIZATION_ANNOTATION = 'amazonaws.com/organization-id'; /** * A processor for ingesting AWS Accounts from AWS Organizations. * - * If custom authentication is needed, it can be achieved by configuring the global AWS.credentials object. + * If custom authentication is needed, it can be achieved by configuring the + * global AWS.credentials object. + * + * @public */ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { - logger: Logger; - organizations: Organizations; - provider: AwsOrganizationProviderConfig; + private readonly organizations: Organizations; + private readonly provider: AwsOrganizationProviderConfig; static fromConfig(config: Config, options: { logger: Logger }) { const c = config.getOptionalConfig('catalog.processors.awsOrganization'); @@ -67,12 +73,8 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { }); } - constructor(options: { - provider: AwsOrganizationProviderConfig; - logger: Logger; - }) { + private constructor(options: { provider: AwsOrganizationProviderConfig }) { this.provider = options.provider; - this.logger = options.logger; const credentials = AwsOrganizationCloudAccountProcessor.buildCredentials( this.provider, ); @@ -82,64 +84,8 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { }); // Only available in us-east-1 } - normalizeName(name: string): string { - return name - .trim() - .toLocaleLowerCase() - .replace(/[^a-zA-Z0-9\-]/g, '-'); - } - - extractInformationFromArn(arn: string): { - accountId: string; - organizationId: string; - } { - const parts = arn.split('/'); - - return { - accountId: parts[parts.length - 1], - organizationId: parts[parts.length - 2], - }; - } - - async getAwsAccounts(): Promise { - let awsAccounts: Account[] = []; - let isInitialAttempt = true; - let nextToken = undefined; - while (isInitialAttempt || nextToken) { - isInitialAttempt = false; - const orgAccounts: ListAccountsResponse = await this.organizations - .listAccounts({ NextToken: nextToken }) - .promise(); - if (orgAccounts.Accounts) { - awsAccounts = awsAccounts.concat(orgAccounts.Accounts); - } - nextToken = orgAccounts.NextToken; - } - - return awsAccounts; - } - - mapAccountToComponent(account: Account): ResourceEntityV1alpha1 { - const { accountId, organizationId } = this.extractInformationFromArn( - account.Arn as string, - ); - return { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Resource', - metadata: { - annotations: { - [ACCOUNTID_ANNOTATION]: accountId, - [ARN_ANNOTATION]: account.Arn || '', - [ORGANIZATION_ANNOTATION]: organizationId, - }, - name: this.normalizeName(account.Name || ''), - namespace: 'default', - }, - spec: { - type: 'cloud-account', - owner: 'unknown', - }, - }; + getProcessorName(): string { + return 'AwsOrganizationCloudAccountProcessor'; } async readLocation( @@ -165,10 +111,70 @@ export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { } return true; }) - .forEach((entity: ResourceEntityV1alpha1) => { + .forEach(entity => { emit(results.entity(location, entity)); }); return true; } + + private normalizeName(name: string): string { + return name + .trim() + .toLocaleLowerCase('en-US') + .replace(/[^a-zA-Z0-9\-]/g, '-'); + } + + private extractInformationFromArn(arn: string): { + accountId: string; + organizationId: string; + } { + const parts = arn.split('/'); + + return { + accountId: parts[parts.length - 1], + organizationId: parts[parts.length - 2], + }; + } + + private async getAwsAccounts(): Promise { + let awsAccounts: Account[] = []; + let isInitialAttempt = true; + let nextToken = undefined; + while (isInitialAttempt || nextToken) { + isInitialAttempt = false; + const orgAccounts: ListAccountsResponse = await this.organizations + .listAccounts({ NextToken: nextToken }) + .promise(); + if (orgAccounts.Accounts) { + awsAccounts = awsAccounts.concat(orgAccounts.Accounts); + } + nextToken = orgAccounts.NextToken; + } + + return awsAccounts; + } + + private mapAccountToComponent(account: Account): ResourceEntityV1alpha1 { + const { accountId, organizationId } = this.extractInformationFromArn( + account.Arn as string, + ); + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Resource', + metadata: { + annotations: { + [ACCOUNTID_ANNOTATION]: accountId, + [ARN_ANNOTATION]: account.Arn || '', + [ORGANIZATION_ANNOTATION]: organizationId, + }, + name: this.normalizeName(account.Name || ''), + namespace: 'default', + }, + spec: { + type: 'cloud-account', + owner: 'unknown', + }, + }; + } } diff --git a/plugins/catalog/src/components/CatalogResultListItem/index.ts b/plugins/catalog-backend-module-aws/src/processors/index.ts similarity index 86% rename from plugins/catalog/src/components/CatalogResultListItem/index.ts rename to plugins/catalog-backend-module-aws/src/processors/index.ts index e8a67d8f89..fa858aa744 100644 --- a/plugins/catalog/src/components/CatalogResultListItem/index.ts +++ b/plugins/catalog-backend-module-aws/src/processors/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { CatalogResultListItem } from './CatalogResultListItem'; +export { AwsOrganizationCloudAccountProcessor } from './AwsOrganizationCloudAccountProcessor'; diff --git a/plugins/catalog-backend-module-aws/src/setupTests.ts b/plugins/catalog-backend-module-aws/src/setupTests.ts new file mode 100644 index 0000000000..d3232290a7 --- /dev/null +++ b/plugins/catalog-backend-module-aws/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export {}; diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 55162c6003..e05e4936a8 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.3.14 + +### Patch Changes + +- ed09ad8093: Updated usage of the `LocationSpec` type from `@backstage/catalog-model`, which is deprecated. +- 25e97e7242: Minor wording update +- df61ca71dd: Implemented required `getProcessorName` method for catalog processor. +- Updated dependencies + - @backstage/plugin-catalog-backend@0.22.0 + - @backstage/catalog-model@0.11.0 + +## 0.3.13 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 244d24ebc4: Import `Location` from the `@backstage/catalog-client` package. +- 27eccab216: Replaces use of deprecated catalog-model constants. +- Updated dependencies + - @backstage/plugin-catalog-backend@0.21.4 + - @backstage/errors@0.2.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.3.12 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index e2dbef74e5..f6b2475e65 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -11,7 +11,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { GroupEntity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; -import { LocationSpec } from '@backstage/catalog-model'; +import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger as Logger_2 } from 'winston'; import { SearchEntry } from 'ldapjs'; import { SearchOptions } from 'ldapjs'; @@ -137,6 +137,8 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { }, ): LdapOrgReaderProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) readLocation( location: LocationSpec, _optional: boolean, diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts index 57781fd711..df167bc520 100644 --- a/plugins/catalog-backend-module-ldap/config.d.ts +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { JsonValue } from '@backstage/config'; +import { JsonValue } from '@backstage/types'; export interface Config { /** diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index a499049265..dc2f56aad6 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", - "description": "A Backstage catalog backend modules that helps integrate towards LDAP", - "version": "0.3.12", + "description": "A Backstage catalog backend module that helps integrate towards LDAP", + "version": "0.3.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin-module" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,26 +24,27 @@ "backstage" ], "scripts": { - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-backend": "^0.21.3", - "@backstage/types": "^0.1.1", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-backend": "^0.22.0", + "@backstage/types": "^0.1.3", "@types/ldapjs": "^2.2.0", "ldapjs": "^2.2.0", "lodash": "^4.17.21", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-ldap/src/index.ts b/plugins/catalog-backend-module-ldap/src/index.ts index a3b3c46815..243044369c 100644 --- a/plugins/catalog-backend-module-ldap/src/index.ts +++ b/plugins/catalog-backend-module-ldap/src/index.ts @@ -15,7 +15,7 @@ */ /** - * A Backstage catalog backend modules that helps integrate towards LDAP + * A Backstage catalog backend module that helps integrate towards LDAP * * @packageDocumentation */ diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index 91fbd87c39..414d043d3d 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -106,6 +106,12 @@ export class LdapClient { reject(new Error(errorString(e))); }); + res.on('page', (_result, cb) => { + if (cb) { + cb(); + } + }); + res.on('end', r => { if (!r) { reject(new Error('Null response')); diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts index be2d748840..6c31d5a27d 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { Logger } from 'winston'; import { @@ -28,6 +27,7 @@ import { import { CatalogProcessor, CatalogProcessorEmit, + LocationSpec, results, } from '@backstage/plugin-catalog-backend'; @@ -69,6 +69,10 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { this.userTransformer = options.userTransformer; } + getProcessorName(): string { + return 'LdapOrgReaderProcessor'; + } + async readLocation( location: LocationSpec, _optional: boolean, diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 847fbae9d4..d2e9facfb5 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.2.17 + +### Patch Changes + +- ed09ad8093: Updated usage of the `LocationSpec` type from `@backstage/catalog-model`, which is deprecated. +- 25e97e7242: Minor wording update +- a097678475: add configuration to use search criteria to select groups +- df61ca71dd: Implemented required `getProcessorName` method for catalog processor. +- Updated dependencies + - @backstage/plugin-catalog-backend@0.22.0 + - @backstage/catalog-model@0.11.0 + +## 0.2.16 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 27eccab216: Replaces use of deprecated catalog-model constants. +- Updated dependencies + - @backstage/plugin-catalog-backend@0.21.4 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + ## 0.2.15 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/api-report.md b/plugins/catalog-backend-module-msgraph/api-report.md index 4f5d247a06..7ad603e6ca 100644 --- a/plugins/catalog-backend-module-msgraph/api-report.md +++ b/plugins/catalog-backend-module-msgraph/api-report.md @@ -9,7 +9,7 @@ import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { GroupEntity } from '@backstage/catalog-model'; -import { LocationSpec } from '@backstage/catalog-model'; +import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger as Logger_2 } from 'winston'; import * as MicrosoftGraph from '@microsoft/microsoft-graph-types'; import * as msal from '@azure/msal-node'; @@ -80,9 +80,16 @@ export class MicrosoftGraphClient { ): Promise; getUserProfile(userId: string): Promise; getUsers(query?: ODataQuery): AsyncIterable; - requestApi(path: string, query?: ODataQuery): Promise; + requestApi( + path: string, + query?: ODataQuery, + headers?: Record, + ): Promise; requestCollection(path: string, query?: ODataQuery): AsyncIterable; - requestRaw(url: string): Promise; + requestRaw( + url: string, + headers?: Record, + ): Promise; } // @public @@ -134,6 +141,8 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { }, ): MicrosoftGraphOrgReaderProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) readLocation( location: LocationSpec, _optional: boolean, @@ -151,7 +160,9 @@ export type MicrosoftGraphProviderConfig = { userFilter?: string; userExpand?: string[]; userGroupMemberFilter?: string; + userGroupMemberSearch?: string; groupFilter?: string; + groupSearch?: string; }; // @public @@ -159,6 +170,7 @@ export function normalizeEntityName(name: string): string; // @public export type ODataQuery = { + search?: string; filter?: string; expand?: string[]; select?: string[]; @@ -181,7 +193,9 @@ export function readMicrosoftGraphOrg( options: { userExpand?: string[]; userFilter?: string; + userGroupMemberSearch?: string; userGroupMemberFilter?: string; + groupSearch?: string; groupFilter?: string; userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; diff --git a/plugins/catalog-backend-module-msgraph/config.d.ts b/plugins/catalog-backend-module-msgraph/config.d.ts index c7eb5ae8e4..d47abf60bb 100644 --- a/plugins/catalog-backend-module-msgraph/config.d.ts +++ b/plugins/catalog-backend-module-msgraph/config.d.ts @@ -73,12 +73,24 @@ export interface Config { * E.g. "securityEnabled eq false and mailEnabled eq true" */ groupFilter?: string; + /** + * The search criteria to apply to extract users by groups memberships. + * + * E.g. "\"displayName:-team\"" would only match groups which contain '-team' + */ + groupSearch?: string; /** * The filter to apply to extract users by groups memberships. * * E.g. "displayName eq 'Backstage Users'" */ userGroupMemberFilter?: string; + /** + * The search criteria to apply to extract groups. + * + * E.g. "\"displayName:-team\"" would only match groups which contain '-team' + */ + userGroupMemberSearch?: string; }>; }; }; diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 7fd0a2d319..5005a16c5b 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "description": "A Backstage catalog backend modules that helps integrate towards Microsoft Graph", - "version": "0.2.15", + "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", + "version": "0.2.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin-module" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,18 +24,19 @@ "backstage" ], "scripts": { - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { "@azure/msal-node": "^1.1.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/plugin-catalog-backend": "^0.21.3", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/plugin-catalog-backend": "^0.22.0", "@microsoft/microsoft-graph-types": "^2.6.0", "@types/node-fetch": "^2.5.12", "lodash": "^4.17.21", @@ -42,9 +46,9 @@ "qs": "^6.9.4" }, "devDependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/cli": "^0.13.2", - "@backstage/test-utils": "^0.2.4", + "@backstage/backend-common": "^0.11.0", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@types/lodash": "^4.14.151", "msw": "^0.35.0" }, diff --git a/plugins/catalog-backend-module-msgraph/src/index.ts b/plugins/catalog-backend-module-msgraph/src/index.ts index 396ccc3e6a..6a7adedc58 100644 --- a/plugins/catalog-backend-module-msgraph/src/index.ts +++ b/plugins/catalog-backend-module-msgraph/src/index.ts @@ -15,7 +15,7 @@ */ /** - * A Backstage catalog backend modules that helps integrate towards Microsoft Graph + * A Backstage catalog backend module that helps integrate towards Microsoft Graph * * @packageDocumentation */ diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts index 827c9fc14d..db749a536e 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/client.ts @@ -27,6 +27,10 @@ import { MicrosoftGraphProviderConfig } from './config'; * @public */ export type ODataQuery = { + /** + * search resources within a collection matching a free-text search expression. + */ + search?: string; /** * filter a collection of resources */ @@ -102,7 +106,15 @@ export class MicrosoftGraphClient { path: string, query?: ODataQuery, ): AsyncIterable { - let response = await this.requestApi(path, query); + const headers: Record = query?.search + ? { + // Eventual consistency is required to use $search. + // If a new user/group is not found, it'll eventually be imported on a subsequent read + ConsistencyLevel: 'eventual', + } + : {}; + + let response = await this.requestApi(path, query, headers); for (;;) { if (response.status !== 200) { @@ -121,7 +133,7 @@ export class MicrosoftGraphClient { return; } - response = await this.requestRaw(result['@odata.nextLink']); + response = await this.requestRaw(result['@odata.nextLink'], headers); } } @@ -131,10 +143,16 @@ export class MicrosoftGraphClient { * @public * @param path - Resource in Microsoft Graph * @param query - OData Query {@link ODataQuery} + * @param headers - optional HTTP headers */ - async requestApi(path: string, query?: ODataQuery): Promise { + async requestApi( + path: string, + query?: ODataQuery, + headers?: Record, + ): Promise { const queryString = qs.stringify( { + $search: query?.search, $filter: query?.filter, $select: query?.select?.join(','), $expand: query?.expand?.join(','), @@ -146,15 +164,22 @@ export class MicrosoftGraphClient { }, ); - return await this.requestRaw(`${this.baseUrl}/${path}${queryString}`); + return await this.requestRaw( + `${this.baseUrl}/${path}${queryString}`, + headers, + ); } /** * Makes a HTTP call to Graph API with token * * @param url - HTTP Endpoint of Graph API + * @param headers - optional HTTP headers */ - async requestRaw(url: string): Promise { + async requestRaw( + url: string, + headers?: Record, + ): Promise { // Make sure that we always have a valid access token (might be cached) const token = await this.pca.acquireTokenByClientCredential({ scopes: ['https://graph.microsoft.com/.default'], @@ -166,6 +191,7 @@ export class MicrosoftGraphClient { return await fetch(url, { headers: { + ...headers, Authorization: `Bearer ${token.accessToken}`, }, }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index c401bc2553..6809144f80 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -64,12 +64,24 @@ export type MicrosoftGraphProviderConfig = { * E.g. "displayName eq 'Backstage Users'" */ userGroupMemberFilter?: string; + /** + * The search criteria to apply to extract users by groups memberships. + * + * E.g. "\"displayName:-team\"" would only match groups which contain '-team' + */ + userGroupMemberSearch?: string; /** * The filter to apply to extract groups. * * E.g. "securityEnabled eq false and mailEnabled eq true" */ groupFilter?: string; + /** + * The search criteria to apply to extract groups. + * + * E.g. "\"displayName:-team\"" would only match groups which contain '-team' + */ + groupSearch?: string; }; /** @@ -98,13 +110,22 @@ export function readMicrosoftGraphConfig( const userGroupMemberFilter = providerConfig.getOptionalString( 'userGroupMemberFilter', ); + const userGroupMemberSearch = providerConfig.getOptionalString( + 'userGroupMemberSearch', + ); const groupFilter = providerConfig.getOptionalString('groupFilter'); + const groupSearch = providerConfig.getOptionalString('groupSearch'); if (userFilter && userGroupMemberFilter) { throw new Error( `userFilter and userGroupMemberFilter are mutually exclusive, only one can be specified.`, ); } + if (userFilter && userGroupMemberSearch) { + throw new Error( + `userGroupMemberSearch cannot be specified when userFilter is defined.`, + ); + } providers.push({ target, @@ -114,7 +135,9 @@ export function readMicrosoftGraphConfig( clientSecret, userFilter, userGroupMemberFilter, + userGroupMemberSearch, groupFilter, + groupSearch, }); } diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts index 58e60ab76a..f862fd5ace 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/read.ts @@ -137,6 +137,7 @@ export async function readMicrosoftGraphUsers( export async function readMicrosoftGraphUsersInGroups( client: MicrosoftGraphClient, options: { + userGroupMemberSearch?: string; userGroupMemberFilter?: string; transformer?: UserTransformer; logger: Logger; @@ -155,6 +156,7 @@ export async function readMicrosoftGraphUsersInGroups( const groupMemberUsers: Set = new Set(); for await (const group of client.getGroups({ + search: options?.userGroupMemberSearch, filter: options?.userGroupMemberFilter, })) { // Process all groups in parallel, otherwise it can take quite some time @@ -324,6 +326,7 @@ export async function readMicrosoftGraphGroups( client: MicrosoftGraphClient, tenantId: string, options?: { + groupSearch?: string; groupFilter?: string; groupTransformer?: GroupTransformer; organizationTransformer?: OrganizationTransformer; @@ -351,6 +354,7 @@ export async function readMicrosoftGraphGroups( const promises: Promise[] = []; for await (const group of client.getGroups({ + search: options?.groupSearch, filter: options?.groupFilter, })) { // Process all groups in parallel, otherwise it can take quite some time @@ -504,7 +508,9 @@ export async function readMicrosoftGraphOrg( options: { userExpand?: string[]; userFilter?: string; + userGroupMemberSearch?: string; userGroupMemberFilter?: string; + groupSearch?: string; groupFilter?: string; userTransformer?: UserTransformer; groupTransformer?: GroupTransformer; @@ -519,6 +525,7 @@ export async function readMicrosoftGraphOrg( client, { userGroupMemberFilter: options.userGroupMemberFilter, + userGroupMemberSearch: options.userGroupMemberSearch, transformer: options.userTransformer, logger: options.logger, }, @@ -535,6 +542,7 @@ export async function readMicrosoftGraphOrg( } const { groups, rootGroup, groupMember, groupMemberOf } = await readMicrosoftGraphGroups(client, tenantId, { + groupSearch: options?.groupSearch, groupFilter: options?.groupFilter, groupTransformer: options?.groupTransformer, organizationTransformer: options?.organizationTransformer, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index 92836893f0..7d27e87d73 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -123,7 +123,9 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { { userFilter: provider.userFilter, userGroupMemberFilter: provider.userGroupMemberFilter, + userGroupMemberSearch: provider.userGroupMemberSearch, groupFilter: provider.groupFilter, + groupSearch: provider.groupSearch, groupTransformer: this.options.groupTransformer, userTransformer: this.options.userTransformer, organizationTransformer: this.options.organizationTransformer, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts index e0e63d86fc..4db3a9b1a0 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { CatalogProcessor, CatalogProcessorEmit, + LocationSpec, results, } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; @@ -73,6 +73,9 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { this.groupTransformer = options.groupTransformer; this.organizationTransformer = options.organizationTransformer; } + getProcessorName(): string { + return 'MicrosoftGraphOrgReaderProcessor'; + } async readLocation( location: LocationSpec, @@ -105,7 +108,9 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { userExpand: provider.userExpand, userFilter: provider.userFilter, userGroupMemberFilter: provider.userGroupMemberFilter, + userGroupMemberSearch: provider.userGroupMemberSearch, groupFilter: provider.groupFilter, + groupSearch: provider.groupSearch, userTransformer: this.userTransformer, groupTransformer: this.groupTransformer, organizationTransformer: this.organizationTransformer, diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index a371712c80..d80acc6d23 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,174 @@ # @backstage/plugin-catalog-backend +## 0.22.0 + +### Minor Changes + +- 209fd128e6: The `CodeOwnersProcessor` no longer supports the deprecated SCM-specific location types like `'github/api'`. This is a breaking change but it is unlikely to have an impact, as these location types haven't been supported by the rest of the catalog for a long time. +- 9876e7f172: **BREAKING**: Removed unused `durationText` utility. +- 25e97e7242: **BREAKING**: Removed `AwsOrganizationCloudAccountProcessor` from the default + set of builtin processors, and instead moved it into its own module + `@backstage/plugin-catalog-backend-module-aws`. + + If you were using this processor, through making use of the location type + `aws-cloud-accounts` and/or using the configuration key + `catalog.processors.awsOrganization`, you will from now on have to add the + processor manually to your catalog. + + First, add the `@backstage/plugin-catalog-backend-module-aws` dependency to your + `packages/backend` package. + + Then, in `packages/backend/src/plugins/catalog.ts`: + + ```diff + +import { AwsOrganizationCloudAccountProcessor } from '@backstage/plugin-catalog-backend-module-aws'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); + + builder.addProcessor( + + AwsOrganizationCloudAccountProcessor.fromConfig( + + env.config, + + { logger: env.logger } + + ) + + ); + // ... + ``` + +- e9cf0dd03e: Made the `GitLabDiscoveryProcessor.updateLastActivity` method private, as it was accidentally exposed. It has also been fixed to properly operate in its own cache namespace to avoid collisions with other processors. +- df61ca71dd: Updated all processors to implement `getProcessorName`. + + **BREAKING**: The `CatalogProcessor` interface now require that the `CatalogProcessor` class implements `getProcessorName()`. + The processor name has previously defaulted processor class name. It's therefore _recommended_ to keep your return the same name as the class name if you did not implement this method previously. + + For example: + + ```ts + class CustomProcessor implements CatalogProcessor { + getProcessorName() { + // Use the same name as the class name if this method was not previously implemented. + return 'CustomProcessor'; + } + } + ``` + +### Patch Changes + +- 919cf2f836: The catalog API now returns entity relations that have three fields: The old + `type` and `target` fields, as well as a new `targetRef` field. The last one is + the stringified form of the second one. + + **DEPRECATION**: The `target` field is hereby deprecated, both as seen from the + catalog API as well as from the `@backstage/catalog-model` package. Both + `target` and `targetRef` will be produced for some time, but eventually, + `target` will be removed entirely. Please update your readers to stop consuming + the `relations.[].target` field from the catalog API as soon as possible. + +- 957cb4cb20: Deprecated the `runPeriodically` function which is no longer in use. +- 01e124ea60: Added an `/entity-facets` endpoint, which lets you query the distribution of + possible values for fields of entities. + + This can be useful for example when populating a dropdown in the user interface, + such as listing all tag values that are actually being used right now in your + catalog instance, along with putting the most common ones at the top. + +- 082c32f948: Deprecated the second parameter of `results.location()` that determines whether an emitted location is optional. In cases where this is currently being set to `false`, the parameter can simply be dropped, as that is the default. Usage where this was being set to `true` should be migrated to set the `presence` option of the emitted location to `optional`. For example: + + ```ts + results.location( + { + type: 'url', + target: 'http://example.com/foo', + }, + true, + ); + + // migrated to + + results.location({ + type: 'url', + target: 'http://example.com/foo', + presence: 'optional', + }); + ``` + +- ed09ad8093: Added `LocationSpec`, which was moved over from `@backstage/catalog-model`. + + Added `LocationInput`, which replaced `LocationSpec` where it was used in the `LocationService` and `LocationStore` interfaces. The `LocationInput` type deprecates the `presence` field, which was not being used in those contexts. + +- 6d994fd9da: Cleanup catalog-backend API report. +- 7010349c9a: Added `EntityRelationSpec`, which was moved over from `@backstage/catalog-model`. +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- 420f8d710f: Removed the `processors.githubOrg` config section which is unused and has been replaced by the integrations config. +- b1296f1f57: Deprecated `StaticLocationProcessor` which is unused and replaced by `ConfigLocationEntityProvider`. +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-scaffolder-common@0.2.2 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/plugin-permission-node@0.5.2 + - @backstage/integration@0.7.5 + +## 0.21.5 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/integration@0.7.4 + - @backstage/search-common@0.2.4 + - @backstage/types@0.1.3 + - @backstage/plugin-catalog-common@0.1.4 + - @backstage/plugin-permission-common@0.5.1 + - @backstage/plugin-permission-node@0.5.1 + - @backstage/plugin-scaffolder-common@0.2.1 + +## 0.21.4 + +### Patch Changes + +- 379da9fb1d: The following processors now properly accept an `ScmIntegrationRegistry` (an + interface) instead of an `ScmIntegrations` (which is a concrete class). + + - `AzureDevOpsDiscoveryProcessor` + - `CodeOwnersProcessor` + - `GitLabDiscoveryProcessor` + - `GithubDiscoveryProcessor` + - `GithubMultiOrgReaderProcessor` + - `GithubOrgReaderProcessor` + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 538ca90790: Use updated type names from `@backstage/catalog-client` +- ca1d6c1788: Support "dependencyOf" relation in Resource entities +- 244d24ebc4: Import `Location` from the `@backstage/catalog-client` package. +- e483dd6c72: Update internal `Location` validation. +- 216725b434: Updated to use new names for `parseLocationRef` and `stringifyLocationRef` +- e72d371296: Use `TemplateEntityV1beta2` from `@backstage/plugin-scaffolder-common` instead + of `@backstage/catalog-model`. +- 27eccab216: Replaces use of deprecated catalog-model constants. +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- b590e9b58d: Optimized entity provider mutations with large numbers of new additions, such as big initial startup commits +- Updated dependencies + - @backstage/plugin-scaffolder-common@0.2.0 + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/search-common@0.2.3 + - @backstage/types@0.1.2 + - @backstage/plugin-catalog-common@0.1.3 + - @backstage/plugin-permission-node@0.5.0 + ## 0.21.3 ### Patch Changes @@ -725,16 +894,16 @@ ### Minor Changes -- 8bfc0571c: Add a default catalog value for BitBucketDiscoveryProcessor. This allows to have a target like so: https://bitbucket.mycompany.com/projects/backstage/repos/service-* - which will be expanded to https://bitbucket.mycompany.com/projects/backstage/repos/service-a/catalog-info.yaml given that repository 'service-a' exists. +- 8bfc0571c: Add a default catalog value for BitBucketDiscoveryProcessor. This allows to have a target like so: `https://bitbucket.mycompany.com/projects/backstage/repos/service-*` + which will be expanded to `https://bitbucket.mycompany.com/projects/backstage/repos/service-a/catalog-info.yaml` given that repository 'service-a' exists. ## Migration If you are using a custom [Bitbucket parser](https://backstage.io/docs/integrations/bitbucket/discovery#custom-repository-processing) and your `bitbucket-discovery` target (e.g. in your app-config.yaml) omits the catalog path in any of the following ways: - - https://bitbucket.mycompany.com/projects/backstage/repos/service-* - - https://bitbucket.mycompany.com/projects/backstage/repos/* - - https://bitbucket.mycompany.com/projects/backstage/repos/*/ + - `https://bitbucket.mycompany.com/projects/backstage/repos/service-*` + - `https://bitbucket.mycompany.com/projects/backstage/repos/*` + - `https://bitbucket.mycompany.com/projects/backstage/repos/*/` then you will be affected by this change. The 'target' input to your parser before this commit would be '/', and after this commit it will be '/catalog-info.yaml', and as such needs to be handled to maintain the same functionality. @@ -1052,7 +1221,7 @@ - 0fd4ea443: Updates the `GithubCredentialsProvider` to return the token type, it can either be `token` or `app` depending on the authentication method. - Update the `GithubOrgReaderProcessor` NOT to query for email addresses if GitHub Apps is used for authentication, this is due to inconsistencies in the GitHub API when using server to server communications and installation tokens. https://github.community/t/api-v4-unable-to-retrieve-email-resource-not-accessible-by-integration/13831/4 for more info. + Update the `GithubOrgReaderProcessor` NOT to query for email addresses if GitHub Apps is used for authentication, this is due to inconsistencies in the GitHub API when using server to server communications and installation tokens. See [this community discussion](https://github.community/t/api-v4-unable-to-retrieve-email-resource-not-accessible-by-integration/13831/4) for more info. **Removes** deprecated GithubOrgReaderProcessor provider configuration(`catalog.processors.githubOrg`). If you're using the deprecated config section make sure to migrate to [integrations](https://backstage.io/docs/integrations/github/locations) instead. diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index d97df9967c..9b4db418b3 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -5,7 +5,6 @@ ```ts /// -import { Account } from 'aws-sdk/clients/organizations'; import { BitbucketIntegration } from '@backstage/integration'; import { CatalogApi } from '@backstage/catalog-client'; import { ConditionalPolicyDecision } from '@backstage/plugin-permission-node'; @@ -13,8 +12,8 @@ import { Conditions } from '@backstage/plugin-permission-node'; import { Config } from '@backstage/config'; import { DocumentCollator } from '@backstage/search-common'; import { Entity } from '@backstage/catalog-model'; +import { EntityName } from '@backstage/catalog-model'; import { EntityPolicy } from '@backstage/catalog-model'; -import { EntityRelationSpec } from '@backstage/catalog-model'; import express from 'express'; import { GetEntitiesRequest } from '@backstage/catalog-client'; import { GithubCredentialsProvider } from '@backstage/integration'; @@ -23,9 +22,7 @@ import { IndexableDocument } from '@backstage/search-common'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Location as Location_2 } from '@backstage/catalog-client'; -import { LocationSpec } from '@backstage/catalog-model'; import { Logger as Logger_2 } from 'winston'; -import { Organizations } from 'aws-sdk'; import { Permission } from '@backstage/plugin-permission-common'; import { PermissionAuthorizer } from '@backstage/plugin-permission-common'; import { PermissionCondition } from '@backstage/plugin-permission-common'; @@ -33,15 +30,12 @@ import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { ResourceEntityV1alpha1 } from '@backstage/catalog-model'; import { Router } from 'express'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TokenManager } from '@backstage/backend-common'; import { UrlReader } from '@backstage/backend-common'; import { Validators } from '@backstage/catalog-model'; -// Warning: (ae-missing-release-tag) "AnalyzeLocationEntityField" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type AnalyzeLocationEntityField = { field: string; @@ -53,44 +47,35 @@ export type AnalyzeLocationEntityField = { description: string; }; -// Warning: (ae-missing-release-tag) "AnalyzeLocationExistingEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type AnalyzeLocationExistingEntity = { location: LocationSpec; isRegistered: boolean; entity: Entity; }; -// Warning: (ae-missing-release-tag) "AnalyzeLocationGenerateEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type AnalyzeLocationGenerateEntity = { entity: RecursivePartial; fields: AnalyzeLocationEntityField[]; }; -// Warning: (ae-missing-release-tag) "AnalyzeLocationRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type AnalyzeLocationRequest = { location: LocationSpec; }; -// Warning: (ae-missing-release-tag) "AnalyzeLocationResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type AnalyzeLocationResponse = { existingEntityFiles: AnalyzeLocationExistingEntity[]; generateEntities: AnalyzeLocationGenerateEntity[]; }; -// Warning: (ae-missing-release-tag) "AnnotateLocationEntityProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class AnnotateLocationEntityProcessor implements CatalogProcessor { - // Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts - constructor(options: Options); + constructor(options: { integrations: ScmIntegrationRegistry }); + // (undocumented) + getProcessorName(): string; // (undocumented) preProcessEntity( entity: Entity, @@ -100,70 +85,23 @@ export class AnnotateLocationEntityProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "AnnotateScmSlugEntityProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class AnnotateScmSlugEntityProcessor implements CatalogProcessor { constructor(opts: { scmIntegrationRegistry: ScmIntegrationRegistry }); // (undocumented) static fromConfig(config: Config): AnnotateScmSlugEntityProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) preProcessEntity(entity: Entity, location: LocationSpec): Promise; } -// Warning: (ae-missing-release-tag) "AwsOrganizationCloudAccountProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export class AwsOrganizationCloudAccountProcessor implements CatalogProcessor { - constructor(options: { - provider: AwsOrganizationProviderConfig; - logger: Logger_2; - }); - // (undocumented) - extractInformationFromArn(arn: string): { - accountId: string; - organizationId: string; - }; - // (undocumented) - static fromConfig( - config: Config, - options: { - logger: Logger_2; - }, - ): AwsOrganizationCloudAccountProcessor; - // (undocumented) - getAwsAccounts(): Promise; - // (undocumented) - logger: Logger_2; - // (undocumented) - mapAccountToComponent(account: Account): ResourceEntityV1alpha1; - // (undocumented) - normalizeName(name: string): string; - // (undocumented) - organizations: Organizations; - // (undocumented) - provider: AwsOrganizationProviderConfig; - // (undocumented) - readLocation( - location: LocationSpec, - _optional: boolean, - emit: CatalogProcessorEmit, - ): Promise; -} - -// Warning: (ae-missing-release-tag) "AwsOrganizationProviderConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export type AwsOrganizationProviderConfig = { - roleArn?: string; -}; - -// Warning: (ae-missing-release-tag) "AwsS3DiscoveryProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class AwsS3DiscoveryProcessor implements CatalogProcessor { constructor(reader: UrlReader); // (undocumented) + getProcessorName(): string; + // (undocumented) readLocation( location: LocationSpec, optional: boolean, @@ -172,8 +110,6 @@ export class AwsS3DiscoveryProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "AzureDevOpsDiscoveryProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor { constructor(options: { @@ -188,6 +124,8 @@ export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor { }, ): AzureDevOpsDiscoveryProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) readLocation( location: LocationSpec, _optional: boolean, @@ -195,8 +133,6 @@ export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "BitbucketDiscoveryProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class BitbucketDiscoveryProcessor implements CatalogProcessor { constructor(options: { @@ -213,6 +149,8 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { }, ): BitbucketDiscoveryProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) readLocation( location: LocationSpec, _optional: boolean, @@ -220,8 +158,6 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "BitbucketRepositoryParser" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type BitbucketRepositoryParser = (options: { integration: BitbucketIntegration; @@ -229,10 +165,10 @@ export type BitbucketRepositoryParser = (options: { logger: Logger_2; }) => AsyncIterable; -// Warning: (ae-missing-release-tag) "BuiltinKindsEntityProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class BuiltinKindsEntityProcessor implements CatalogProcessor { + // (undocumented) + getProcessorName(): string; // (undocumented) postProcessEntity( entity: Entity, @@ -303,8 +239,6 @@ export const catalogConditions: Conditions<{ >; }>; -// Warning: (ae-missing-release-tag) "CatalogEntityDocument" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export interface CatalogEntityDocument extends IndexableDocument { // (undocumented) @@ -319,8 +253,6 @@ export interface CatalogEntityDocument extends IndexableDocument { owner: string; } -// Warning: (ae-missing-release-tag) "CatalogEnvironment" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type CatalogEnvironment = { logger: Logger_2; @@ -330,8 +262,6 @@ export type CatalogEnvironment = { permissions: PermissionAuthorizer; }; -// Warning: (ae-missing-release-tag) "CatalogProcessingEngine" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export interface CatalogProcessingEngine { // (undocumented) @@ -340,19 +270,15 @@ export interface CatalogProcessingEngine { stop(): Promise; } -// Warning: (ae-missing-release-tag) "CatalogProcessingOrchestrator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface CatalogProcessingOrchestrator { // (undocumented) process(request: EntityProcessingRequest): Promise; } -// Warning: (ae-missing-release-tag) "CatalogProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type CatalogProcessor = { - getProcessorName?(): string; + getProcessorName(): string; readLocation?( location: LocationSpec, optional: boolean, @@ -387,13 +313,9 @@ export interface CatalogProcessorCache { set(key: string, value: ItemType): Promise; } -// Warning: (ae-missing-release-tag) "CatalogProcessorEmit" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type CatalogProcessorEmit = (generated: CatalogProcessorResult) => void; -// Warning: (ae-missing-release-tag) "CatalogProcessorEntityResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type CatalogProcessorEntityResult = { type: 'entity'; @@ -401,8 +323,6 @@ export type CatalogProcessorEntityResult = { location: LocationSpec; }; -// Warning: (ae-missing-release-tag) "CatalogProcessorErrorResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type CatalogProcessorErrorResult = { type: 'error'; @@ -410,25 +330,19 @@ export type CatalogProcessorErrorResult = { location: LocationSpec; }; -// Warning: (ae-missing-release-tag) "CatalogProcessorLocationResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type CatalogProcessorLocationResult = { type: 'location'; location: LocationSpec; - optional: boolean; + optional?: boolean; }; -// Warning: (ae-missing-release-tag) "CatalogProcessorParser" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type CatalogProcessorParser = (options: { data: Buffer; location: LocationSpec; }) => AsyncIterable; -// Warning: (ae-missing-release-tag) "CatalogProcessorRelationResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type CatalogProcessorRelationResult = { type: 'relation'; @@ -436,8 +350,6 @@ export type CatalogProcessorRelationResult = { entityRef?: string; }; -// Warning: (ae-missing-release-tag) "CatalogProcessorResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type CatalogProcessorResult = | CatalogProcessorLocationResult @@ -461,8 +373,6 @@ export type CatalogRulesEnforcer = { isAllowed(entity: Entity, location: LocationSpec): boolean; }; -// Warning: (ae-missing-release-tag) "CodeOwnersProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class CodeOwnersProcessor implements CatalogProcessor { constructor(options: { @@ -479,6 +389,8 @@ export class CodeOwnersProcessor implements CatalogProcessor { }, ): CodeOwnersProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) preProcessEntity(entity: Entity, location: LocationSpec): Promise; } @@ -492,8 +404,6 @@ export const createCatalogPolicyDecision: ( conditions: PermissionCriteria>, ) => ConditionalPolicyDecision; -// Warning: (ae-missing-release-tag) "createRandomRefreshInterval" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function createRandomRefreshInterval(options: { minSeconds: number; @@ -503,8 +413,6 @@ export function createRandomRefreshInterval(options: { // @public export function createRouter(options: RouterOptions): Promise; -// Warning: (ae-missing-release-tag) "DefaultCatalogCollator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class DefaultCatalogCollator implements DocumentCollator { constructor(options: { @@ -546,8 +454,6 @@ export class DefaultCatalogCollator implements DocumentCollator { readonly visibilityPermission: Permission; } -// Warning: (ae-missing-release-tag) "DefaultCatalogProcessingOrchestrator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class DefaultCatalogProcessingOrchestrator implements CatalogProcessingOrchestrator @@ -572,19 +478,12 @@ export class DefaultCatalogRulesEnforcer implements CatalogRulesEnforcer { isAllowed(entity: Entity, location: LocationSpec): boolean; } -// Warning: (ae-missing-release-tag) "DeferredEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type DeferredEntity = { entity: Entity; locationKey?: string; }; -// Warning: (ae-missing-release-tag) "durationText" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export function durationText(startTimestamp: [number, number]): string; - // @public (undocumented) export type EntitiesCatalog = { entities(request?: EntitiesRequest): Promise; @@ -600,10 +499,9 @@ export type EntitiesCatalog = { authorizationToken?: string; }, ): Promise; + facets(request: EntityFacetsRequest): Promise; }; -// Warning: (ae-missing-release-tag) "EntitiesRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type EntitiesRequest = { filter?: EntityFilter; @@ -612,24 +510,18 @@ export type EntitiesRequest = { authorizationToken?: string; }; -// Warning: (ae-missing-release-tag) "EntitiesResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type EntitiesResponse = { entities: Entity[]; pageInfo: PageInfo; }; -// Warning: (ae-missing-release-tag) "EntitiesSearchFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type EntitiesSearchFilter = { key: string; values?: string[]; }; -// Warning: (ae-missing-release-tag) "entity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) function entity( atLocation: LocationSpec, @@ -645,8 +537,24 @@ export type EntityAncestryResponse = { }>; }; -// Warning: (ae-missing-release-tag) "EntityFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public +export interface EntityFacetsRequest { + authorizationToken?: string; + facets: string[]; + filter?: EntityFilter; +} + +// @public +export interface EntityFacetsResponse { + facets: Record< + string, + Array<{ + value: string; + count: number; + }> + >; +} + // @public export type EntityFilter = | { @@ -660,8 +568,6 @@ export type EntityFilter = } | EntitiesSearchFilter; -// Warning: (ae-missing-release-tag) "EntityPagination" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type EntityPagination = { limit?: number; @@ -669,17 +575,13 @@ export type EntityPagination = { after?: string; }; -// Warning: (ae-missing-release-tag) "EntityProcessingRequest" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type EntityProcessingRequest = { entity: Entity; state?: JsonObject; }; -// Warning: (ae-missing-release-tag) "EntityProcessingResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type EntityProcessingResult = | { ok: true; @@ -694,27 +596,18 @@ export type EntityProcessingResult = errors: Error[]; }; -// Warning: (ae-missing-release-tag) "EntityProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface EntityProvider { - // (undocumented) connect(connection: EntityProviderConnection): Promise; - // (undocumented) getProviderName(): string; } -// Warning: (ae-missing-release-tag) "EntityProviderConnection" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export interface EntityProviderConnection { - // (undocumented) applyMutation(mutation: EntityProviderMutation): Promise; } -// Warning: (ae-missing-release-tag) "EntityProviderMutation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type EntityProviderMutation = | { type: 'full'; @@ -726,10 +619,17 @@ export type EntityProviderMutation = removed: DeferredEntity[]; }; -// Warning: (ae-missing-release-tag) "FileReaderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public +export type EntityRelationSpec = { + source: EntityName; + type: string; + target: EntityName; +}; + // @public (undocumented) export class FileReaderProcessor implements CatalogProcessor { + // (undocumented) + getProcessorName(): string; // (undocumented) readLocation( location: LocationSpec, @@ -739,16 +639,12 @@ export class FileReaderProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "generalError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) function generalError( atLocation: LocationSpec, message: string, ): CatalogProcessorResult; -// Warning: (ae-missing-release-tag) "GithubDiscoveryProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class GithubDiscoveryProcessor implements CatalogProcessor { constructor(options: { @@ -765,6 +661,8 @@ export class GithubDiscoveryProcessor implements CatalogProcessor { }, ): GithubDiscoveryProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) readLocation( location: LocationSpec, _optional: boolean, @@ -772,6 +670,13 @@ export class GithubDiscoveryProcessor implements CatalogProcessor { ): Promise; } +// @public +export type GithubMultiOrgConfig = Array<{ + name: string; + groupNamespace: string; + userNamespace: string | undefined; +}>; + // @alpha export class GithubMultiOrgReaderProcessor implements CatalogProcessor { constructor(options: { @@ -789,6 +694,8 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { }, ): GithubMultiOrgReaderProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) readLocation( location: LocationSpec, _optional: boolean, @@ -796,8 +703,6 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "GitHubOrgEntityProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class GitHubOrgEntityProvider implements EntityProvider { constructor(options: { @@ -825,8 +730,6 @@ export class GitHubOrgEntityProvider implements EntityProvider { read(): Promise; } -// Warning: (ae-missing-release-tag) "GithubOrgReaderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class GithubOrgReaderProcessor implements CatalogProcessor { constructor(options: { @@ -843,6 +746,8 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { }, ): GithubOrgReaderProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) readLocation( location: LocationSpec, _optional: boolean, @@ -850,8 +755,6 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "GitLabDiscoveryProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class GitLabDiscoveryProcessor implements CatalogProcessor { // (undocumented) @@ -862,33 +765,27 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { }, ): GitLabDiscoveryProcessor; // (undocumented) + getProcessorName(): string; + // (undocumented) readLocation( location: LocationSpec, _optional: boolean, emit: CatalogProcessorEmit, ): Promise; - // (undocumented) - updateLastActivity(): Promise; } -// Warning: (ae-missing-release-tag) "inputError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) function inputError( atLocation: LocationSpec, message: string, ): CatalogProcessorResult; -// Warning: (ae-missing-release-tag) "location" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) function location_2( newLocation: LocationSpec, - optional: boolean, + optional?: boolean, ): CatalogProcessorResult; -// Warning: (ae-missing-release-tag) "LocationAnalyzer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type LocationAnalyzer = { analyzeLocation( @@ -896,12 +793,12 @@ export type LocationAnalyzer = { ): Promise; }; -// Warning: (ae-missing-release-tag) "LocationEntityProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class LocationEntityProcessor implements CatalogProcessor { constructor(options: LocationEntityProcessorOptions); // (undocumented) + getProcessorName(): string; + // (undocumented) postProcessEntity( entity: Entity, location: LocationSpec, @@ -909,20 +806,26 @@ export class LocationEntityProcessor implements CatalogProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "LocationEntityProcessorOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type LocationEntityProcessorOptions = { integrations: ScmIntegrationRegistry; }; -// Warning: (ae-missing-release-tag) "LocationService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export interface LocationInput { + // @deprecated (undocumented) + presence?: 'optional' | 'required'; + // (undocumented) + target: string; + // (undocumented) + type: string; +} + +// @public export interface LocationService { // (undocumented) createLocation( - spec: LocationSpec, + location: LocationInput, dryRun: boolean, options?: { authorizationToken?: string; @@ -952,12 +855,17 @@ export interface LocationService { }): Promise; } -// Warning: (ae-missing-release-tag) "LocationStore" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type LocationSpec = { + type: string; + target: string; + presence?: 'optional' | 'required'; +}; + +// @public export interface LocationStore { // (undocumented) - createLocation(spec: LocationSpec): Promise; + createLocation(location: LocationInput): Promise; // (undocumented) deleteLocation(id: string): Promise; // (undocumented) @@ -966,16 +874,12 @@ export interface LocationStore { listLocations(): Promise; } -// Warning: (ae-missing-release-tag) "notFoundError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) function notFoundError( atLocation: LocationSpec, message: string, ): CatalogProcessorResult; -// Warning: (ae-missing-release-tag) "PageInfo" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type PageInfo = | { @@ -986,8 +890,6 @@ export type PageInfo = endCursor: string; }; -// Warning: (ae-missing-release-tag) "parseEntityYaml" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function parseEntityYaml( data: Buffer, @@ -1020,17 +922,15 @@ export const permissionRules: { >; }; -// Warning: (ae-missing-release-tag) "PlaceholderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export class PlaceholderProcessor implements CatalogProcessor { constructor(options: PlaceholderProcessorOptions); // (undocumented) + getProcessorName(): string; + // (undocumented) preProcessEntity(entity: Entity, location: LocationSpec): Promise; } -// Warning: (ae-missing-release-tag) "PlaceholderProcessorOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type PlaceholderProcessorOptions = { resolvers: Record; @@ -1038,15 +938,11 @@ export type PlaceholderProcessorOptions = { integrations: ScmIntegrationRegistry; }; -// Warning: (ae-missing-release-tag) "PlaceholderResolver" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type PlaceholderResolver = ( params: PlaceholderResolverParams, ) => Promise; -// Warning: (ae-missing-release-tag) "PlaceholderResolverParams" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type PlaceholderResolverParams = { key: string; @@ -1056,21 +952,15 @@ export type PlaceholderResolverParams = { resolveUrl: PlaceholderResolverResolveUrl; }; -// Warning: (ae-missing-release-tag) "PlaceholderResolverRead" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type PlaceholderResolverRead = (url: string) => Promise; -// Warning: (ae-missing-release-tag) "PlaceholderResolverResolveUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type PlaceholderResolverResolveUrl = ( url: string, base: string, ) => string; -// Warning: (ae-missing-release-tag) "RecursivePartial" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type RecursivePartial = { [P in keyof T]?: T[P] extends (infer U)[] @@ -1080,8 +970,6 @@ export type RecursivePartial = { : T[P]; }; -// Warning: (ae-missing-release-tag) "RefreshIntervalFunction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type RefreshIntervalFunction = () => number; @@ -1096,8 +984,6 @@ export interface RefreshService { refresh(options: RefreshOptions): Promise; } -// Warning: (ae-missing-release-tag) "relation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) function relation(spec: EntityRelationSpec): CatalogProcessorResult; @@ -1131,14 +1017,10 @@ export interface RouterOptions { refreshService?: RefreshService; } -// Warning: (ae-missing-release-tag) "runPeriodically" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public +// @public @deprecated export function runPeriodically(fn: () => any, delayMs: number): () => void; -// Warning: (ae-missing-release-tag) "StaticLocationProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public @deprecated (undocumented) export class StaticLocationProcessor implements StaticLocationProcessor { constructor(staticLocations: LocationSpec[]); // (undocumented) @@ -1151,12 +1033,9 @@ export class StaticLocationProcessor implements StaticLocationProcessor { ): Promise; } -// Warning: (ae-missing-release-tag) "UrlReaderProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor { - // Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts - constructor(options: Options_2); + constructor(options: { reader: UrlReader; logger: Logger_2 }); // (undocumented) getProcessorName(): string; // (undocumented) @@ -1168,8 +1047,4 @@ export class UrlReaderProcessor implements CatalogProcessor { cache: CatalogProcessorCache, ): Promise; } - -// Warnings were encountered during analysis: -// -// src/ingestion/processors/GithubMultiOrgReaderProcessor.d.ts:25:9 - (ae-forgotten-export) The symbol "GithubMultiOrgConfig" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 5701c9df48..957f18c15f 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -14,8 +14,6 @@ * limitations under the License. */ -import { JsonValue } from '@backstage/config'; - export interface Config { /** * Configuration options for the catalog plugin. @@ -112,39 +110,6 @@ export interface Config { * List of processor-specific options and attributes */ processors?: { - /** - * GithubOrgReaderProcessor configuration - * - * @deprecated Configure a GitHub integration instead. - */ - githubOrg?: { - /** - * The configuration parameters for each single GitHub org provider. - */ - providers: Array<{ - /** - * The prefix of the target that this matches on, e.g. - * "https://github.com", with no trailing slash. - */ - target: string; - /** - * The base URL of the API of this provider, e.g. - * "https://api.github.com", with no trailing slash. - * - * May be omitted specifically for GitHub; then it will be deduced. - */ - apiBaseUrl?: string; - /** - * The authorization token to use for requests to this provider. - * - * If no token is specified, anonymous access is used. - * - * @visibility secret - */ - token?: string; - }>; - }; - /** * GithubMultiOrgReaderProcessor configuration */ @@ -172,19 +137,6 @@ export interface Config { userNamespace?: string; }>; }; - - /** - * AwsOrganizationCloudAccountProcessor configuration - */ - awsOrganization?: { - provider: { - /** - * The role to be assumed by this processor - * - */ - roleArn?: string; - }; - }; }; }; } diff --git a/plugins/airbrake/src/EntityAirbrakeContent.test.tsx b/plugins/catalog-backend/migrations/20220222164811_reprocess_for_relation_refs.js similarity index 61% rename from plugins/airbrake/src/EntityAirbrakeContent.test.tsx rename to plugins/catalog-backend/migrations/20220222164811_reprocess_for_relation_refs.js index f0afef425f..f4be2172a0 100644 --- a/plugins/airbrake/src/EntityAirbrakeContent.test.tsx +++ b/plugins/catalog-backend/migrations/20220222164811_reprocess_for_relation_refs.js @@ -13,13 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { EntityAirbrakeContent } from './plugin'; -import { renderInTestApp } from '@backstage/test-utils'; -describe('The Airbrake entity', () => { - it('should render the content properly', async () => { - const rendered = await renderInTestApp(); - expect(rendered.getByText('ChunkLoadError')).toBeInTheDocument(); +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + // Make sure to reprocess everything, to make sure that relations have a targetRef produced + await knex('refresh_state').update({ + result_hash: '', + next_update_at: knex.fn.now(), }); -}); + await knex('final_entities').update({ hash: '' }); +}; + +exports.down = async function down() {}; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index a4a40af206..55c92c92be 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "0.21.3", + "version": "0.22.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,26 +24,27 @@ "backstage" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/plugin-catalog-common": "^0.1.2", - "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/plugin-permission-node": "^0.4.3", - "@backstage/search-common": "^0.2.2", - "@backstage/types": "^0.1.1", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/plugin-catalog-common": "^0.1.4", + "@backstage/plugin-permission-common": "^0.5.1", + "@backstage/plugin-permission-node": "^0.5.2", + "@backstage/plugin-scaffolder-common": "^0.2.2", + "@backstage/search-common": "^0.2.4", + "@backstage/types": "^0.1.3", "@octokit/graphql": "^4.5.8", "@types/express": "^4.17.6", "aws-sdk": "^2.840.0", @@ -65,10 +69,10 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.17", - "@backstage/cli": "^0.13.2", - "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/test-utils": "^0.2.4", + "@backstage/backend-test-utils": "^0.1.19", + "@backstage/cli": "^0.14.1", + "@backstage/plugin-permission-common": "^0.5.1", + "@backstage/test-utils": "^0.2.6", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-backend/src/catalog/index.ts b/plugins/catalog-backend/src/catalog/index.ts index 95c6572b10..59f67b2f52 100644 --- a/plugins/catalog-backend/src/catalog/index.ts +++ b/plugins/catalog-backend/src/catalog/index.ts @@ -18,9 +18,11 @@ export type { EntitiesCatalog, EntitiesRequest, EntitiesResponse, - EntityAncestryResponse, - PageInfo, EntitiesSearchFilter, + EntityAncestryResponse, + EntityFacetsRequest, + EntityFacetsResponse, EntityFilter, EntityPagination, + PageInfo, } from './types'; diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index a79229b9a1..b575b96c6c 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -21,6 +21,7 @@ import { Entity } from '@backstage/catalog-model'; * * Any (at least one) of the outer sets must match, within which all of the * individual filters must match. + * @public */ export type EntityFilter = | { allOf: EntityFilter[] } @@ -30,6 +31,7 @@ export type EntityFilter = /** * A pagination rule for entities. + * @public */ export type EntityPagination = { limit?: number; @@ -38,7 +40,8 @@ export type EntityPagination = { }; /** - * Matches rows in the entities_search table. + * Matches rows in the search table. + * @public */ export type EntitiesSearchFilter = { /** @@ -57,6 +60,7 @@ export type EntitiesSearchFilter = { values?: string[]; }; +/** @public */ export type PageInfo = | { hasNextPage: false; @@ -66,6 +70,7 @@ export type PageInfo = endCursor: string; }; +/** @public */ export type EntitiesRequest = { filter?: EntityFilter; fields?: (entity: Entity) => Entity; @@ -73,6 +78,7 @@ export type EntitiesRequest = { authorizationToken?: string; }; +/** @public */ export type EntitiesResponse = { entities: Entity[]; pageInfo: PageInfo; @@ -87,6 +93,44 @@ export type EntityAncestryResponse = { }>; }; +/** + * The request shape for {@link EntitiesCatalog.facets}. + * + * @public + */ +export interface EntityFacetsRequest { + /** + * A filter to apply on the full list of entities before computing the facets. + */ + filter?: EntityFilter; + /** + * The facets to compute. + * + * @remarks + * + * This is a list of strings corresponding to paths within individual entity + * shapes. For example, to compute the facets for all available tags, you + * would pass in the string 'metadata.tags'. + */ + facets: string[]; + /** + * The optional token that authorizes the action. + */ + authorizationToken?: string; +} + +/** + * The response shape for {@link EntitiesCatalog.facets}. + * + * @public + */ +export interface EntityFacetsResponse { + /** + * The computed facets, one entry per facet in the request. + */ + facets: Record>; +} + /** @public */ export type EntitiesCatalog = { /** @@ -115,4 +159,12 @@ export type EntitiesCatalog = { entityRef: string, options?: { authorizationToken?: string }, ): Promise; + + /** + * Computes facets for a set of entities, e.g. for populating filter lists + * or driving insights or similar. + * + * @param request - Request options + */ + facets(request: EntityFacetsRequest): Promise; }; diff --git a/plugins/catalog-backend/src/database/types.ts b/plugins/catalog-backend/src/database/types.ts index ac2ff07498..08201323ea 100644 --- a/plugins/catalog-backend/src/database/types.ts +++ b/plugins/catalog-backend/src/database/types.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import { Entity, EntityRelationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/types'; import { DateTime } from 'luxon'; -import { DeferredEntity } from '../processing/types'; +import { DeferredEntity, EntityRelationSpec } from '../processing/types'; /** * An abstraction for transactions of the underlying database technology. diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts index 24d6b2fd66..d29a445a8c 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.test.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { DefaultCatalogRulesEnforcer } from './CatalogRules'; +import { LocationSpec } from './processors'; const entity = { user: { @@ -35,11 +36,11 @@ const entity = { const location: Record = { x: { - type: 'github', + type: 'url', target: 'https://github.com/a/b/blob/master/x.yaml', }, y: { - type: 'github', + type: 'url', target: 'https://github.com/a/b/blob/master/y.yaml', }, z: { @@ -180,7 +181,7 @@ describe('DefaultCatalogRulesEnforcer', () => { rules: [{ allow: ['user'] }], locations: [ { - type: 'github', + type: 'url', target: 'https://github.com/a/b/blob/master/x.yaml', rules: [ { @@ -204,7 +205,7 @@ describe('DefaultCatalogRulesEnforcer', () => { const enforcer = DefaultCatalogRulesEnforcer.fromConfig( new ConfigReader({ catalog: { - rules: [{ allow: ['Group'], locations: [{ type: 'github' }] }], + rules: [{ allow: ['Group'], locations: [{ type: 'url' }] }], }, }), ); diff --git a/plugins/catalog-backend/src/ingestion/CatalogRules.ts b/plugins/catalog-backend/src/ingestion/CatalogRules.ts index a0ef5f3d53..b4ad394168 100644 --- a/plugins/catalog-backend/src/ingestion/CatalogRules.ts +++ b/plugins/catalog-backend/src/ingestion/CatalogRules.ts @@ -15,8 +15,9 @@ */ import { Config } from '@backstage/config'; -import { LocationSpec, Entity } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import path from 'path'; +import { LocationSpec } from './processors'; /** * Rules to apply to catalog entities. diff --git a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.test.ts index 41d44ab157..5f5b20d1c2 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.test.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations } from '@backstage/integration'; +import { LocationSpec } from './types'; import { AnnotateLocationEntityProcessor } from './AnnotateLocationEntityProcessor'; describe('AnnotateLocationEntityProcessor', () => { diff --git a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts index 8adbaec628..b8c31ed5a2 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AnnotateLocationEntityProcessor.ts @@ -21,19 +21,23 @@ import { ANNOTATION_SOURCE_LOCATION, ANNOTATION_VIEW_URL, Entity, - LocationSpec, stringifyLocationRef, } from '@backstage/catalog-model'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { identity, merge, pickBy } from 'lodash'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; - -type Options = { - integrations: ScmIntegrationRegistry; -}; +import { CatalogProcessor, CatalogProcessorEmit, LocationSpec } from './types'; +/** @public */ export class AnnotateLocationEntityProcessor implements CatalogProcessor { - constructor(private readonly options: Options) {} + constructor( + private readonly options: { + integrations: ScmIntegrationRegistry; + }, + ) {} + + getProcessorName(): string { + return 'AnnotateLocationEntityProcessor'; + } async preProcessEntity( entity: Entity, diff --git a/plugins/catalog-backend/src/ingestion/processors/AnnotateScmSlugEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/AnnotateScmSlugEntityProcessor.test.ts index 5bdf4a4b83..311dd13789 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AnnotateScmSlugEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AnnotateScmSlugEntityProcessor.test.ts @@ -13,9 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { AnnotateScmSlugEntityProcessor } from './AnnotateScmSlugEntityProcessor'; +import { LocationSpec } from './types'; describe('AnnotateScmSlugEntityProcessor', () => { describe('github', () => { diff --git a/plugins/catalog-backend/src/ingestion/processors/AnnotateScmSlugEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AnnotateScmSlugEntityProcessor.ts index 483472da4b..4d3b3aff6c 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AnnotateScmSlugEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AnnotateScmSlugEntityProcessor.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { ScmIntegrationRegistry, @@ -21,15 +21,20 @@ import { } from '@backstage/integration'; import parseGitUrl from 'git-url-parse'; import { identity, merge, pickBy } from 'lodash'; -import { CatalogProcessor } from './types'; +import { CatalogProcessor, LocationSpec } from './types'; const GITHUB_ACTIONS_ANNOTATION = 'github.com/project-slug'; +/** @public */ export class AnnotateScmSlugEntityProcessor implements CatalogProcessor { constructor( private readonly opts: { scmIntegrationRegistry: ScmIntegrationRegistry }, ) {} + getProcessorName(): string { + return 'AnnotateScmSlugEntityProcessor'; + } + static fromConfig(config: Config): AnnotateScmSlugEntityProcessor { return new AnnotateScmSlugEntityProcessor({ scmIntegrationRegistry: ScmIntegrations.fromConfig(config), diff --git a/plugins/catalog-backend/src/ingestion/processors/AwsS3DiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AwsS3DiscoveryProcessor.ts index 63bc45726d..0dc44cc157 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AwsS3DiscoveryProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AwsS3DiscoveryProcessor.ts @@ -15,7 +15,6 @@ */ import { UrlReader } from '@backstage/backend-common'; -import { LocationSpec } from '@backstage/catalog-model'; import { isError } from '@backstage/errors'; import limiterFactory from 'p-limit'; import * as result from './results'; @@ -23,11 +22,17 @@ import { CatalogProcessor, CatalogProcessorEmit, CatalogProcessorParser, + LocationSpec, } from './types'; +/** @public */ export class AwsS3DiscoveryProcessor implements CatalogProcessor { constructor(private readonly reader: UrlReader) {} + getProcessorName(): string { + return 'AwsS3DiscoveryProcessor'; + } + async readLocation( location: LocationSpec, optional: boolean, diff --git a/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.test.ts index 463d982ca2..a406feaa74 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.test.ts @@ -15,13 +15,13 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { LocationSpec } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { codeSearch } from './azure'; import { AzureDevOpsDiscoveryProcessor, parseUrl, } from './AzureDevOpsDiscoveryProcessor'; +import { LocationSpec } from './types'; jest.mock('./azure'); const mockCodeSearch = codeSearch as jest.MockedFunction; @@ -167,7 +167,6 @@ describe('AzureDevOpsDiscoveryProcessor', () => { 'https://dev.azure.com/shopify/engineering/_git/backstage?path=/catalog-info.yaml', presence: 'optional', }, - optional: true, }); expect(emitter).toHaveBeenCalledWith({ type: 'location', @@ -177,7 +176,6 @@ describe('AzureDevOpsDiscoveryProcessor', () => { 'https://dev.azure.com/shopify/engineering/_git/ios-app?path=/src/catalog-info.yaml', presence: 'optional', }, - optional: true, }); }); @@ -215,7 +213,6 @@ describe('AzureDevOpsDiscoveryProcessor', () => { 'https://dev.azure.com/shopify/engineering/_git/backstage?path=/catalog-info.yaml', presence: 'optional', }, - optional: true, }); }); @@ -254,7 +251,6 @@ describe('AzureDevOpsDiscoveryProcessor', () => { 'https://dev.azure.com/shopify/engineering/_git/backstage?path=/src/main/catalog.yaml', presence: 'optional', }, - optional: true, }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.ts index 4f5df6f656..bc74621f1d 100644 --- a/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { ScmIntegrationRegistry, @@ -22,7 +21,7 @@ import { } from '@backstage/integration'; import { Logger } from 'winston'; import * as results from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit, LocationSpec } from './types'; import { codeSearch } from './azure'; /** @@ -38,6 +37,8 @@ import { codeSearch } from './azure'; * You may also explicitly specify a single repo: * * target: https://dev.azure.com/org/project/_git/repo + * + * @public **/ export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; @@ -60,6 +61,10 @@ export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor { this.logger = options.logger; } + getProcessorName(): string { + return 'AzureDevOpsDiscoveryProcessor'; + } + async readLocation( location: LocationSpec, _optional: boolean, @@ -97,17 +102,14 @@ export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor { for (const file of files) { emit( - results.location( - { - type: 'url', - target: `${baseUrl}/${org}/${project}/_git/${file.repository.name}?path=${file.path}`, - presence: 'optional', - }, + results.location({ + type: 'url', + target: `${baseUrl}/${org}/${project}/_git/${file.repository.name}?path=${file.path}`, // Not all locations may actually exist, since the user defined them as a wildcard pattern. // Thus, we emit them as optional and let the downstream processor find them while not outputting // an error if it couldn't. - true, - ), + presence: 'optional', + }), ); } diff --git a/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.test.ts index 7ace995fda..9ee8bf1b57 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.test.ts @@ -16,13 +16,13 @@ import { getVoidLogger } from '@backstage/backend-common'; import { BitbucketDiscoveryProcessor } from './BitbucketDiscoveryProcessor'; import { ConfigReader } from '@backstage/config'; -import { LocationSpec } from '@backstage/catalog-model'; import { BitbucketRepository20, BitbucketRepositoryParser, PagedResponse, PagedResponse20, } from './bitbucket'; +import { LocationSpec } from './types'; import { results } from './index'; import { RequestHandler, rest } from 'msw'; import { setupServer } from 'msw/node'; @@ -210,7 +210,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/projects/backstage/repos/backstage/browse/catalog.yaml', presence: 'optional', }, - optional: true, }); expect(emitter).toHaveBeenCalledWith({ type: 'location', @@ -220,7 +219,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/projects/demo/repos/demo/browse/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -246,7 +244,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/projects/backstage/repos/techdocs-cli/browse/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -269,7 +266,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/projects/demo/repos/demo/browse/catalog.yaml?ref=branch-name', presence: 'optional', }, - optional: true, }); }); @@ -292,7 +288,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/projects/backstage/repos/test/browse/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -321,7 +316,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/projects/backstage/repos/techdocs-cli/browse/catalog-info.yaml', presence: 'optional', }, - optional: true, }); }); }); @@ -369,7 +363,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/custom-path/projects/backstage/repos/backstage/browse/catalog.yaml', presence: 'optional', }, - optional: true, }); expect(emitter).toHaveBeenCalledWith({ type: 'location', @@ -379,7 +372,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/custom-path/projects/demo/repos/demo/browse/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -408,7 +400,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/custom-path/projects/backstage/repos/techdocs-cli/browse/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -434,7 +425,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/custom-path/projects/demo/repos/demo/browse/catalog.yaml?ref=branch-name', presence: 'optional', }, - optional: true, }); }); @@ -460,7 +450,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/custom-path/projects/backstage/repos/test/browse/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -492,7 +481,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.mycompany.com/custom-path/projects/backstage/repos/techdocs-cli/browse/catalog-info.yaml', presence: 'optional', }, - optional: true, }); }); }); @@ -536,7 +524,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-one/src/master/catalog-info.yaml', presence: 'optional', }, - optional: true, }); expect(emitter).toHaveBeenCalledWith({ type: 'location', @@ -546,7 +533,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-two/src/master/catalog-info.yaml', presence: 'optional', }, - optional: true, }); }); @@ -574,7 +560,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-one/src/master/my/nested/path/catalog.yaml', presence: 'optional', }, - optional: true, }); expect(emitter).toHaveBeenCalledWith({ type: 'location', @@ -584,7 +569,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-two/src/master/my/nested/path/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -612,7 +596,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-one/src/master/catalog.yaml', presence: 'optional', }, - optional: true, }); expect(emitter).toHaveBeenCalledWith({ type: 'location', @@ -622,7 +605,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-two/src/master/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -649,7 +631,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-one/src/master/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -677,7 +658,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-three/src/master/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -703,7 +683,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-one/src/master/catalog-info.yaml', presence: 'optional', }, - optional: true, }); expect(mockCall).toBeCalledTimes(1); // it should be possible to do this via an `expect.objectContaining` check but seems to fail with some encoding issue. @@ -739,7 +718,6 @@ describe('BitbucketDiscoveryProcessor', () => { 'https://bitbucket.org/myworkspace/repository-one/src/master/catalog-info.yaml', presence: 'optional', }, - optional: true, }); }); @@ -766,13 +744,11 @@ describe('BitbucketDiscoveryProcessor', () => { describe('Custom repository parser', () => { const customRepositoryParser: BitbucketRepositoryParser = async function* customRepositoryParser({}) { - yield results.location( - { - type: 'custom-location-type', - target: 'custom-target', - }, - true, - ); + yield results.location({ + type: 'custom-location-type', + target: 'custom-target', + presence: 'optional', + }); }; const processor = BitbucketDiscoveryProcessor.fromConfig( @@ -808,8 +784,8 @@ describe('BitbucketDiscoveryProcessor', () => { location: { type: 'custom-location-type', target: 'custom-target', + presence: 'optional', }, - optional: true, }); }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.ts index f08888d1ab..8fcf64dc55 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BitbucketDiscoveryProcessor.ts @@ -21,7 +21,6 @@ import { ScmIntegrationRegistry, ScmIntegrations, } from '@backstage/integration'; -import { LocationSpec } from '@backstage/catalog-model'; import { BitbucketRepositoryParser, BitbucketClient, @@ -31,12 +30,13 @@ import { BitbucketRepository, BitbucketRepository20, } from './bitbucket'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit, LocationSpec } from './types'; const DEFAULT_BRANCH = 'master'; const DEFAULT_CATALOG_LOCATION = '/catalog-info.yaml'; const EMPTY_CATALOG_LOCATION = '/'; +/** @public */ export class BitbucketDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly parser: BitbucketRepositoryParser; @@ -64,6 +64,10 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { this.logger = options.logger; } + getProcessorName(): string { + return 'BitbucketDiscoveryProcessor'; + } + async readLocation( location: LocationSpec, _optional: boolean, diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts index c42980d97b..2532293acf 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.test.ts @@ -21,9 +21,9 @@ import { GroupEntity, ResourceEntity, SystemEntity, - TemplateEntityV1beta2, UserEntity, } from '@backstage/catalog-model'; +import { TemplateEntityV1beta2 } from '@backstage/plugin-scaffolder-common'; import { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; describe('BuiltinKindsEntityProcessor', () => { @@ -187,7 +187,7 @@ describe('BuiltinKindsEntityProcessor', () => { await expect( processor.postProcessEntity(entity, location, emit), ).rejects.toThrowError( - 'Entity reference "r" did not specify a kind (e.g. starting with "Component:"), and has no default', + 'Entity reference "r" had missing or empty kind (e.g. did not start with "component:" or similar)', ); }); @@ -360,7 +360,7 @@ describe('BuiltinKindsEntityProcessor', () => { await expect( processor.postProcessEntity(entity, location, emit), ).rejects.toThrowError( - 'Entity reference "c" did not specify a kind (e.g. starting with "Component:"), and has no default', + 'Entity reference "c" had missing or empty kind (e.g. did not start with "component:" or similar)', ); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts index 5999be9793..89b0bb1bb5 100644 --- a/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/BuiltinKindsEntityProcessor.ts @@ -26,7 +26,6 @@ import { GroupEntity, groupEntityV1alpha1Validator, locationEntityV1alpha1Validator, - LocationSpec, parseEntityRef, RELATION_API_CONSUMED_BY, RELATION_API_PROVIDED_BY, @@ -46,14 +45,17 @@ import { resourceEntityV1alpha1Validator, SystemEntity, systemEntityV1alpha1Validator, - TemplateEntityV1beta2, - templateEntityV1beta2Validator, UserEntity, userEntityV1alpha1Validator, } from '@backstage/catalog-model'; +import { + TemplateEntityV1beta2, + templateEntityV1beta2Validator, +} from '@backstage/plugin-scaffolder-common'; import * as result from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit, LocationSpec } from './types'; +/** @public */ export class BuiltinKindsEntityProcessor implements CatalogProcessor { private readonly validators = [ apiEntityV1alpha1Validator, @@ -67,6 +69,10 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { domainEntityV1alpha1Validator, ]; + getProcessorName(): string { + return 'BuiltinKindsEntityProcessor'; + } + async validateEntityKind(entity: Entity): Promise { for (const validator of this.validators) { const results = await validator.check(entity); @@ -100,11 +106,6 @@ export class BuiltinKindsEntityProcessor implements CatalogProcessor { } for (const target of [targets].flat()) { const targetRef = parseEntityRef(target, context); - if (targetRef.kind === undefined) { - throw new Error( - `Entity reference "${target}" did not specify a kind (e.g. starting with "Component:"), and has no default`, - ); - } emit( result.relation({ source: selfRef, diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts index 2f52a1ddd9..8d6d1f8ff7 100644 --- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.test.ts @@ -15,9 +15,9 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { LocationSpec } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { CodeOwnersProcessor } from './CodeOwnersProcessor'; +import { LocationSpec } from './types'; const mockCodeOwnersText = () => ` * @acme/team-foo @acme/team-bar @@ -27,7 +27,7 @@ const mockCodeOwnersText = () => ` describe('CodeOwnersProcessor', () => { const mockLocation = ({ basePath = '', - type = 'github', + type = 'url', } = {}): LocationSpec => ({ type, target: `https://github.com/backstage/backstage/blob/master/${basePath}catalog-info.yaml`, diff --git a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts index f358abf7e9..93e3c1618a 100644 --- a/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/CodeOwnersProcessor.ts @@ -15,7 +15,7 @@ */ import { UrlReader } from '@backstage/backend-common'; -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { ScmIntegrationRegistry, @@ -23,20 +23,13 @@ import { } from '@backstage/integration'; import { Logger } from 'winston'; import { findCodeOwnerByTarget } from './codeowners'; -import { CatalogProcessor } from './types'; +import { CatalogProcessor, LocationSpec } from './types'; const ALLOWED_KINDS = ['API', 'Component', 'Domain', 'Resource', 'System']; -const ALLOWED_LOCATION_TYPES = [ - 'url', - 'azure/api', - 'bitbucket/api', - 'github', - 'github/api', - 'gitlab', - 'gitlab/api', -]; +const ALLOWED_LOCATION_TYPES = ['url']; +/** @public */ export class CodeOwnersProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly logger: Logger; @@ -64,6 +57,10 @@ export class CodeOwnersProcessor implements CatalogProcessor { this.reader = options.reader; } + getProcessorName(): string { + return 'CodeOwnersProcessor'; + } + async preProcessEntity( entity: Entity, location: LocationSpec, diff --git a/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts index 9ff3f52ba0..996beff24a 100644 --- a/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/FileReaderProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import fs from 'fs-extra'; import g from 'glob'; import path from 'path'; @@ -24,11 +23,17 @@ import { CatalogProcessor, CatalogProcessorEmit, CatalogProcessorParser, + LocationSpec, } from './types'; const glob = promisify(g); +/** @public */ export class FileReaderProcessor implements CatalogProcessor { + getProcessorName(): string { + return 'FileReaderProcessor'; + } + async readLocation( location: LocationSpec, optional: boolean, diff --git a/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.test.ts index 8d85d7375f..8261690e8f 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.test.ts @@ -16,11 +16,11 @@ import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; -import { LocationSpec } from '@backstage/catalog-model'; import { GitLabDiscoveryProcessor, parseUrl } from './GitLabDiscoveryProcessor'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; import { GitLabProject } from './gitlab'; +import { LocationSpec } from './types'; const server = setupServer(); @@ -210,7 +210,6 @@ describe('GitlabDiscoveryProcessor', () => { target: 'https://gitlab.fake/1/-/blob/main/catalog-info.yaml', presence: 'optional', }, - optional: true, }, { type: 'location', @@ -219,7 +218,6 @@ describe('GitlabDiscoveryProcessor', () => { target: 'https://gitlab.fake/2/-/blob/master/catalog-info.yaml', presence: 'optional', }, - optional: true, }, ]); }); @@ -257,7 +255,6 @@ describe('GitlabDiscoveryProcessor', () => { target: 'https://gitlab.fake/1/-/blob/master/catalog-info.yaml', presence: 'optional', }, - optional: true, }, ]); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.ts index cc0113bfbe..b534d50044 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GitLabDiscoveryProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { ScmIntegrationRegistry, @@ -22,7 +21,7 @@ import { } from '@backstage/integration'; import { Logger } from 'winston'; import * as results from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit, LocationSpec } from './types'; import { GitLabClient, GitLabProject, paginated } from './gitlab'; import { CacheClient, @@ -32,6 +31,7 @@ import { /** * Extracts repositories out of an GitLab instance. + * @public */ export class GitLabDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; @@ -60,6 +60,10 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { this.logger = options.logger; } + getProcessorName(): string { + return 'GitLabDiscoveryProcessor'; + } + async readLocation( location: LocationSpec, _optional: boolean, @@ -113,20 +117,17 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { const project_branch = branch === '*' ? project.default_branch : branch; emit( - results.location( - { - type: 'url', - // The format expected by the GitLabUrlReader: - // https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath - // - // This unfortunately will trigger another API call in `getGitLabFileFetchUrl` to get the project ID. - // The alternative is using the `buildRawUrl` function, which does not support subgroups, so providing a raw - // URL here won't work either. - target: `${project.web_url}/-/blob/${project_branch}/${catalogPath}`, - presence: 'optional', - }, - true, - ), + results.location({ + type: 'url', + // The format expected by the GitLabUrlReader: + // https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath + // + // This unfortunately will trigger another API call in `getGitLabFileFetchUrl` to get the project ID. + // The alternative is using the `buildRawUrl` function, which does not support subgroups, so providing a raw + // URL here won't work either. + target: `${project.web_url}/-/blob/${project_branch}/${catalogPath}`, + presence: 'optional', + }), ); } @@ -138,9 +139,10 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { return true; } - async updateLastActivity(): Promise { - const lastActivity = await this.cache.get('last-activity'); - await this.cache.set('last-activity', new Date().toISOString()); + private async updateLastActivity(): Promise { + const cacheKey = `processors/${this.getProcessorName()}/last-activity`; + const lastActivity = await this.cache.get(cacheKey); + await this.cache.set(cacheKey, new Date().toISOString()); return lastActivity as string | undefined; } } diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.test.ts index d6746f13da..55e5560451 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.test.ts @@ -15,9 +15,9 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { LocationSpec } from '@backstage/catalog-model'; import { GithubDiscoveryProcessor, parseUrl } from './GithubDiscoveryProcessor'; import { getOrganizationRepositories } from './github'; +import { LocationSpec } from './types'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations, @@ -175,7 +175,6 @@ describe('GithubDiscoveryProcessor', () => { 'https://github.com/backstage/backstage/blob/master/catalog.yaml', presence: 'optional', }, - optional: true, }); expect(emitter).toHaveBeenCalledWith({ type: 'location', @@ -184,7 +183,6 @@ describe('GithubDiscoveryProcessor', () => { target: 'https://github.com/backstage/demo/blob/master/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -217,7 +215,6 @@ describe('GithubDiscoveryProcessor', () => { 'https://github.com/backstage/tech-docs/blob/main/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -272,7 +269,6 @@ describe('GithubDiscoveryProcessor', () => { 'https://github.com/backstage/backstage/blob/master/catalog-info.yaml', presence: 'optional', }, - optional: true, }); }); @@ -328,7 +324,6 @@ describe('GithubDiscoveryProcessor', () => { 'https://github.com/backstage/techdocs-cli/blob/master/catalog.yaml', presence: 'optional', }, - optional: true, }); expect(emitter).toHaveBeenCalledWith({ type: 'location', @@ -338,7 +333,6 @@ describe('GithubDiscoveryProcessor', () => { 'https://github.com/backstage/techdocs-container/blob/master/catalog.yaml', presence: 'optional', }, - optional: true, }); }); @@ -394,7 +388,6 @@ describe('GithubDiscoveryProcessor', () => { target: 'https://github.com/backstage/test/blob/master/catalog.yaml', presence: 'optional', }, - optional: true, }); }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.ts index 3cb73f0181..e790134011 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubDiscoveryProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { DefaultGithubCredentialsProvider, @@ -26,7 +25,7 @@ import { graphql } from '@octokit/graphql'; import { Logger } from 'winston'; import { getOrganizationRepositories } from './github'; import * as results from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit, LocationSpec } from './types'; /** * Extracts repositories out of a GitHub org. @@ -41,6 +40,8 @@ import { CatalogProcessor, CatalogProcessorEmit } from './types'; * You may also explicitly specify the source branch: * * target: https://github.com/backstage/*\/blob/main/catalog-info.yaml + * + * @public **/ export class GithubDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; @@ -73,6 +74,9 @@ export class GithubDiscoveryProcessor implements CatalogProcessor { options.githubCredentialsProvider || DefaultGithubCredentialsProvider.fromIntegrations(this.integrations); } + getProcessorName(): string { + return 'GithubDiscoveryProcessor'; + } async readLocation( location: LocationSpec, @@ -137,17 +141,14 @@ export class GithubDiscoveryProcessor implements CatalogProcessor { const path = `/blob/${branchName}${catalogPath}`; emit( - results.location( - { - type: 'url', - target: `${repository.url}${path}`, - presence: 'optional', - }, + results.location({ + type: 'url', + target: `${repository.url}${path}`, // Not all locations may actually exist, since the user defined them as a wildcard pattern. // Thus, we emit them as optional and let the downstream processor find them while not outputting // an error if it couldn't. - true, - ), + presence: 'optional', + }), ); } diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts index 7d10428494..383b82c019 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { DefaultGithubCredentialsProvider, @@ -33,7 +32,7 @@ import { readGithubMultiOrgConfig, } from './github'; import * as results from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit, LocationSpec } from './types'; import { buildOrgHierarchy } from './util/org'; /** @@ -78,6 +77,9 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { options.githubCredentialsProvider || DefaultGithubCredentialsProvider.fromIntegrations(this.integrations); } + getProcessorName(): string { + return 'GithubMultiOrgReaderProcessor'; + } async readLocation( location: LocationSpec, diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts index 953c926c71..c01db88d3a 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ import { getVoidLogger } from '@backstage/backend-common'; -import { LocationSpec } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations, @@ -22,6 +21,7 @@ import { } from '@backstage/integration'; import { graphql } from '@octokit/graphql'; import { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor'; +import { LocationSpec } from './types'; jest.mock('@octokit/graphql'); diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts index dbb7b98e87..8b5914cc80 100644 --- a/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/GithubOrgReaderProcessor.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { GithubCredentialType, @@ -31,13 +30,14 @@ import { parseGitHubOrgUrl, } from './github'; import * as results from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit, LocationSpec } from './types'; import { assignGroupsToUsers, buildOrgHierarchy } from './util/org'; type GraphQL = typeof graphql; /** * Extracts teams and users out of a GitHub org. + * @public */ export class GithubOrgReaderProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; @@ -70,6 +70,9 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { DefaultGithubCredentialsProvider.fromIntegrations(this.integrations); this.logger = options.logger; } + getProcessorName(): string { + return 'GithubOrgReaderProcessor'; + } async readLocation( location: LocationSpec, diff --git a/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.test.ts index c4551e30f8..83f1ca1925 100644 --- a/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.test.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { ConfigReader } from '@backstage/config'; import { ScmIntegrations, @@ -22,6 +21,7 @@ import { } from '@backstage/integration'; import path from 'path'; import { toAbsoluteUrl } from './LocationEntityProcessor'; +import { LocationSpec } from './types'; describe('LocationEntityProcessor', () => { describe('toAbsoluteUrl', () => { diff --git a/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts index c6b7def5fc..533a366f57 100644 --- a/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/LocationEntityProcessor.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { Entity, LocationEntity, LocationSpec } from '@backstage/catalog-model'; +import { Entity, LocationEntity } from '@backstage/catalog-model'; import { ScmIntegrationRegistry } from '@backstage/integration'; import path from 'path'; import * as result from './results'; -import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { CatalogProcessor, CatalogProcessorEmit, LocationSpec } from './types'; export function toAbsoluteUrl( integrations: ScmIntegrationRegistry, @@ -38,13 +38,19 @@ export function toAbsoluteUrl( } } +/** @public */ export type LocationEntityProcessorOptions = { integrations: ScmIntegrationRegistry; }; +/** @public */ export class LocationEntityProcessor implements CatalogProcessor { constructor(private readonly options: LocationEntityProcessorOptions) {} + getProcessorName(): string { + return 'LocationEntityProcessor'; + } + async postProcessEntity( entity: Entity, location: LocationSpec, @@ -77,7 +83,7 @@ export class LocationEntityProcessor implements CatalogProcessor { location, maybeRelativeTarget, ); - emit(result.location({ type, target }, false)); + emit(result.location({ type, target })); } } diff --git a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts index c7a49353ae..7bfc66276e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.test.ts @@ -154,7 +154,7 @@ describe('PlaceholderProcessor', () => { spec: { data: { $text: '../file.txt' } }, }, { - type: 'github', + type: 'url', target: 'https://github.com/backstage/backstage/a/b/catalog-info.yaml', }, @@ -190,7 +190,7 @@ describe('PlaceholderProcessor', () => { spec: { data: { $json: './file.json' } }, }, { - type: 'github', + type: 'url', target: 'https://github.com/backstage/backstage/a/b/catalog-info.yaml', }, @@ -224,7 +224,7 @@ describe('PlaceholderProcessor', () => { spec: { data: { $yaml: '../file.yaml' } }, }, { - type: 'github', + type: 'url', target: 'https://github.com/backstage/backstage/a/b/catalog-info.yaml', }, @@ -262,7 +262,7 @@ describe('PlaceholderProcessor', () => { }, }, { - type: 'github', + type: 'url', target: 'https://github.com/backstage/backstage/a/b/catalog-info.yaml', }, @@ -300,7 +300,7 @@ describe('PlaceholderProcessor', () => { }, }, { - type: 'github', + type: 'url', target: './a/b/catalog-info.yaml', }, ), @@ -340,7 +340,7 @@ describe('PlaceholderProcessor', () => { }, }, { - type: 'github', + type: 'url', target: './a/b/catalog-info.yaml', }, ), diff --git a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts index 65f65a0479..0d572a024d 100644 --- a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts @@ -15,18 +15,22 @@ */ import { UrlReader } from '@backstage/backend-common'; -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; import { ScmIntegrationRegistry } from '@backstage/integration'; import yaml from 'yaml'; -import { CatalogProcessor } from './types'; +import { CatalogProcessor, LocationSpec } from './types'; +/** @public */ export type PlaceholderResolverRead = (url: string) => Promise; + +/** @public */ export type PlaceholderResolverResolveUrl = ( url: string, base: string, ) => string; +/** @public */ export type PlaceholderResolverParams = { key: string; value: JsonValue; @@ -35,10 +39,12 @@ export type PlaceholderResolverParams = { resolveUrl: PlaceholderResolverResolveUrl; }; +/** @public */ export type PlaceholderResolver = ( params: PlaceholderResolverParams, ) => Promise; +/** @public */ export type PlaceholderProcessorOptions = { resolvers: Record; reader: UrlReader; @@ -48,10 +54,15 @@ export type PlaceholderProcessorOptions = { /** * Traverses raw entity JSON looking for occurrences of $-prefixed placeholders * that it then fills in with actual data. + * @public */ export class PlaceholderProcessor implements CatalogProcessor { constructor(private readonly options: PlaceholderProcessorOptions) {} + getProcessorName(): string { + return 'PlaceholderProcessor'; + } + async preProcessEntity( entity: Entity, location: LocationSpec, diff --git a/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts index 425ad6e0fe..1c2c27f425 100644 --- a/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/StaticLocationProcessor.ts @@ -14,11 +14,14 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import * as result from './results'; -import { CatalogProcessorEmit } from './types'; +import { CatalogProcessorEmit, LocationSpec } from './types'; +/** + * @deprecated no longer in use, replaced by the ConfigLocationEntityProvider. + * @public + */ export class StaticLocationProcessor implements StaticLocationProcessor { static fromConfig(config: Config): StaticLocationProcessor { const locations: LocationSpec[] = []; @@ -45,7 +48,7 @@ export class StaticLocationProcessor implements StaticLocationProcessor { } for (const staticLocation of this.staticLocations) { - emit(result.location(staticLocation, false)); + emit(result.location(staticLocation)); } return true; diff --git a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts index 6a85cc07c7..c2be454bcf 100644 --- a/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts +++ b/plugins/catalog-backend/src/ingestion/processors/UrlReaderProcessor.ts @@ -15,7 +15,7 @@ */ import { UrlReader } from '@backstage/backend-common'; -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { assertError } from '@backstage/errors'; import parseGitUrl from 'git-url-parse'; import limiterFactory from 'p-limit'; @@ -28,15 +28,11 @@ import { CatalogProcessorEntityResult, CatalogProcessorParser, CatalogProcessorResult, + LocationSpec, } from './types'; const CACHE_KEY = 'v1'; -type Options = { - reader: UrlReader; - logger: Logger; -}; - // WARNING: If you change this type, you likely need to bump the CACHE_KEY as well type CacheItem = { etag: string; @@ -47,8 +43,14 @@ type CacheItem = { }[]; }; +/** @public */ export class UrlReaderProcessor implements CatalogProcessor { - constructor(private readonly options: Options) {} + constructor( + private readonly options: { + reader: UrlReader; + logger: Logger; + }, + ) {} getProcessorName() { return 'url-reader'; diff --git a/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.test.ts b/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.test.ts index 397d259bae..35e24ed4cb 100644 --- a/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.test.ts @@ -25,14 +25,11 @@ describe('BitbucketRepositoryParser', () => { 'https://bitbucket.mycompany.com/projects/project-key/repos/repo-slug/browse'; const path = '/catalog-info.yaml'; const expected = [ - results.location( - { - type: 'url', - target: `${browseUrl}${path}`, - presence: 'optional', - }, - true, - ), + results.location({ + type: 'url', + target: `${browseUrl}${path}`, + presence: 'optional', + }), ]; const actual = await defaultRepositoryParser({ integration: {} as BitbucketIntegration, diff --git a/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.ts b/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.ts index 34a9284b3e..9eaec221b5 100644 --- a/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.ts +++ b/plugins/catalog-backend/src/ingestion/processors/bitbucket/BitbucketRepositoryParser.ts @@ -18,6 +18,7 @@ import { results } from '../index'; import { Logger } from 'winston'; import { BitbucketIntegration } from '@backstage/integration'; +/** @public */ export type BitbucketRepositoryParser = (options: { integration: BitbucketIntegration; target: string; @@ -26,15 +27,12 @@ export type BitbucketRepositoryParser = (options: { export const defaultRepositoryParser: BitbucketRepositoryParser = async function* defaultRepositoryParser({ target }) { - yield results.location( - { - type: 'url', - target: target, - presence: 'optional', - }, + yield results.location({ + type: 'url', + target: target, // Not all locations may actually exist, since the user defined them as a wildcard pattern. // Thus, we emit them as optional and let the downstream processor find them while not outputting // an error if it couldn't. - true, - ); + presence: 'optional', + }); }; diff --git a/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts b/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts index a67d2378d0..3b722a0e7b 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts @@ -15,68 +15,9 @@ */ import { ConfigReader } from '@backstage/config'; -import { readGithubConfig, readGithubMultiOrgConfig } from './config'; +import { readGithubMultiOrgConfig } from './config'; describe('config', () => { - describe('readGithubConfig', () => { - function config( - providers: { target: string; apiBaseUrl?: string; token?: string }[], - ) { - return new ConfigReader({ - catalog: { processors: { githubOrg: { providers } } }, - }); - } - - it('adds a default GitHub entry when missing', () => { - const output = readGithubConfig(config([])); - expect(output).toEqual([ - { - target: 'https://github.com', - apiBaseUrl: 'https://api.github.com', - }, - ]); - }); - - it('injects the correct GitHub API base URL when missing', () => { - const output = readGithubConfig( - config([{ target: 'https://github.com' }]), - ); - expect(output).toEqual([ - { - target: 'https://github.com', - apiBaseUrl: 'https://api.github.com', - }, - ]); - }); - - it('rejects custom targets with no base URLs', () => { - expect(() => - readGithubConfig(config([{ target: 'https://ghe.company.com' }])), - ).toThrow( - 'Provider at https://ghe.company.com must configure an explicit apiBaseUrl', - ); - }); - - it('rejects funky configs', () => { - expect(() => readGithubConfig(config([{ target: 7 } as any]))).toThrow( - /target/, - ); - expect(() => - readGithubConfig(config([{ noTarget: '7' } as any])), - ).toThrow(/target/); - expect(() => - readGithubConfig( - config([{ target: 'https://github.com', apiBaseUrl: 7 } as any]), - ), - ).toThrow(/apiBaseUrl/); - expect(() => - readGithubConfig( - config([{ target: 'https://github.com', token: 7 } as any]), - ), - ).toThrow(/token/); - }); - }); - describe('readGithubMultiOrgConfig', () => { function config( orgs: { name: string; groupNamespace?: string; userNamespace?: string }[], diff --git a/plugins/catalog-backend/src/ingestion/processors/github/config.ts b/plugins/catalog-backend/src/ingestion/processors/github/config.ts index 27bec6eb0d..242296b4be 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/config.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/config.ts @@ -15,77 +15,10 @@ */ import { Config } from '@backstage/config'; -import { trimEnd } from 'lodash'; - -/** - * The configuration parameters for a single GitHub API provider. - */ -export type ProviderConfig = { - /** - * The prefix of the target that this matches on, e.g. "https://github.com", - * with no trailing slash. - */ - target: string; - - /** - * The base URL of the API of this provider, e.g. "https://api.github.com", - * with no trailing slash. - * - * May be omitted specifically for GitHub; then it will be deduced. - */ - apiBaseUrl?: string; - - /** - * The authorization token to use for requests to this provider. - * - * If no token is specified, anonymous access is used. - */ - token?: string; -}; - -// TODO(freben): Break out common code and config from here and GithubReaderProcessor -export function readGithubConfig(config: Config): ProviderConfig[] { - const providers: ProviderConfig[] = []; - - const providerConfigs = - config.getOptionalConfigArray('catalog.processors.githubOrg.providers') ?? - []; - - // First read all the explicit providers - for (const providerConfig of providerConfigs) { - const target = trimEnd(providerConfig.getString('target'), '/'); - let apiBaseUrl = providerConfig.getOptionalString('apiBaseUrl'); - const token = providerConfig.getOptionalString('token'); - - if (apiBaseUrl) { - apiBaseUrl = trimEnd(apiBaseUrl, '/'); - } else if (target === 'https://github.com') { - apiBaseUrl = 'https://api.github.com'; - } - - if (!apiBaseUrl) { - throw new Error( - `Provider at ${target} must configure an explicit apiBaseUrl`, - ); - } - - providers.push({ target, apiBaseUrl, token }); - } - - // If no explicit github.com provider was added, put one in the list as - // a convenience - if (!providers.some(p => p.target === 'https://github.com')) { - providers.push({ - target: 'https://github.com', - apiBaseUrl: 'https://api.github.com', - }); - } - - return providers; -} /** * The configuration parameters for a multi-org GitHub processor. + * @public */ export type GithubMultiOrgConfig = Array<{ /** diff --git a/plugins/catalog-backend/src/ingestion/processors/github/index.ts b/plugins/catalog-backend/src/ingestion/processors/github/index.ts index 3d9e881ddd..3e31d9e00c 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/index.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -export { readGithubConfig, readGithubMultiOrgConfig } from './config'; -export type { GithubMultiOrgConfig, ProviderConfig } from './config'; +export { readGithubMultiOrgConfig } from './config'; +export type { GithubMultiOrgConfig } from './config'; export { getOrganizationRepositories, getOrganizationTeams, diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts index 63feac51af..84a92fb1ba 100644 --- a/plugins/catalog-backend/src/ingestion/processors/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/index.ts @@ -18,8 +18,6 @@ import * as results from './results'; export { AnnotateLocationEntityProcessor } from './AnnotateLocationEntityProcessor'; export { AnnotateScmSlugEntityProcessor } from './AnnotateScmSlugEntityProcessor'; -export { AwsOrganizationCloudAccountProcessor } from './AwsOrganizationCloudAccountProcessor'; -export type { AwsOrganizationProviderConfig } from './awsOrganization/config'; export { AwsS3DiscoveryProcessor } from './AwsS3DiscoveryProcessor'; export { BitbucketDiscoveryProcessor } from './BitbucketDiscoveryProcessor'; export { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor'; @@ -47,3 +45,4 @@ export { parseEntityYaml } from './util/parse'; export { results }; export type { BitbucketRepositoryParser } from './bitbucket'; +export type { GithubMultiOrgConfig } from './github'; diff --git a/plugins/catalog-backend/src/ingestion/processors/results.ts b/plugins/catalog-backend/src/ingestion/processors/results.ts index 5b6f07bae4..01583678df 100644 --- a/plugins/catalog-backend/src/ingestion/processors/results.ts +++ b/plugins/catalog-backend/src/ingestion/processors/results.ts @@ -15,13 +15,11 @@ */ import { InputError, NotFoundError } from '@backstage/errors'; -import { - Entity, - EntityRelationSpec, - LocationSpec, -} from '@backstage/catalog-model'; -import { CatalogProcessorResult } from './types'; +import { Entity } from '@backstage/catalog-model'; +import { CatalogProcessorResult, LocationSpec } from './types'; +import { EntityRelationSpec } from '../../processing/types'; +/** @public */ export function notFoundError( atLocation: LocationSpec, message: string, @@ -33,6 +31,7 @@ export function notFoundError( }; } +/** @public */ export function inputError( atLocation: LocationSpec, message: string, @@ -44,6 +43,7 @@ export function inputError( }; } +/** @public */ export function generalError( atLocation: LocationSpec, message: string, @@ -51,13 +51,15 @@ export function generalError( return { type: 'error', location: atLocation, error: new Error(message) }; } +/** @public */ export function location( newLocation: LocationSpec, - optional: boolean, + optional?: boolean, ): CatalogProcessorResult { return { type: 'location', location: newLocation, optional }; } +/** @public */ export function entity( atLocation: LocationSpec, newEntity: Entity, @@ -65,6 +67,7 @@ export function entity( return { type: 'entity', location: atLocation, entity: newEntity }; } +/** @public */ export function relation(spec: EntityRelationSpec): CatalogProcessorResult { return { type: 'relation', relation: spec }; } diff --git a/plugins/catalog-backend/src/ingestion/processors/types.ts b/plugins/catalog-backend/src/ingestion/processors/types.ts index 3e4e24b0fe..879b4d32a8 100644 --- a/plugins/catalog-backend/src/ingestion/processors/types.ts +++ b/plugins/catalog-backend/src/ingestion/processors/types.ts @@ -14,19 +14,35 @@ * limitations under the License. */ -import { - Entity, - EntityRelationSpec, - LocationSpec, -} from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; +import { EntityRelationSpec } from '../../processing/types'; +/** + * Holds the entity location information. + * + * @remarks + * + * `presence` flag: when using repo importer plugin, location is being created before the component yaml file is merged to the main branch. + * This flag is then set to indicate that the file can be not present. + * default value: 'required'. + * + * @public + */ +export type LocationSpec = { + type: string; + target: string; + presence?: 'optional' | 'required'; +}; + +/** + * @public + */ export type CatalogProcessor = { /** * A unique identifier for the Catalog Processor. - * It's strongly recommended to implement getProcessorName as this method will be required in the future. */ - getProcessorName?(): string; + getProcessorName(): string; /** * Reads the contents of a location. @@ -120,6 +136,7 @@ export type CatalogProcessor = { * A parser, that is able to take the raw catalog descriptor data and turn it * into the actual result pieces. The default implementation performs a YAML * document parsing. + * @public */ export type CatalogProcessorParser = (options: { data: Buffer; @@ -154,32 +171,39 @@ export interface CatalogProcessorCache { set(key: string, value: ItemType): Promise; } +/** @public */ export type CatalogProcessorEmit = (generated: CatalogProcessorResult) => void; +/** @public */ export type CatalogProcessorLocationResult = { type: 'location'; location: LocationSpec; - optional: boolean; + /** @deprecated Set `location.presence = 'optional'` instead */ + optional?: boolean; }; +/** @public */ export type CatalogProcessorEntityResult = { type: 'entity'; entity: Entity; location: LocationSpec; }; +/** @public */ export type CatalogProcessorRelationResult = { type: 'relation'; relation: EntityRelationSpec; entityRef?: string; }; +/** @public */ export type CatalogProcessorErrorResult = { type: 'error'; error: Error; location: LocationSpec; }; +/** @public */ export type CatalogProcessorResult = | CatalogProcessorLocationResult | CatalogProcessorEntityResult diff --git a/plugins/catalog-backend/src/ingestion/processors/util/parse.ts b/plugins/catalog-backend/src/ingestion/processors/util/parse.ts index a6aba7e1f9..0beaf1cd4c 100644 --- a/plugins/catalog-backend/src/ingestion/processors/util/parse.ts +++ b/plugins/catalog-backend/src/ingestion/processors/util/parse.ts @@ -14,16 +14,17 @@ * limitations under the License. */ -import { - Entity, - LocationSpec, - stringifyLocationRef, -} from '@backstage/catalog-model'; +import { Entity, stringifyLocationRef } from '@backstage/catalog-model'; import lodash from 'lodash'; import yaml from 'yaml'; import * as result from '../results'; -import { CatalogProcessorParser, CatalogProcessorResult } from '../types'; +import { + CatalogProcessorParser, + CatalogProcessorResult, + LocationSpec, +} from '../types'; +/** @public */ export function* parseEntityYaml( data: Buffer, location: LocationSpec, diff --git a/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.ts b/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.ts index 20e9966bd1..a20f7fdb0b 100644 --- a/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.ts +++ b/plugins/catalog-backend/src/ingestion/providers/GitHubOrgEntityProvider.ts @@ -38,7 +38,7 @@ import { import { assignGroupsToUsers, buildOrgHierarchy } from '../processors/util/org'; // TODO: Consider supporting an (optional) webhook that reacts on org changes - +/** @public */ export class GitHubOrgEntityProvider implements EntityProvider { private connection?: EntityProviderConnection; private githubCredentialsProvider: GithubCredentialsProvider; diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 30cdee4760..eccf583e4b 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -14,13 +14,11 @@ * limitations under the License. */ -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { RecursivePartial } from '../util/RecursivePartial'; +import { LocationSpec } from './processors'; -// -// LocationAnalyzer -// - +/** @public */ export type LocationAnalyzer = { /** * Generates an entity configuration for given git repository. It's used for @@ -33,30 +31,38 @@ export type LocationAnalyzer = { ): Promise; }; +/** @public */ export type AnalyzeLocationRequest = { location: LocationSpec; }; +/** @public */ export type AnalyzeLocationResponse = { existingEntityFiles: AnalyzeLocationExistingEntity[]; generateEntities: AnalyzeLocationGenerateEntity[]; }; -// If the folder pointed to already contained catalog info yaml files, they are -// read and emitted like this so that the frontend can inform the user that it -// located them and can make sure to register them as well if they weren't -// already +/** + * If the folder pointed to already contained catalog info yaml files, they are + * read and emitted like this so that the frontend can inform the user that it + * located them and can make sure to register them as well if they weren't + * already + * @public + */ export type AnalyzeLocationExistingEntity = { location: LocationSpec; isRegistered: boolean; entity: Entity; }; -// This is some form of representation of what the analyzer could deduce. -// We should probably have a chat about how this can best be conveyed to -// the frontend. It'll probably contain a (possibly incomplete) entity, plus -// enough info for the frontend to know what form data to show to the user -// for overriding/completing the info. +/** + * This is some form of representation of what the analyzer could deduce. + * We should probably have a chat about how this can best be conveyed to + * the frontend. It'll probably contain a (possibly incomplete) entity, plus + * enough info for the frontend to know what form data to show to the user + * for overriding/completing the info. + * @public + * */ export type AnalyzeLocationGenerateEntity = { // Some form of partial representation of the entity entity: RecursivePartial; @@ -67,12 +73,15 @@ export type AnalyzeLocationGenerateEntity = { // This is where I get really vague. Something like this perhaps? Or it could be // something like a json-schema that contains enough info for the frontend to // be able to present a form and explanations +/** @public */ export type AnalyzeLocationEntityField = { - // e.g. "spec.owner"? The frontend needs to know how to "inject" the field into the - // entity again if the user wants to change it + /** + * e.g. "spec.owner"? The frontend needs to know how to "inject" the field into the + * entity again if the user wants to change it + */ field: string; - // The outcome of the analysis for this particular field + /** The outcome of the analysis for this particular field */ state: | 'analysisSuggestedValue' | 'analysisSuggestedNoValue' @@ -81,10 +90,11 @@ export type AnalyzeLocationEntityField = { // If the analysis did suggest a value, this is where it would be. Not sure if we want // to limit this to strings or if we want it to be any JsonValue value: string | null; - - // A text to show to the user to inform about the choices made. Like, it could say - // "Found a CODEOWNERS file that covers this target, so we suggest leaving this - // field empty; which would currently make it owned by X" where X is taken from the - // codeowners file. + /** + * A text to show to the user to inform about the choices made. Like, it could say + * "Found a CODEOWNERS file that covers this target, so we suggest leaving this + * field empty; which would currently make it owned by X" where X is taken from the + * codeowners file. + */ description: string; }; diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts index 282d2558ef..8e20655393 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts @@ -29,6 +29,7 @@ describe('isEntityOwner', () => { relations: [ { type: 'ownedBy', + targetRef: 'user:default/spiderman', target: { kind: 'user', namespace: 'default', @@ -52,6 +53,7 @@ describe('isEntityOwner', () => { relations: [ { type: 'ownedBy', + targetRef: 'user:default/green-goblin', target: { kind: 'user', namespace: 'default', diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index 3176b31b87..a6dda13c29 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -14,16 +14,13 @@ * limitations under the License. */ -import { - Entity, - RELATION_OWNED_BY, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; import { createCatalogPermissionRule } from './util'; /** * A catalog {@link @backstage/plugin-permission-node#PermissionRule} which * filters for entities with a specified owner. + * * @public */ export const isEntityOwner = createCatalogPermissionRule({ @@ -36,7 +33,7 @@ export const isEntityOwner = createCatalogPermissionRule({ return resource.relations .filter(relation => relation.type === RELATION_OWNED_BY) - .some(relation => claims.includes(stringifyEntityRef(relation.target))); + .some(relation => claims.includes(relation.targetRef)); }, toQuery: (claims: string[]) => ({ key: 'relations.ownedBy', diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts index cc77f6a17b..0853484408 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.test.ts @@ -21,7 +21,6 @@ import { Entity, EntityPolicies, LocationEntity, - LocationSpec, } from '@backstage/catalog-model'; import { ScmIntegrations } from '@backstage/integration'; import { @@ -29,6 +28,7 @@ import { CatalogProcessorCache, CatalogProcessorEmit, CatalogProcessorParser, + LocationSpec, results, } from '../ingestion'; import { CatalogRulesEnforcer } from '../ingestion/CatalogRules'; @@ -208,6 +208,7 @@ describe('DefaultCatalogProcessingOrchestrator', () => { const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); const processor: jest.Mocked = { + getProcessorName: jest.fn(), validateEntityKind: jest.fn(async () => true), readLocation: jest.fn(async (_l, _o, emit) => { emit(results.entity({ type: 't', target: 't' }, entity)); diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts index 105121ae2a..9649bc937b 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts @@ -18,7 +18,6 @@ import { Entity, EntityPolicy, LocationEntity, - LocationSpec, parseLocationRef, stringifyEntityRef, stringifyLocationRef, @@ -36,6 +35,7 @@ import { Logger } from 'winston'; import { CatalogProcessor, CatalogProcessorParser, + LocationSpec, } from '../ingestion/processors'; import * as results from '../ingestion/processors/results'; import { @@ -64,6 +64,7 @@ type Context = { cache: ProcessorCacheManager; }; +/** @public */ export class DefaultCatalogProcessingOrchestrator implements CatalogProcessingOrchestrator { diff --git a/plugins/catalog-backend/src/processing/ProcessorCacheManager.test.ts b/plugins/catalog-backend/src/processing/ProcessorCacheManager.test.ts index e7f9fb313b..71631b13d8 100644 --- a/plugins/catalog-backend/src/processing/ProcessorCacheManager.test.ts +++ b/plugins/catalog-backend/src/processing/ProcessorCacheManager.test.ts @@ -21,7 +21,11 @@ class MyProcessor implements CatalogProcessor { getProcessorName = () => 'my-processor'; } -class OtherProcessor implements CatalogProcessor {} +class OtherProcessor implements CatalogProcessor { + getProcessorName(): string { + return 'OtherProcessor'; + } +} describe('ProcessorCacheManager', () => { const myProcessor = new MyProcessor(); diff --git a/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts b/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts index 472d6453ce..ab95fcedb7 100644 --- a/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts +++ b/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts @@ -104,7 +104,7 @@ export class ProcessorCacheManager { key?: string, ): CatalogProcessorCache { // constructor name will be deprecated in the future when we make `getProcessorName` required in the implementation - const name = processor.getProcessorName?.() ?? processor.constructor.name; + const name = processor.getProcessorName(); const cache = this.caches.get(name); if (cache) { return key ? cache.withKey(key) : cache; diff --git a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts index 832c5b41bf..351e68882b 100644 --- a/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts +++ b/plugins/catalog-backend/src/processing/ProcessorOutputCollector.ts @@ -16,7 +16,6 @@ import { Entity, - EntityRelationSpec, ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, stringifyLocationRef, @@ -25,7 +24,7 @@ import { assertError } from '@backstage/errors'; import { Logger } from 'winston'; import { CatalogProcessorResult } from '../ingestion'; import { locationSpecToLocationEntity } from '../util/conversion'; -import { DeferredEntity } from './types'; +import { DeferredEntity, EntityRelationSpec } from './types'; import { getEntityLocationRef, getEntityOriginLocationRef, diff --git a/plugins/catalog-backend/src/processing/index.ts b/plugins/catalog-backend/src/processing/index.ts index dc8b07eceb..83f8a9f566 100644 --- a/plugins/catalog-backend/src/processing/index.ts +++ b/plugins/catalog-backend/src/processing/index.ts @@ -19,6 +19,7 @@ export type { CatalogProcessingEngine, EntityProcessingRequest, EntityProcessingResult, + EntityRelationSpec, DeferredEntity, } from './types'; export { DefaultCatalogProcessingOrchestrator } from './DefaultCatalogProcessingOrchestrator'; diff --git a/plugins/catalog-backend/src/processing/refresh.ts b/plugins/catalog-backend/src/processing/refresh.ts index c60f42ff96..3e03e3add8 100644 --- a/plugins/catalog-backend/src/processing/refresh.ts +++ b/plugins/catalog-backend/src/processing/refresh.ts @@ -16,12 +16,14 @@ /** * Function that returns the catalog refresh interval in seconds. + * @public */ export type RefreshIntervalFunction = () => number; /** * Creates a function that returns a random refresh interval between minSeconds and maxSeconds. * @returns A {@link RefreshIntervalFunction} that provides the next refresh interval + * @public */ export function createRandomRefreshInterval(options: { minSeconds: number; diff --git a/plugins/catalog-backend/src/processing/types.ts b/plugins/catalog-backend/src/processing/types.ts index 7fb1157d3b..b732df297e 100644 --- a/plugins/catalog-backend/src/processing/types.ts +++ b/plugins/catalog-backend/src/processing/types.ts @@ -14,14 +14,44 @@ * limitations under the License. */ -import { Entity, EntityRelationSpec } from '@backstage/catalog-model'; +import { Entity, EntityName } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/types'; +/** + * Holds the relation data for entities. + * + * @public + */ +export type EntityRelationSpec = { + /** + * The source entity of this relation. + */ + source: EntityName; + + /** + * The type of the relation. + */ + type: string; + + /** + * The target entity of this relation. + */ + target: EntityName; +}; + +/** + * The request to process an entity. + * @public + */ export type EntityProcessingRequest = { entity: Entity; state?: JsonObject; // Versions for multiple deployments etc }; +/** + * The result of processing an entity. + * @public + */ export type EntityProcessingResult = | { ok: true; @@ -36,15 +66,24 @@ export type EntityProcessingResult = errors: Error[]; }; +/** + * Responsible for executing the individual processing steps in order to fully process an entity. + * @public + * */ export interface CatalogProcessingOrchestrator { process(request: EntityProcessingRequest): Promise; } +/** + * Entities that are not yet processed. + * @public + * */ export type DeferredEntity = { entity: Entity; locationKey?: string; }; +/** @public */ export interface CatalogProcessingEngine { start(): Promise; stop(): Promise; diff --git a/plugins/catalog-backend/src/processing/util.ts b/plugins/catalog-backend/src/processing/util.ts index a4fa984dc1..94634f794a 100644 --- a/plugins/catalog-backend/src/processing/util.ts +++ b/plugins/catalog-backend/src/processing/util.ts @@ -19,7 +19,6 @@ import { entityEnvelopeSchemaValidator, entitySchemaValidator, LocationEntity, - LocationSpec, ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, stringifyEntityRef, @@ -28,6 +27,7 @@ import { JsonObject, JsonValue } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import path from 'path'; +import { LocationSpec } from '../ingestion'; export function isLocationEntity(entity: Entity): entity is LocationEntity { return entity.kind === 'Location'; @@ -37,7 +37,9 @@ export function getEntityLocationRef(entity: Entity): string { const ref = entity.metadata.annotations?.[ANNOTATION_LOCATION]; if (!ref) { const entityRef = stringifyEntityRef(entity); - throw new InputError(`Entity '${entityRef}' does not have a location`); + throw new InputError( + `Entity '${entityRef}' does not have the annotation ${ANNOTATION_LOCATION}`, + ); } return ref; } @@ -47,7 +49,7 @@ export function getEntityOriginLocationRef(entity: Entity): string { if (!ref) { const entityRef = stringifyEntityRef(entity); throw new InputError( - `Entity '${entityRef}' does not have an origin location`, + `Entity '${entityRef}' does not have the annotation ${ANNOTATION_ORIGIN_LOCATION}`, ); } return ref; diff --git a/plugins/catalog-backend/src/providers/DefaultLocationStore.ts b/plugins/catalog-backend/src/providers/DefaultLocationStore.ts index f93afedbaa..9d5971508f 100644 --- a/plugins/catalog-backend/src/providers/DefaultLocationStore.ts +++ b/plugins/catalog-backend/src/providers/DefaultLocationStore.ts @@ -14,7 +14,6 @@ * limitations under the License. */ -import { LocationSpec } from '@backstage/catalog-model'; import { Location } from '@backstage/catalog-client'; import { ConflictError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; @@ -23,7 +22,7 @@ import { DbLocationsRow } from '../database/tables'; import { getEntityLocationRef } from '../processing/util'; import { EntityProvider, EntityProviderConnection } from './types'; import { locationSpecToLocationEntity } from '../util/conversion'; -import { LocationStore } from '../service'; +import { LocationInput, LocationStore } from '../service'; export class DefaultLocationStore implements LocationStore, EntityProvider { private _connection: EntityProviderConnection | undefined; @@ -34,25 +33,25 @@ export class DefaultLocationStore implements LocationStore, EntityProvider { return 'DefaultLocationStore'; } - async createLocation(spec: LocationSpec): Promise { + async createLocation(input: LocationInput): Promise { const location = await this.db.transaction(async tx => { - // Attempt to find a previous location matching the spec + // Attempt to find a previous location matching the input const previousLocations = await this.locations(tx); - // TODO: when location id's are a compilation of spec target we can remove this full + // TODO: when location id's are a compilation of input target we can remove this full // lookup of locations first and just grab the by that instead. const previousLocation = previousLocations.some( - l => spec.type === l.type && spec.target === l.target, + l => input.type === l.type && input.target === l.target, ); if (previousLocation) { throw new ConflictError( - `Location ${spec.type}:${spec.target} already exists`, + `Location ${input.type}:${input.target} already exists`, ); } const inner: DbLocationsRow = { id: uuid(), - type: spec.type, - target: spec.target, + type: input.type, + target: input.target, }; await tx('locations').insert(inner); diff --git a/plugins/catalog-backend/src/providers/types.ts b/plugins/catalog-backend/src/providers/types.ts index 90c5f13215..226c021e3c 100644 --- a/plugins/catalog-backend/src/providers/types.ts +++ b/plugins/catalog-backend/src/providers/types.ts @@ -16,15 +16,36 @@ import { DeferredEntity } from '../processing'; +/** + * @public + * A 'full' mutation replaces all existing entities created by this entity provider with new ones. + * A 'delta' mutation can both add and remove entities provided by this provider. Previously provided + * entities from a 'full' mutation are not removed. + */ export type EntityProviderMutation = | { type: 'full'; entities: DeferredEntity[] } | { type: 'delta'; added: DeferredEntity[]; removed: DeferredEntity[] }; +/** + * The EntityProviderConnection is the connection between the catalog and the entity provider. + * The EntityProvider use this connection to add and remove entities from the catalog. + * @public + */ export interface EntityProviderConnection { + /** + * Applies either a full or delta update to the catalog engine. + */ applyMutation(mutation: EntityProviderMutation): Promise; } +/** + * An EntityProvider is able to provide entities to the catalog. + * See https://backstage.io/docs/features/software-catalog/life-of-an-entity for more details. + * @public + */ export interface EntityProvider { + /** Unique name provider name used internally for caching. */ getProviderName(): string; + /** Connect is called upon initialization by the catalog engine. */ connect(connection: EntityProviderConnection): Promise; } diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts index 8e72a265af..96704a6c1a 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts @@ -32,6 +32,7 @@ import { } from '@backstage/catalog-client'; import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common'; +/** @public */ export interface CatalogEntityDocument extends IndexableDocument { componentType: string; namespace: string; @@ -40,6 +41,7 @@ export interface CatalogEntityDocument extends IndexableDocument { owner: string; } +/** @public */ export class DefaultCatalogCollator implements DocumentCollator { protected discovery: PluginEndpointDiscovery; protected locationTemplate: string; diff --git a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts index 156db8e8ce..1f0e5ea644 100644 --- a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts @@ -30,6 +30,7 @@ describe('AuthorizedEntitiesCatalog', () => { entities: jest.fn(), removeEntityByUid: jest.fn(), entityAncestry: jest.fn(), + facets: jest.fn(), }; const fakePermissionApi = { authorize: jest.fn(), @@ -254,4 +255,54 @@ describe('AuthorizedEntitiesCatalog', () => { }); }); }); + + describe('facets', () => { + it('returns empty response on DENY', async () => { + fakePermissionApi.authorize.mockResolvedValue([ + { result: AuthorizeResult.DENY }, + ]); + const catalog = createCatalog(); + + expect( + await catalog.facets({ + facets: ['a'], + authorizationToken: 'abcd', + }), + ).toEqual({ + facets: { a: [] }, + }); + }); + + it('calls underlying catalog method with correct filter on CONDITIONAL', async () => { + fakePermissionApi.authorize.mockResolvedValue([ + { + result: AuthorizeResult.CONDITIONAL, + conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + }, + ]); + const catalog = createCatalog(isEntityKind); + + await catalog.facets({ facets: ['a'], authorizationToken: 'abcd' }); + + expect(fakeCatalog.facets).toHaveBeenCalledWith({ + facets: ['a'], + authorizationToken: 'abcd', + filter: { key: 'kind', values: ['b'] }, + }); + }); + + it('calls underlying catalog method on ALLOW', async () => { + fakePermissionApi.authorize.mockResolvedValue([ + { result: AuthorizeResult.ALLOW }, + ]); + const catalog = createCatalog(); + + await catalog.facets({ facets: ['a'], authorizationToken: 'abcd' }); + + expect(fakeCatalog.facets).toHaveBeenCalledWith({ + facets: ['a'], + authorizationToken: 'abcd', + }); + }); + }); }); diff --git a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.ts b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.ts index 99fc3ebc8e..2f94e23303 100644 --- a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.ts @@ -30,6 +30,8 @@ import { EntitiesRequest, EntitiesResponse, EntityAncestryResponse, + EntityFacetsRequest, + EntityFacetsResponse, EntityFilter, } from '../catalog/types'; import { basicEntityFilter } from './request/basicEntityFilter'; @@ -151,6 +153,35 @@ export class AuthorizedEntitiesCatalog implements EntitiesCatalog { }; } + async facets(request: EntityFacetsRequest): Promise { + const authorizeDecision = ( + await this.permissionApi.authorize( + [{ permission: catalogEntityReadPermission }], + { token: request?.authorizationToken }, + ) + )[0]; + + if (authorizeDecision.result === AuthorizeResult.DENY) { + return { + facets: Object.fromEntries(request.facets.map(f => [f, []])), + }; + } + + if (authorizeDecision.result === AuthorizeResult.CONDITIONAL) { + const permissionFilter: EntityFilter = this.transformConditions( + authorizeDecision.conditions, + ); + return this.entitiesCatalog.facets({ + ...request, + filter: request?.filter + ? { allOf: [permissionFilter, request.filter] } + : permissionFilter, + }); + } + + return this.entitiesCatalog.facets(request); + } + private findParents( entityRef: string, allAncestryItems: { entity: Entity; parentEntityRefs: string[] }[], diff --git a/plugins/catalog-backend/src/service/AuthorizedLocationService.ts b/plugins/catalog-backend/src/service/AuthorizedLocationService.ts index 13557102c9..077f56c737 100644 --- a/plugins/catalog-backend/src/service/AuthorizedLocationService.ts +++ b/plugins/catalog-backend/src/service/AuthorizedLocationService.ts @@ -15,7 +15,7 @@ */ import { Location } from '@backstage/catalog-client'; -import { LocationSpec, Entity } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { NotAllowedError, NotFoundError } from '@backstage/errors'; import { catalogLocationCreatePermission, @@ -26,7 +26,7 @@ import { AuthorizeResult, PermissionAuthorizer, } from '@backstage/plugin-permission-common'; -import { LocationService } from './types'; +import { LocationInput, LocationService } from './types'; export class AuthorizedLocationService implements LocationService { constructor( @@ -35,7 +35,7 @@ export class AuthorizedLocationService implements LocationService { ) {} async createLocation( - spec: LocationSpec, + spec: LocationInput, dryRun: boolean, options?: { authorizationToken?: string; diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 46c92c43da..9004cb4c56 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -97,6 +97,7 @@ import { basicEntityFilter } from './request/basicEntityFilter'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { AuthorizedLocationService } from './AuthorizedLocationService'; +/** @public */ export type CatalogEnvironment = { logger: Logger; database: PluginDatabaseManager; diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index d6d4e957f6..43e208197f 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -469,6 +469,62 @@ describe('DefaultEntitiesCatalog', () => { expect(entities.length).toBe(0); }, ); + + it.each(databases.eachSupportedId())( + 'should return both target and targetRef for entities', + async databaseId => { + const { knex } = await createDatabase(databaseId); + await addEntity( + knex, + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'one' }, + spec: {}, + relations: [{ type: 'r', targetRef: 'x:y/z' } as any], + }, + [], + ); + await addEntity( + knex, + { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'two' }, + spec: {}, + relations: [ + { + type: 'r', + target: { kind: 'x', namespace: 'y', name: 'z' }, + } as any, + ], + }, + [], + ); + const catalog = new DefaultEntitiesCatalog(knex); + + const { entities } = await catalog.entities(); + + expect( + entities.find(e => e.metadata.name === 'one')!.relations, + ).toEqual([ + { + type: 'r', + targetRef: 'x:y/z', + target: { kind: 'x', namespace: 'y', name: 'z' }, + }, + ]); + expect( + entities.find(e => e.metadata.name === 'two')!.relations, + ).toEqual([ + { + type: 'r', + targetRef: 'x:y/z', + target: { kind: 'x', namespace: 'y', name: 'z' }, + }, + ]); + }, + ); }); describe('removeEntityByUid', () => { @@ -535,4 +591,129 @@ describe('DefaultEntitiesCatalog', () => { }, ); }); + + describe('facets', () => { + it.each(databases.eachSupportedId())( + 'can filter and collect properly', + async databaseId => { + const { knex } = await createDatabase(databaseId); + + await addEntityToSearch(knex, { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'one' }, + spec: {}, + }); + await addEntityToSearch(knex, { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'two' }, + spec: {}, + }); + await addEntityToSearch(knex, { + apiVersion: 'a', + kind: 'k2', + metadata: { name: 'two' }, + spec: {}, + }); + const catalog = new DefaultEntitiesCatalog(knex); + + await expect(catalog.facets({ facets: ['kind'] })).resolves.toEqual({ + facets: { + kind: [ + { value: 'k', count: 2 }, + { value: 'k2', count: 1 }, + ], + }, + }); + }, + ); + + it.each(databases.eachSupportedId())( + 'can match on annotations and labels with dots in them', + async databaseId => { + const { knex } = await createDatabase(databaseId); + + await addEntityToSearch(knex, { + apiVersion: 'a', + kind: 'k', + metadata: { + name: 'one', + annotations: { 'a.b/c.d': 'annotation1' }, + labels: { 'e.f/g.h': 'label1' }, + }, + spec: {}, + }); + await addEntityToSearch(knex, { + apiVersion: 'a', + kind: 'k', + metadata: { + name: 'two', + annotations: { 'a.b/c.d': 'annotation2' }, + labels: { 'e.f/g.h': 'label2' }, + }, + spec: {}, + }); + const catalog = new DefaultEntitiesCatalog(knex); + + await expect( + catalog.facets({ + facets: ['metadata.annotations.a.b/c.d', 'metadata.labels.e.f/g.h'], + }), + ).resolves.toEqual({ + facets: { + 'metadata.annotations.a.b/c.d': [ + { value: 'annotation1', count: 1 }, + { value: 'annotation2', count: 1 }, + ], + 'metadata.labels.e.f/g.h': [ + { value: 'label1', count: 1 }, + { value: 'label2', count: 1 }, + ], + }, + }); + }, + ); + + it.each(databases.eachSupportedId())( + 'can match on strings in arrays', + async databaseId => { + const { knex } = await createDatabase(databaseId); + + await addEntityToSearch(knex, { + apiVersion: 'a', + kind: 'k', + metadata: { + name: 'one', + tags: ['java', 'rust'], + }, + spec: {}, + }); + await addEntityToSearch(knex, { + apiVersion: 'a', + kind: 'k', + metadata: { + name: 'two', + tags: ['java', 'node'], + }, + spec: {}, + }); + const catalog = new DefaultEntitiesCatalog(knex); + + await expect( + catalog.facets({ + facets: ['metadata.tags'], + }), + ).resolves.toEqual({ + facets: { + 'metadata.tags': expect.arrayContaining([ + { value: 'java', count: 2 }, + { value: 'rust', count: 1 }, + { value: 'node', count: 1 }, + ]), + }, + }); + }, + ); + }); }); diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index 6979f94ba7..f8b68bebc5 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -14,24 +14,31 @@ * limitations under the License. */ -import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { + Entity, + parseEntityRef, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { InputError, NotFoundError } from '@backstage/errors'; import { Knex } from 'knex'; +import lodash from 'lodash'; import { EntitiesCatalog, EntitiesRequest, EntitiesResponse, - EntityAncestryResponse, - EntityPagination, - EntityFilter, EntitiesSearchFilter, + EntityAncestryResponse, + EntityFacetsRequest, + EntityFacetsResponse, + EntityFilter, + EntityPagination, } from '../catalog/types'; import { DbFinalEntitiesRow, + DbPageInfo, DbRefreshStateReferencesRow, DbRefreshStateRow, DbSearchRow, - DbPageInfo, } from '../database/tables'; function parsePagination(input?: EntityPagination): { @@ -191,11 +198,32 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { }; } - const dbResponse = rows.map(e => JSON.parse(e.final_entity!)); + let entities: Entity[] = rows.map(e => JSON.parse(e.final_entity!)); - const entities = dbResponse.map(e => - request?.fields ? request.fields(e) : e, - ); + if (request?.fields) { + entities = entities.map(e => request.fields!(e)); + } + + // TODO(freben): This is added as a compatibility guarantee, until we can be + // sure that all adopters have re-stitched their entities so that the new + // targetRef field is present on them, and that they have stopped consuming + // the now-removed old field + for (const entity of entities) { + if (entity.relations) { + for (const relation of entity.relations) { + if (!relation.targetRef && relation.target) { + // This is the case where an old-form entity, not yet stitched with + // the updated code, was in the database + relation.targetRef = stringifyEntityRef(relation.target); + } else if (!relation.target && relation.targetRef) { + // This is the case where a new-form entity, stitched with the + // updated code, was in the database but we still want to produce + // the old data shape as well for compatibility reasons + relation.target = parseEntityRef(relation.targetRef); + } + } + } + } return { entities, @@ -295,4 +323,49 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { items, }; } + + async facets(request: EntityFacetsRequest): Promise { + const { entities } = await this.entities({ + filter: request.filter, + authorizationToken: request.authorizationToken, + }); + + const facets: EntityFacetsResponse['facets'] = {}; + + for (const facet of request.facets) { + const values = entities + .map(entity => { + // TODO(freben): Generalize this code to handle any field that may + // have dots in its key? + if (facet.startsWith('metadata.annotations.')) { + return entity.metadata.annotations?.[ + facet.substring('metadata.annotations.'.length) + ]; + } else if (facet.startsWith('metadata.labels.')) { + return entity.metadata.labels?.[ + facet.substring('metadata.labels.'.length) + ]; + } + return lodash.get(entity, facet); + }) + .flatMap(field => { + if (typeof field === 'string') { + return [field]; + } else if (Array.isArray(field)) { + return field.filter(i => typeof i === 'string'); + } + return []; + }) + .sort(); + + const counts = lodash.countBy(values, lodash.identity); + + facets[facet] = Object.entries(counts).map(([value, count]) => ({ + value, + count, + })); + } + + return { facets }; + } } diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.ts b/plugins/catalog-backend/src/service/DefaultLocationService.ts index 8bc02e24f6..c6b5eaf162 100644 --- a/plugins/catalog-backend/src/service/DefaultLocationService.ts +++ b/plugins/catalog-backend/src/service/DefaultLocationService.ts @@ -15,7 +15,6 @@ */ import { Entity, - LocationSpec, ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, stringifyEntityRef, @@ -25,7 +24,7 @@ import { CatalogProcessingOrchestrator, DeferredEntity, } from '../processing/types'; -import { LocationService, LocationStore } from './types'; +import { LocationInput, LocationService, LocationStore } from './types'; import { locationSpecToMetadataName } from '../util/conversion'; export class DefaultLocationService implements LocationService { @@ -35,13 +34,13 @@ export class DefaultLocationService implements LocationService { ) {} async createLocation( - spec: LocationSpec, + input: LocationInput, dryRun: boolean, ): Promise<{ location: Location; entities: Entity[]; exists?: boolean }> { if (dryRun) { - return this.dryRunCreateLocation(spec); + return this.dryRunCreateLocation(input); } - const location = await this.store.createLocation(spec); + const location = await this.store.createLocation(input); return { location, entities: [] }; } @@ -93,7 +92,7 @@ export class DefaultLocationService implements LocationService { } private async dryRunCreateLocation( - spec: LocationSpec, + spec: LocationInput, ): Promise<{ location: Location; entities: Entity[]; exists?: boolean }> { // Run the existence check in parallel with the processing const existsPromise = this.store diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 6ae24ecd27..8db15cb4a1 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -18,11 +18,11 @@ import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { NotFoundError } from '@backstage/errors'; import type { Location } from '@backstage/catalog-client'; -import type { Entity, LocationSpec } from '@backstage/catalog-model'; +import type { Entity } from '@backstage/catalog-model'; import express from 'express'; import request from 'supertest'; import { EntitiesCatalog } from '../catalog'; -import { LocationService, RefreshService } from './types'; +import { LocationInput, LocationService, RefreshService } from './types'; import { basicEntityFilter } from './request'; import { createRouter } from './createRouter'; import { AuthorizeResult } from '@backstage/plugin-permission-common'; @@ -40,6 +40,7 @@ describe('createRouter readonly disabled', () => { entities: jest.fn(), removeEntityByUid: jest.fn(), entityAncestry: jest.fn(), + facets: jest.fn(), }; locationService = { getLocation: jest.fn(), @@ -298,7 +299,7 @@ describe('createRouter readonly disabled', () => { const spec = { typez: 'b', target: 'c', - } as unknown as LocationSpec; + } as unknown as LocationInput; const response = await request(app) .post('/locations') @@ -310,7 +311,7 @@ describe('createRouter readonly disabled', () => { }); it('passes the body down', async () => { - const spec: LocationSpec = { + const spec: LocationInput = { type: 'b', target: 'c', }; @@ -338,7 +339,7 @@ describe('createRouter readonly disabled', () => { }); it('supports dry run', async () => { - const spec: LocationSpec = { + const spec: LocationInput = { type: 'b', target: 'c', }; @@ -394,6 +395,7 @@ describe('createRouter readonly enabled', () => { entities: jest.fn(), removeEntityByUid: jest.fn(), entityAncestry: jest.fn(), + facets: jest.fn(), }; locationService = { getLocation: jest.fn(), @@ -504,7 +506,7 @@ describe('createRouter readonly enabled', () => { describe('POST /locations', () => { it('is not allowed', async () => { - const spec: LocationSpec = { + const spec: LocationInput = { type: 'b', target: 'c', }; @@ -520,7 +522,7 @@ describe('createRouter readonly enabled', () => { }); it('supports dry run', async () => { - const spec: LocationSpec = { + const spec: LocationInput = { type: 'b', target: 'c', }; @@ -578,6 +580,7 @@ describe('NextRouter permissioning', () => { entities: jest.fn(), removeEntityByUid: jest.fn(), entityAncestry: jest.fn(), + facets: jest.fn(), }; locationService = { getLocation: jest.fn(), diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 0dce765507..56121f4155 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -32,11 +32,12 @@ import { } from './request'; import { disallowReadonlyMode, - locationSpec, + locationInput, validateRequestBody, } from './util'; import { RefreshOptions, LocationService, RefreshService } from './types'; import { z } from 'zod'; +import { parseEntityFacetParams } from './request/parseEntityFacetParams'; /** * Options used by {@link createRouter}. @@ -160,13 +161,21 @@ export async function createRouter( const response = await entitiesCatalog.entityAncestry(entityRef); res.status(200).json(response); }, - ); + ) + .get('/entity-facets', async (req, res) => { + const response = await entitiesCatalog.facets({ + filter: parseEntityFilterParams(req.query), + facets: parseEntityFacetParams(req.query), + authorizationToken: getBearerToken(req.header('authorization')), + }); + res.status(200).json(response); + }); } if (locationService) { router .post('/locations', async (req, res) => { - const location = await validateRequestBody(req, locationSpec); + const location = await validateRequestBody(req, locationInput); const dryRun = yn(req.query.dryRun, { default: false }); // when in dryRun addLocation is effectively a read operation so we don't @@ -209,9 +218,9 @@ export async function createRouter( router.post('/analyze-location', async (req, res) => { const body = await validateRequestBody( req, - z.object({ location: locationSpec }), + z.object({ location: locationInput }), ); - const schema = z.object({ location: locationSpec }); + const schema = z.object({ location: locationInput }); const output = await locationAnalyzer.analyzeLocation(schema.parse(body)); res.status(200).json(output); }); diff --git a/plugins/catalog-backend/src/service/index.ts b/plugins/catalog-backend/src/service/index.ts index fd1b31b9d1..e1c7f54475 100644 --- a/plugins/catalog-backend/src/service/index.ts +++ b/plugins/catalog-backend/src/service/index.ts @@ -19,6 +19,7 @@ export type { RefreshService, RefreshOptions, LocationStore, + LocationInput, } from './types'; export { createRouter } from './createRouter'; export type { RouterOptions } from './createRouter'; diff --git a/plugins/catalog-backend/src/service/request/parseEntityFacetParams.ts b/plugins/catalog-backend/src/service/request/parseEntityFacetParams.ts new file mode 100644 index 0000000000..00493f068f --- /dev/null +++ b/plugins/catalog-backend/src/service/request/parseEntityFacetParams.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { parseStringsParam } from './common'; + +/** + * Parses the facets part of a facet query, like + * /entity-facets?filter=metadata.namespace=default,kind=Component&facet=metadata.namespace + */ +export function parseEntityFacetParams( + params: Record, +): string[] { + // Each facet string is on the form a.b.c + const facetStrings = parseStringsParam(params.facet, 'facet'); + if (facetStrings) { + const filtered = facetStrings.filter(Boolean); + if (filtered.length) { + return filtered; + } + } + + throw new InputError('Missing facet parameter'); +} diff --git a/plugins/catalog-backend/src/service/types.ts b/plugins/catalog-backend/src/service/types.ts index c923949a04..02268ab63c 100644 --- a/plugins/catalog-backend/src/service/types.ts +++ b/plugins/catalog-backend/src/service/types.ts @@ -14,12 +14,28 @@ * limitations under the License. */ -import { Entity, LocationSpec } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { Location } from '@backstage/catalog-client'; +/** + * Holds the information required to create a new location in the catalog location store. + * + * @public + */ +export interface LocationInput { + type: string; + target: string; + /** @deprecated This field is ignored and will be removed */ + presence?: 'optional' | 'required'; +} + +/** + * The location service manages entity locations. + * @public + */ export interface LocationService { createLocation( - spec: LocationSpec, + location: LocationInput, dryRun: boolean, options?: { authorizationToken?: string; @@ -59,8 +75,11 @@ export interface RefreshService { refresh(options: RefreshOptions): Promise; } +/** + * Interacts with the database to manage locations. + * @public */ export interface LocationStore { - createLocation(spec: LocationSpec): Promise; + createLocation(location: LocationInput): Promise; listLocations(): Promise; getLocation(id: string): Promise; deleteLocation(id: string): Promise; diff --git a/plugins/catalog-backend/src/service/util.ts b/plugins/catalog-backend/src/service/util.ts index 09f1c2d8cf..9a914aadcb 100644 --- a/plugins/catalog-backend/src/service/util.ts +++ b/plugins/catalog-backend/src/service/util.ts @@ -40,7 +40,7 @@ export async function requireRequestBody(req: Request): Promise { return body; } -export const locationSpec = z +export const locationInput = z .object({ type: z.string(), target: z.string(), diff --git a/plugins/catalog-backend/src/stitching/Stitcher.test.ts b/plugins/catalog-backend/src/stitching/Stitcher.test.ts index be57ff7083..9df4bedd60 100644 --- a/plugins/catalog-backend/src/stitching/Stitcher.test.ts +++ b/plugins/catalog-backend/src/stitching/Stitcher.test.ts @@ -93,6 +93,7 @@ describe('Stitcher', () => { relations: [ { type: 'looksAt', + targetRef: 'k:ns/other', target: { kind: 'k', namespace: 'ns', @@ -159,6 +160,7 @@ describe('Stitcher', () => { relations: expect.arrayContaining([ { type: 'looksAt', + targetRef: 'k:ns/other', target: { kind: 'k', namespace: 'ns', @@ -167,6 +169,7 @@ describe('Stitcher', () => { }, { type: 'looksAt', + targetRef: 'k:ns/third', target: { kind: 'k', namespace: 'ns', diff --git a/plugins/catalog-backend/src/stitching/Stitcher.ts b/plugins/catalog-backend/src/stitching/Stitcher.ts index 9ec028ed35..37b86fef46 100644 --- a/plugins/catalog-backend/src/stitching/Stitcher.ts +++ b/plugins/catalog-backend/src/stitching/Stitcher.ts @@ -18,6 +18,7 @@ import { ENTITY_STATUS_CATALOG_PROCESSING_TYPE } from '@backstage/catalog-client import { AlphaEntity, parseEntityRef, + EntityRelation, EntityStatusItem, } from '@backstage/catalog-model'; import { SerializedError, stringifyError } from '@backstage/errors'; @@ -183,9 +184,11 @@ export class Stitcher { ); entity.relations = uniqueRelationRows .filter(row => row.relationType /* exclude null row, if relevant */) - .map(row => ({ + .map(row => ({ type: row.relationType!, + // TODO(freben): This field is deprecated and should be removed in a future release target: parseEntityRef(row.relationTarget!), + targetRef: row.relationTarget!, })); if (statusItems.length) { entity.status = { @@ -210,7 +213,7 @@ export class Stitcher { } // This may throw if the entity is invalid, so we call it before - // the final_entites write, even though we may end up not needing + // the final_entities write, even though we may end up not needing // to write the search index. const searchEntries = buildEntitySearch(entityId, entity); diff --git a/plugins/catalog-backend/src/stitching/buildEntitySearch.test.ts b/plugins/catalog-backend/src/stitching/buildEntitySearch.test.ts index 173538f18f..52e3392676 100644 --- a/plugins/catalog-backend/src/stitching/buildEntitySearch.test.ts +++ b/plugins/catalog-backend/src/stitching/buildEntitySearch.test.ts @@ -141,8 +141,16 @@ describe('buildEntitySearch', () => { it('adds relations', () => { const input: Entity = { relations: [ - { type: 't1', target: { kind: 'k', namespace: 'ns', name: 'a' } }, - { type: 't2', target: { kind: 'k', namespace: 'ns', name: 'b' } }, + { + type: 't1', + targetRef: 'k:ns/a', + target: { kind: 'k', namespace: 'ns', name: 'a' }, + }, + { + type: 't2', + targetRef: 'k:ns/b', + target: { kind: 'k', namespace: 'ns', name: 'b' }, + }, ], apiVersion: 'a', kind: 'b', @@ -182,8 +190,16 @@ describe('buildEntitySearch', () => { expect(() => buildEntitySearch('eid', { relations: [ - { type: 'dup', target: { kind: 'k', namespace: 'ns', name: 'a' } }, - { type: 'DUP', target: { kind: 'k', namespace: 'ns', name: 'b' } }, + { + type: 'dup', + targetRef: 'k:ns/a', + target: { kind: 'k', namespace: 'ns', name: 'a' }, + }, + { + type: 'DUP', + targetRef: 'k:ns/b', + target: { kind: 'k', namespace: 'ns', name: 'b' }, + }, ], apiVersion: 'a', kind: 'b', diff --git a/plugins/catalog-backend/src/stitching/buildEntitySearch.ts b/plugins/catalog-backend/src/stitching/buildEntitySearch.ts index d0506e8998..cd8a3735de 100644 --- a/plugins/catalog-backend/src/stitching/buildEntitySearch.ts +++ b/plugins/catalog-backend/src/stitching/buildEntitySearch.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - Entity, - DEFAULT_NAMESPACE, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { Entity, DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { InputError } from '@backstage/errors'; import { DbSearchRow } from '../database/tables'; @@ -181,7 +177,7 @@ export function buildEntitySearch( for (const relation of entity.relations ?? []) { raw.push({ key: `relations.${relation.type}`, - value: stringifyEntityRef(relation.target), + value: relation.targetRef, }); } diff --git a/plugins/catalog-backend/src/util/RecursivePartial.ts b/plugins/catalog-backend/src/util/RecursivePartial.ts index 502f4464c1..92e1ab9296 100644 --- a/plugins/catalog-backend/src/util/RecursivePartial.ts +++ b/plugins/catalog-backend/src/util/RecursivePartial.ts @@ -16,6 +16,7 @@ /** * Makes all keys of an entire hierarchy optional. + * @public */ export type RecursivePartial = { [P in keyof T]?: T[P] extends (infer U)[] diff --git a/plugins/catalog-backend/src/util/conversion.ts b/plugins/catalog-backend/src/util/conversion.ts index 9dd5ce7986..75ed4f5e96 100644 --- a/plugins/catalog-backend/src/util/conversion.ts +++ b/plugins/catalog-backend/src/util/conversion.ts @@ -17,13 +17,13 @@ import { Entity, LocationEntityV1alpha1, - LocationSpec, ANNOTATION_LOCATION, ANNOTATION_ORIGIN_LOCATION, stringifyEntityRef, stringifyLocationRef, } from '@backstage/catalog-model'; import { createHash } from 'crypto'; +import { LocationSpec } from '../ingestion'; export function locationSpecToMetadataName(location: LocationSpec) { const hash = createHash('sha1') diff --git a/plugins/catalog-backend/src/util/index.ts b/plugins/catalog-backend/src/util/index.ts index 2e592fb8df..13a9c84b0f 100644 --- a/plugins/catalog-backend/src/util/index.ts +++ b/plugins/catalog-backend/src/util/index.ts @@ -16,4 +16,3 @@ export * from './runPeriodically'; export * from './RecursivePartial'; -export * from './timing'; diff --git a/plugins/catalog-backend/src/util/runPeriodically.ts b/plugins/catalog-backend/src/util/runPeriodically.ts index 2f3104e221..361fb33b2f 100644 --- a/plugins/catalog-backend/src/util/runPeriodically.ts +++ b/plugins/catalog-backend/src/util/runPeriodically.ts @@ -23,6 +23,8 @@ * @param delayMs - The delay between a completed function invocation and the * next. * @returns A function that, when called, stops the invocation loop. + * @deprecated use \@backstage/backend-tasks package instead. + * @public */ export function runPeriodically(fn: () => any, delayMs: number): () => void { let cancel: () => void; diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index a236e55857..337bd6854c 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-catalog-common +## 0.1.4 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/plugin-permission-common@0.5.1 + +## 0.1.3 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/plugin-permission-common@0.5.0 + ## 0.1.2 ### Patch Changes diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 4b94809ed5..9a52d22798 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-common", "description": "Common functionalities for the catalog plugin", - "version": "0.1.2", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -12,6 +12,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,18 +25,18 @@ "backstage" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test --passWithNoTests", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/plugin-permission-common": "^0.4.0" + "@backstage/plugin-permission-common": "^0.5.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0" + "@backstage/cli": "^0.14.0" }, "files": [ "dist" diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 0bb6e7224a..5ce044aa14 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-catalog-graph +## 0.2.12 + +### Patch Changes + +- a8331830f1: Remove use of deprecated `useEntityKinds` hook. +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.11 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 538ca90790: Use updated type names from `@backstage/catalog-client` +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/catalog-client@0.7.0 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.2.10 ### Patch Changes diff --git a/plugins/catalog-graph/dev/index.tsx b/plugins/catalog-graph/dev/index.tsx index 3ff9285a81..62d5eccef3 100644 --- a/plugins/catalog-graph/dev/index.tsx +++ b/plugins/catalog-graph/dev/index.tsx @@ -42,6 +42,7 @@ import { catalogGraphPlugin, EntityCatalogGraphCard, } from '../src'; +import { CatalogEntityPage } from '@backstage/plugin-catalog'; type DataRelation = [string, string, string]; type DataEntity = [string, string, DataRelation[]]; @@ -118,6 +119,11 @@ const entities = ( }, relations: relations.map(([type, k, n]) => ({ target: { kind: k, name: n, namespace: DEFAULT_NAMESPACE }, + targetRef: stringifyEntityRef({ + kind: k, + namespace: DEFAULT_NAMESPACE, + name: n, + }), type, })), }; @@ -162,6 +168,12 @@ createDevApp() ), }) .addPage({ + path: '/catalog-graph', element: , }) + .addPage({ + path: '/catalog/:kind/:namespace/:name', + element: , + title: 'MockComponent', + }) .render(); diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 541d21f319..0d85123646 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.2.10", + "version": "0.2.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,23 +10,26 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -42,10 +45,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/plugin-catalog": "^0.9.0", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx index 7209a516a5..7cebdee692 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx @@ -60,12 +60,12 @@ describe('', () => { getEntityByName: jest.fn(async _ => ({ ...entity, relations: [] })), removeEntityByUid: jest.fn(), getLocationById: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), addLocation: jest.fn(), removeLocationById: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; apis = TestApiRegistry.from([catalogApiRef, catalog]); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx index 4c7a64a7ce..2f686f8f62 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx @@ -58,6 +58,7 @@ describe('', () => { relations: [ { type: RELATION_PART_OF, + targetRef: 'b:d/e', target: { kind: 'b', namespace: 'd', @@ -76,6 +77,7 @@ describe('', () => { relations: [ { type: RELATION_HAS_PART, + targetRef: 'b:d/c', target: { kind: 'b', namespace: 'd', @@ -89,12 +91,12 @@ describe('', () => { getEntityByName: jest.fn(async n => (n.name === 'e' ? entityE : entityC)), removeEntityByUid: jest.fn(), getLocationById: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), addLocation: jest.fn(), removeLocationById: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; wrapper = ( diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx index c98c77c975..5ac5bc5a5f 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.test.tsx @@ -13,63 +13,63 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useApi as useApiMocked } from '@backstage/core-plugin-api'; -import { useEntityKinds as useEntityKindsMocked } from '@backstage/plugin-catalog-react'; -import { render, waitFor } from '@testing-library/react'; +import { GetEntityFacetsResponse } from '@backstage/catalog-client'; +import { ApiProvider } from '@backstage/core-app-api'; +import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import React from 'react'; import { SelectedKindsFilter } from './SelectedKindsFilter'; -jest.mock('@backstage/core-plugin-api'); -jest.mock('@backstage/plugin-catalog-react'); - -const useApi = useApiMocked as jest.Mock>; -const useEntityKinds = useEntityKindsMocked as jest.Mock< - ReturnType ->; +const catalogApi = { + getEntityFacets: jest.fn().mockResolvedValue({ + facets: { + kind: [ + { value: 'Component', count: 2 }, + { value: 'System', count: 1 }, + { value: 'API', count: 1 }, + { value: 'Resource', count: 1 }, + ], + }, + } as GetEntityFacetsResponse), +}; +const apis = TestApiRegistry.from( + [catalogApiRef, catalogApi], + [alertApiRef, {} as AlertApi], +); describe('', () => { - beforeEach(() => { - useApi.mockReturnValue({}); - useEntityKinds.mockReturnValue({ - loading: false, - kinds: ['API', 'Component', 'System', 'Domain', 'Resource'], - error: undefined, - }); + it('should not explode while loading', async () => { + const rendered = await renderWithEffects( + + {}} /> + , + ); + expect(rendered.baseElement).toBeInTheDocument(); }); - afterEach(() => jest.resetAllMocks()); - - test('should not explode while loading', () => { - useEntityKinds.mockReturnValue({ - loading: true, - kinds: undefined, - error: undefined, - }); - const { baseElement } = render( - {}} />, + it('should render current value', async () => { + const rendered = await renderWithEffects( + + {}} /> + , ); - expect(baseElement).toBeInTheDocument(); + expect(rendered.getByText('API')).toBeInTheDocument(); + expect(rendered.getByText('Component')).toBeInTheDocument(); }); - test('should render current value', () => { - const { getByText } = render( - {}} />, - ); - - expect(getByText('API')).toBeInTheDocument(); - expect(getByText('Component')).toBeInTheDocument(); - }); - - test('should select value', async () => { + it('should select value', async () => { const onChange = jest.fn(); - const { getByText, getByLabelText } = render( - , + const { getByLabelText, getByText } = await renderWithEffects( + + + , ); userEvent.click(getByLabelText('Open')); - await waitFor(() => expect(getByText('System')).toBeInTheDocument()); userEvent.click(getByText('System')); @@ -79,15 +79,16 @@ describe('', () => { }); }); - test('should return undefined if all values are selected', async () => { + it('should return undefined if all values are selected', async () => { const onChange = jest.fn(); - const { getByText, getByLabelText } = render( - , + const { getByLabelText, getByText } = await renderWithEffects( + + + , ); - userEvent.click(getByLabelText('Open')); await waitFor(() => expect(getByText('Resource')).toBeInTheDocument()); @@ -99,10 +100,12 @@ describe('', () => { }); }); - test('should return all values when cleared', async () => { + it('should return all values when cleared', async () => { const onChange = jest.fn(); - const { getByRole } = render( - , + const { getByRole } = await renderWithEffects( + + + , ); userEvent.click(getByRole('combobox')); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx index 87ba8e77bf..316f83da62 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/SelectedKindsFilter.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import { alertApiRef, useApi } from '@backstage/core-plugin-api'; -import { useEntityKinds } from '@backstage/plugin-catalog-react'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { Box, Checkbox, @@ -28,6 +28,7 @@ import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { Autocomplete } from '@material-ui/lab'; import React, { useCallback, useEffect, useMemo } from 'react'; +import useAsync from 'react-use/lib/useAsync'; const useStyles = makeStyles({ formControl: { @@ -43,7 +44,13 @@ export type Props = { export const SelectedKindsFilter = ({ value, onChange }: Props) => { const classes = useStyles(); const alertApi = useApi(alertApiRef); - const { error, kinds } = useEntityKinds(); + const catalogApi = useApi(catalogApiRef); + + const { error, value: kinds } = useAsync(async () => { + return await catalogApi + .getEntityFacets({ facets: ['kind'] }) + .then(response => response.facets.kind?.map(f => f.value).sort() || []); + }); useEffect(() => { if (error) { diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx index be0c4dc5e5..36dc816444 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx @@ -54,6 +54,7 @@ describe('', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -62,6 +63,7 @@ describe('', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_HAS_PART, }, ], @@ -80,6 +82,7 @@ describe('', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_OWNED_BY, }, { @@ -88,6 +91,7 @@ describe('', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_OWNED_BY, }, ], @@ -106,6 +110,7 @@ describe('', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_PART_OF, }, { @@ -114,6 +119,7 @@ describe('', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -122,6 +128,7 @@ describe('', () => { name: 'c2', namespace: 'd', }, + targetRef: 'b:d/c2', type: RELATION_HAS_PART, }, ], @@ -140,6 +147,7 @@ describe('', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_PART_OF, }, ], @@ -150,12 +158,12 @@ describe('', () => { getEntityByName: jest.fn(async n => entities[stringifyEntityRef(n)]), removeEntityByUid: jest.fn(), getLocationById: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), addLocation: jest.fn(), removeLocationById: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; Wrapper = ({ children }) => ( @@ -225,6 +233,7 @@ describe('', () => { name: 'some-component', namespace: 'default', }, + targetRef: 'component:default/some-component', type: RELATION_OWNER_OF, }, ], diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts index bf6fc9ee3f..7e0cd411ce 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.test.ts @@ -49,6 +49,7 @@ describe('useEntityRelationGraph', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -57,6 +58,7 @@ describe('useEntityRelationGraph', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_HAS_PART, }, ], @@ -75,6 +77,7 @@ describe('useEntityRelationGraph', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_OWNED_BY, }, { @@ -83,6 +86,7 @@ describe('useEntityRelationGraph', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_OWNED_BY, }, ], @@ -101,6 +105,7 @@ describe('useEntityRelationGraph', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_PART_OF, }, { @@ -109,6 +114,7 @@ describe('useEntityRelationGraph', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -117,6 +123,7 @@ describe('useEntityRelationGraph', () => { name: 'c2', namespace: 'd', }, + targetRef: 'b:d/c2', type: RELATION_HAS_PART, }, ], @@ -135,6 +142,7 @@ describe('useEntityRelationGraph', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_PART_OF, }, ], diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts index e0d393168b..957f35dc25 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationGraph.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { useEffect } from 'react'; import { useEntityStore } from './useEntityStore'; @@ -64,13 +64,15 @@ export function useEntityRelationGraph({ if ( (!relations || relations.includes(rel.type)) && (!kinds || - kinds.includes(rel.target.kind.toLocaleLowerCase('en-US'))) + kinds.some(kind => + rel.targetRef.startsWith( + `${kind.toLocaleLowerCase('en-US')}:`, + ), + )) ) { - const relationEntityRef = stringifyEntityRef(rel.target); - - if (!processedEntityRefs.has(relationEntityRef)) { - nextDepthRefQueue.push(relationEntityRef); - expectedEntities.add(relationEntityRef); + if (!processedEntityRefs.has(rel.targetRef)) { + nextDepthRefQueue.push(rel.targetRef); + expectedEntities.add(rel.targetRef); } } } diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts index 37073352d6..4715cb106a 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.test.ts @@ -49,6 +49,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -57,6 +58,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_HAS_PART, }, ], @@ -75,6 +77,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_OWNED_BY, }, { @@ -83,6 +86,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_OWNED_BY, }, ], @@ -101,6 +105,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c', namespace: 'd', }, + targetRef: 'b:d/c', type: RELATION_PART_OF, }, { @@ -109,6 +114,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'a1', namespace: 'd', }, + targetRef: 'k:d/a1', type: RELATION_OWNER_OF, }, { @@ -117,6 +123,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c2', namespace: 'd', }, + targetRef: 'b:d/c2', type: RELATION_HAS_PART, }, ], @@ -135,6 +142,7 @@ describe('useEntityRelationNodesAndEdges', () => { name: 'c1', namespace: 'd', }, + targetRef: 'b:d/c1', type: RELATION_PART_OF, }, ], diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts index b272abc654..a1c3416d4f 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityRelationNodesAndEdges.ts @@ -13,10 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - DEFAULT_NAMESPACE, - stringifyEntityRef, -} from '@backstage/catalog-model'; +import { DEFAULT_NAMESPACE } from '@backstage/catalog-model'; import { MouseEvent, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import { RelationPairs, ALL_RELATION_PAIRS } from './relations'; @@ -100,11 +97,9 @@ export function useEntityRelationNodesAndEdges({ if (entity) { entity?.relations?.forEach(rel => { - const targetRef = stringifyEntityRef(rel.target); - // Check if the related entity should be displayed, if not, ignore // the relation too - if (!entities[targetRef]) { + if (!entities[rel.targetRef]) { return; } @@ -114,12 +109,14 @@ export function useEntityRelationNodesAndEdges({ if ( kinds && - !kinds.includes(rel.target.kind.toLocaleLowerCase('en-US')) + !kinds.some(kind => + rel.targetRef.startsWith(`${kind.toLocaleLowerCase('en-US')}:`), + ) ) { return; } - if (!unidirectional || !visitedNodes.has(targetRef)) { + if (!unidirectional || !visitedNodes.has(rel.targetRef)) { if (mergeRelations) { const pair = relationPairs.find( ([l, r]) => l === rel.type || r === rel.type, @@ -127,24 +124,24 @@ export function useEntityRelationNodesAndEdges({ const [left] = pair; edges.push({ - from: left === rel.type ? entityRef : targetRef, - to: left === rel.type ? targetRef : entityRef, + from: left === rel.type ? entityRef : rel.targetRef, + to: left === rel.type ? rel.targetRef : entityRef, relations: pair, label: 'visible', }); } else { edges.push({ from: entityRef, - to: targetRef, + to: rel.targetRef, relations: [rel.type], label: 'visible', }); } } - if (!visitedNodes.has(targetRef)) { - nodeQueue.push(targetRef); - visitedNodes.add(targetRef); + if (!visitedNodes.has(rel.targetRef)) { + nodeQueue.push(rel.targetRef); + visitedNodes.add(rel.targetRef); } }); } diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.test.ts b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.test.ts index a60750bab5..a5dc82eab9 100644 --- a/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.test.ts +++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/useEntityStore.test.ts @@ -32,12 +32,12 @@ describe('useEntityStore', () => { getEntityByName: jest.fn(), removeEntityByUid: jest.fn(), getLocationById: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), addLocation: jest.fn(), removeLocationById: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; useApi.mockReturnValue(catalogApi); diff --git a/plugins/catalog-graphql/CHANGELOG.md b/plugins/catalog-graphql/CHANGELOG.md index f2ad5d14b4..cd1f255e23 100644 --- a/plugins/catalog-graphql/CHANGELOG.md +++ b/plugins/catalog-graphql/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-catalog-graphql +## 0.3.4 + +### Patch Changes + +- 68913af4ff: chore(deps): bump `graphql-modules` from 1.4.4 to 2.0.0 +- Updated dependencies + - @backstage/catalog-model@0.11.0 + +## 0.3.3 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/types@0.1.3 + +## 0.3.2 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.3.1 ### Patch Changes diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index b757790275..6ecf7eb512 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-graphql", "description": "An experimental Backstage catalog GraphQL module", - "version": "0.3.1", + "version": "0.3.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,20 +24,20 @@ "graphql" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", "generate:types": "graphql-codegen", - "clean": "backstage-cli clean" + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/types": "^0.1.1", - "graphql-modules": "^1.0.0", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/types": "^0.1.3", + "graphql-modules": "^2.0.0", "apollo-server": "^3.0.0", "graphql": "^16.0.0", "graphql-tag": "^2.11.0", @@ -43,8 +46,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", - "@backstage/test-utils": "^0.2.4-next.0", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@graphql-codegen/cli": "^2.3.1", "@graphql-codegen/typescript": "^2.4.2", "@graphql-codegen/typescript-resolvers": "^2.4.3", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 8c86fd4ca2..b55f8b34fa 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-catalog-import +## 0.8.3 + +### Patch Changes + +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration@0.7.5 + - @backstage/integration-react@0.1.23 + +## 0.8.2 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- Updated dependencies + - @backstage/catalog-client@0.7.0 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/integration-react@0.1.22 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + ## 0.8.1 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 7ce6f8524f..800b747e8a 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-import", "description": "A Backstage plugin the helps you import entities into your catalog", - "version": "0.8.1", + "version": "0.8.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,25 +24,25 @@ "backstage" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/config": "^0.1.13", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/integration-react": "^0.1.21", - "@backstage/plugin-catalog-react": "^0.6.14", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/config": "^0.1.15", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/integration-react": "^0.1.23", + "@backstage/plugin-catalog-react": "^0.7.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -57,10 +60,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-import/src/api/CatalogImportClient.test.ts b/plugins/catalog-import/src/api/CatalogImportClient.test.ts index fa63825af1..f689c0a883 100644 --- a/plugins/catalog-import/src/api/CatalogImportClient.test.ts +++ b/plugins/catalog-import/src/api/CatalogImportClient.test.ts @@ -94,12 +94,12 @@ describe('CatalogImportClient', () => { addLocation: jest.fn(), removeLocationById: jest.fn(), getEntityByName: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), getLocationById: jest.fn(), removeEntityByUid: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; let catalogImportClient: CatalogImportClient; diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx index 8d26657bb3..fc9b35136a 100644 --- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx +++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx @@ -39,13 +39,13 @@ describe('', () => { getEntities: jest.fn(), addLocation: jest.fn(), getEntityByName: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), getLocationById: jest.fn(), removeLocationById: jest.fn(), removeEntityByUid: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; const errorApi: jest.Mocked = { diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index 65fd275389..ac611032e5 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/plugin-catalog-react +## 0.7.0 + +### Minor Changes + +- 3334ad47d4: Removed the deprecated `EntityContext` which have been replaced by `useEntity`, `EntityProvider` and `AsyncEntityProvider`. +- e2e0b6625c: Improved API documentation. + + **BREAKING**: The individual table column factories (e.g. `createEntityRefColumn`) are now no longer available directly, but only through `EntityTable.columns`. + +- c4276915c0: **BREAKING**: Deleted the deprecated `loadIdentityOwnerRefs` function which is replaced by `ownershipEntityRefs` from `identityApi.getBackstageIdentity()`. + + Deprecated the `loadCatalogOwnerRefs` hook as membership references should be added as `ent` inside `claims` sections of the `SignInResolver` when issuing tokens. See https://backstage.io/docs/auth/identity-resolver for more details on how to prepare your `SignInResolver` if not done already. Usage of the `loadCatalogOwnerRefs` hook should be replaced by `ownershipEntityRefs` from `identityApi.getBackstageIdentity()` instead. + +### Patch Changes + +- a8331830f1: Deprecated the `useEntityKinds` hook due to low usage and utility value. +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- b776ce5aab: Deprecated the `useEntityListProvider` hook which is now renamed to `useEntityList` +- b3ef24038b: Deprecated `reduceCatalogFilters` and `reduceEntityFilters` as these helper functions are used internally and provides low external value. +- 2d339b5f2c: Deprecated `useEntityFromUrl` and the `useEntityCompoundName` hooks as these have very low utility value. +- 96b8ae9a9e: Deprecated the `EntityTypeReturn` type and inlined the return type to `useEntityTypeFilter` as the type and function name does not align +- d4f67fa728: Deprecated the `useOwnedEntities` hook which is replaced by the IdentityAPI. + Deprecated the `useOwnUser` hook due to low external value. +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration@0.7.5 + - @backstage/plugin-permission-react@0.3.2 + +## 0.6.15 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 538ca90790: Use updated type names from `@backstage/catalog-client` +- edbc03814a: Replace usage of `serializeEntityRef` with `stringifyEntityRef` +- 244d24ebc4: Import `Location` from the `@backstage/catalog-client` package. +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- 216725b434: Updated to use new names for `parseLocationRef` and `stringifyLocationRef` +- e72d371296: Use `TemplateEntityV1beta2` from `@backstage/plugin-scaffolder-common` instead + of `@backstage/catalog-model`. +- 98d1aa1ea1: Fix CatalogPage showing all components when owned filter was pre-selected +- 27eccab216: Replaces use of deprecated catalog-model constants. +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/catalog-client@0.7.0 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/plugin-permission-react@0.3.1 + - @backstage/catalog-model@0.10.0 + - @backstage/types@0.1.2 + - @backstage/version-bridge@0.1.2 + ## 0.6.14 ### Patch Changes diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md index 5ab3f20d76..f117e8cd5c 100644 --- a/plugins/catalog-react/api-report.md +++ b/plugins/catalog-react/api-report.md @@ -11,12 +11,10 @@ import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client'; import { CatalogApi } from '@backstage/catalog-client'; import { ComponentEntity } from '@backstage/catalog-model'; import { ComponentProps } from 'react'; -import { Context } from 'react'; import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; import { GetEntitiesResponse } from '@backstage/catalog-client'; import { IconButton } from '@material-ui/core'; -import { IdentityApi } from '@backstage/core-plugin-api'; import { LinkProps } from '@backstage/core-components'; import { Observable } from '@backstage/types'; import { Overrides } from '@material-ui/core/styles/overrides'; @@ -66,9 +64,7 @@ export { CATALOG_FILTER_EXISTS }; export { CatalogApi }; -// Warning: (ae-missing-release-tag) "catalogApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const catalogApiRef: ApiRef; // @public (undocumented) @@ -100,51 +96,37 @@ export type CatalogReactUserListPickerClassKey = | 'menuItem' | 'groupWrapper'; -// Warning: (ae-missing-release-tag) "catalogRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public @deprecated (undocumented) export const catalogRouteRef: RouteRef; // @public (undocumented) -function createDomainColumn(): TableColumn; +export const columnFactories: Readonly<{ + createEntityRefColumn(options: { + defaultKind?: string; + }): TableColumn; + createEntityRelationColumn({ + title, + relation, + defaultKind, + filter: entityFilter, + }: { + title: string; + relation: string; + defaultKind?: string | undefined; + filter?: + | { + kind: string; + } + | undefined; + }): TableColumn; + createOwnerColumn(): TableColumn; + createDomainColumn(): TableColumn; + createSystemColumn(): TableColumn; + createMetadataDescriptionColumn(): TableColumn; + createSpecLifecycleColumn(): TableColumn; + createSpecTypeColumn(): TableColumn; +}>; -// @public (undocumented) -function createEntityRefColumn(options: { - defaultKind?: string; -}): TableColumn; - -// @public (undocumented) -function createEntityRelationColumn({ - title, - relation, - defaultKind, - filter: entityFilter, -}: { - title: string; - relation: string; - defaultKind?: string; - filter?: { - kind: string; - }; -}): TableColumn; - -// @public (undocumented) -function createMetadataDescriptionColumn(): TableColumn; - -// @public (undocumented) -function createOwnerColumn(): TableColumn; - -// @public (undocumented) -function createSpecLifecycleColumn(): TableColumn; - -// @public (undocumented) -function createSpecTypeColumn(): TableColumn; - -// @public (undocumented) -function createSystemColumn(): TableColumn; - -// Warning: (ae-missing-release-tag) "DefaultEntityFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type DefaultEntityFilters = { kind?: EntityKindFilter; @@ -167,13 +149,6 @@ export class DefaultStarredEntitiesApi implements StarredEntitiesApi { toggleStarred(entityRef: string): Promise; } -// Warning: (ae-forgotten-export) The symbol "EntityLoadingStatus" needs to be exported by the entry point index.d.ts -// -// @public @deprecated (undocumented) -export const EntityContext: Context; - -// Warning: (ae-missing-release-tag) "EntityFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type EntityFilter = { getCatalogFilters?: () => Record< @@ -184,9 +159,7 @@ export type EntityFilter = { toQueryValue?: () => string | string[]; }; -// Warning: (ae-missing-release-tag) "EntityKindFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class EntityKindFilter implements EntityFilter { constructor(value: string); // (undocumented) @@ -210,9 +183,7 @@ export interface EntityKindPickerProps { initialFilter?: string; } -// Warning: (ae-missing-release-tag) "EntityLifecycleFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class EntityLifecycleFilter implements EntityFilter { constructor(values: string[]); // (undocumented) @@ -226,24 +197,42 @@ export class EntityLifecycleFilter implements EntityFilter { // @public (undocumented) export const EntityLifecyclePicker: () => JSX.Element | null; -// Warning: (ae-forgotten-export) The symbol "EntityListContextProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "EntityListContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const EntityListContext: React_2.Context< EntityListContextProps | undefined >; -// Warning: (ae-missing-release-tag) "EntityListProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) +export type EntityListContextProps< + EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, +> = { + filters: EntityFilters; + entities: Entity[]; + backendEntities: Entity[]; + updateFilters: ( + filters: + | Partial + | ((prevFilters: EntityFilters) => Partial), + ) => void; + queryParameters: Partial>; + loading: boolean; + error?: Error; +}; + +// @public export const EntityListProvider: ({ children, }: PropsWithChildren<{}>) => JSX.Element; -// Warning: (ae-missing-release-tag) "EntityOwnerFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) +export type EntityLoadingStatus = { + entity?: Entity; + loading: boolean; + error?: Error; + refresh?: VoidFunction; +}; + +// @public export class EntityOwnerFilter implements EntityFilter { constructor(values: string[]); // (undocumented) @@ -276,7 +265,7 @@ export const EntityRefLink: (props: EntityRefLinkProps) => JSX.Element; // @public export type EntityRefLinkProps = { - entityRef: Entity | EntityName; + entityRef: Entity | EntityName | string; defaultKind?: string; title?: string; children?: React_2.ReactNode; @@ -295,8 +284,6 @@ export type EntityRefLinksProps = { defaultKind?: string; } & Omit; -// Warning: (ae-missing-release-tag) "entityRoute" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public @deprecated (undocumented) export const entityRoute: RouteRef<{ name: string; @@ -304,17 +291,13 @@ export const entityRoute: RouteRef<{ namespace: string; }>; -// Warning: (ae-missing-release-tag) "entityRouteParams" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function entityRouteParams(entity: Entity): { readonly kind: string; readonly namespace: string; readonly name: string; }; -// Warning: (ae-missing-release-tag) "entityRouteRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const entityRouteRef: RouteRef<{ name: string; @@ -322,38 +305,48 @@ export const entityRouteRef: RouteRef<{ namespace: string; }>; -// @public (undocumented) +// @public export const EntitySearchBar: () => JSX.Element; -// Warning: (ae-missing-release-tag) "EntitySourceLocation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type EntitySourceLocation = { locationTargetUrl: string; integrationType?: string; }; -// Warning: (ae-missing-release-tag) "EntityTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public -export function EntityTable( - props: EntityTableProps, -): JSX.Element; +export const EntityTable: { + (props: EntityTableProps): JSX.Element; + columns: Readonly<{ + createEntityRefColumn(options: { + defaultKind?: string | undefined; + }): TableColumn; + createEntityRelationColumn({ + title, + relation, + defaultKind, + filter: entityFilter, + }: { + title: string; + relation: string; + defaultKind?: string | undefined; + filter?: + | { + kind: string; + } + | undefined; + }): TableColumn; + createOwnerColumn(): TableColumn; + createDomainColumn(): TableColumn; + createSystemColumn(): TableColumn; + createMetadataDescriptionColumn(): TableColumn; + createSpecLifecycleColumn(): TableColumn; + createSpecTypeColumn(): TableColumn; + }>; + systemEntityColumns: TableColumn[]; + componentEntityColumns: TableColumn[]; +}; -// @public (undocumented) -export namespace EntityTable { - var // Warning: (ae-forgotten-export) The symbol "columnFactories" needs to be exported by the entry point index.d.ts - // - // (undocumented) - columns: typeof columnFactories; - var // (undocumented) - systemEntityColumns: TableColumn[]; - var // (undocumented) - componentEntityColumns: TableColumn[]; -} - -// Warning: (ae-unresolved-link) The @link reference could not be resolved: The reference is ambiguous because "EntityTable" has more than one declaration; you need to add a TSDoc member reference selector -// // @public export interface EntityTableProps { // (undocumented) @@ -368,9 +361,7 @@ export interface EntityTableProps { variant?: 'gridItem'; } -// Warning: (ae-missing-release-tag) "EntityTagFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class EntityTagFilter implements EntityFilter { constructor(values: string[]); // (undocumented) @@ -384,9 +375,7 @@ export class EntityTagFilter implements EntityFilter { // @public (undocumented) export const EntityTagPicker: () => JSX.Element | null; -// Warning: (ae-missing-release-tag) "EntityTextFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class EntityTextFilter implements EntityFilter { constructor(value: string); // (undocumented) @@ -395,9 +384,7 @@ export class EntityTextFilter implements EntityFilter { readonly value: string; } -// Warning: (ae-missing-release-tag) "EntityTypeFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class EntityTypeFilter implements EntityFilter { constructor(value: string | string[]); // (undocumented) @@ -423,26 +410,31 @@ export interface EntityTypePickerProps { initialFilter?: string; } -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "FavoriteEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export const FavoriteEntity: (props: Props) => JSX.Element; +// @public @deprecated (undocumented) +export type EntityTypeReturn = { + loading: boolean; + error?: Error; + availableTypes: string[]; + selectedTypes: string[]; + setSelectedTypes: (types: string[]) => void; +}; + +// @public +export const FavoriteEntity: (props: FavoriteEntityProps) => JSX.Element; -// Warning: (ae-missing-release-tag) "favoriteEntityIcon" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const favoriteEntityIcon: (isStarred: boolean) => JSX.Element; -// Warning: (ae-missing-release-tag) "favoriteEntityTooltip" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public (undocumented) +export type FavoriteEntityProps = ComponentProps & { + entity: Entity; +}; + // @public (undocumented) export const favoriteEntityTooltip: ( isStarred: boolean, ) => 'Remove from favorites' | 'Add to favorites'; -// Warning: (ae-missing-release-tag) "formatEntityRefTitle" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function formatEntityRefTitle( entityRef: Entity | EntityName, @@ -451,18 +443,12 @@ export function formatEntityRefTitle( }, ): string; -// Warning: (ae-missing-release-tag) "getEntityMetadataEditUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function getEntityMetadataEditUrl(entity: Entity): string | undefined; -// Warning: (ae-missing-release-tag) "getEntityMetadataViewUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function getEntityMetadataViewUrl(entity: Entity): string | undefined; -// Warning: (ae-missing-release-tag) "getEntityRelations" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function getEntityRelations( entity: Entity | undefined, @@ -472,8 +458,6 @@ export function getEntityRelations( }, ): EntityName[]; -// Warning: (ae-missing-release-tag) "getEntitySourceLocation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function getEntitySourceLocation( entity: Entity, @@ -487,24 +471,15 @@ export function InspectEntityDialog(props: { onClose: () => void; }): JSX.Element | null; -// Warning: (ae-missing-release-tag) "isOwnerOf" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function isOwnerOf(owner: Entity, owned: Entity): boolean; -// @public +// @public @deprecated export function loadCatalogOwnerRefs( catalogApi: CatalogApi, identityOwnerRefs: string[], ): Promise; -// @public @deprecated -export function loadIdentityOwnerRefs( - identityApi: IdentityApi, -): Promise; - -// Warning: (ae-missing-release-tag) "MockEntityListContextProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const MockEntityListContextProvider: ({ children, @@ -513,22 +488,16 @@ export const MockEntityListContextProvider: ({ value?: Partial> | undefined; }>) => JSX.Element; -// Warning: (ae-missing-release-tag) "reduceCatalogFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public @deprecated (undocumented) export function reduceCatalogFilters( filters: EntityFilter[], ): Record; -// Warning: (ae-missing-release-tag) "reduceEntityFilters" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public @deprecated (undocumented) export function reduceEntityFilters( filters: EntityFilter[], ): (entity: Entity) => boolean; -// Warning: (ae-missing-release-tag) "rootRoute" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public @deprecated (undocumented) export const rootRoute: RouteRef; @@ -541,16 +510,18 @@ export interface StarredEntitiesApi { // @public export const starredEntitiesApiRef: ApiRef; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "UnregisterEntityDialog" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const UnregisterEntityDialog: ({ - open, - onConfirm, - onClose, - entity, -}: Props_2) => JSX.Element; +export const UnregisterEntityDialog: ( + props: UnregisterEntityDialogProps, +) => JSX.Element; + +// @public (undocumented) +export type UnregisterEntityDialogProps = { + open: boolean; + onConfirm: () => any; + onClose: () => any; + entity: Entity; +}; // @public export function useEntity(): { @@ -560,32 +531,29 @@ export function useEntity(): { refresh: VoidFunction | undefined; }; -// Warning: (ae-missing-release-tag) "useEntityCompoundName" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public +// @public @deprecated export const useEntityCompoundName: () => { kind: string; namespace: string; name: string; }; -// Warning: (ae-missing-release-tag) "useEntityFromUrl" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public @deprecated (undocumented) export const useEntityFromUrl: () => EntityLoadingStatus; -// Warning: (ae-missing-release-tag) "useEntityKinds" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public @deprecated export function useEntityKinds(): { error: Error | undefined; loading: boolean; kinds: string[] | undefined; }; -// Warning: (ae-missing-release-tag) "useEntityListProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export function useEntityList< + EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, +>(): EntityListContextProps; + +// @public @deprecated export function useEntityListProvider< EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, >(): EntityListContextProps; @@ -603,32 +571,28 @@ export function useEntityPermission(permission: Permission): { error?: Error; }; -// Warning: (ae-forgotten-export) The symbol "EntityTypeReturn" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "useEntityTypeFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public -export function useEntityTypeFilter(): EntityTypeReturn; +export function useEntityTypeFilter(): { + loading: boolean; + error?: Error; + availableTypes: string[]; + selectedTypes: string[]; + setSelectedTypes: (types: string[]) => void; +}; -// @public +// @public @deprecated export function useOwnedEntities(allowedKinds?: string[]): { loading: boolean; ownedEntities: GetEntitiesResponse | undefined; }; -// Warning: (ae-missing-release-tag) "useOwnUser" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public +// @public @deprecated export function useOwnUser(): AsyncState; -// Warning: (ae-missing-release-tag) "useRelatedEntities" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function useRelatedEntities( entity: Entity, - { - type, - kind, - }: { + relationFilter: { type?: string; kind?: string; }, @@ -638,9 +602,7 @@ export function useRelatedEntities( error: Error | undefined; }; -// Warning: (ae-missing-release-tag) "UserListFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class UserListFilter implements EntityFilter { constructor( value: UserListFilterKind, @@ -659,22 +621,18 @@ export class UserListFilter implements EntityFilter { readonly value: UserListFilterKind; } -// Warning: (ae-missing-release-tag) "UserListFilterKind" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type UserListFilterKind = 'owned' | 'starred' | 'all'; -// Warning: (ae-forgotten-export) The symbol "UserListPickerProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "UserListPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const UserListPicker: ({ - initialFilter, - availableFilters, -}: UserListPickerProps) => JSX.Element; +export const UserListPicker: (props: UserListPickerProps) => JSX.Element; + +// @public (undocumented) +export type UserListPickerProps = { + initialFilter?: UserListFilterKind; + availableFilters?: UserListFilterKind[]; +}; -// Warning: (ae-missing-release-tag) "useStarredEntities" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function useStarredEntities(): { starredEntities: Set; @@ -682,8 +640,6 @@ export function useStarredEntities(): { isStarredEntity: (entityOrRef: Entity | EntityName | string) => boolean; }; -// Warning: (ae-missing-release-tag) "useStarredEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function useStarredEntity(entityOrRef: Entity | EntityName | string): { toggleStarredEntity: () => void; diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index d995fdf848..c7e255f973 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "0.6.14", + "version": "0.7.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,24 +24,25 @@ "backstage" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/plugin-permission-react": "^0.3.0", - "@backstage/types": "^0.1.1", - "@backstage/version-bridge": "^0.1.1", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/plugin-permission-common": "^0.5.1", + "@backstage/plugin-permission-react": "^0.3.2", + "@backstage/types": "^0.1.3", + "@backstage/version-bridge": "^0.1.2", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -56,10 +60,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/plugin-catalog-common": "^0.1.2", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/plugin-catalog-common": "^0.1.4", + "@backstage/plugin-scaffolder-common": "^0.2.2", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/catalog-react/src/api.ts b/plugins/catalog-react/src/api.ts index 51146b95bf..38e1f618cd 100644 --- a/plugins/catalog-react/src/api.ts +++ b/plugins/catalog-react/src/api.ts @@ -17,6 +17,10 @@ import { CatalogApi } from '@backstage/catalog-client'; import { createApiRef } from '@backstage/core-plugin-api'; +/** + * The API reference for the {@link @backstage/catalog-client#CatalogApi}. + * @public + */ export const catalogApiRef = createApiRef({ id: 'plugin.catalog.service', }); diff --git a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.tsx b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.tsx index 17d28aad9f..1891f83df7 100644 --- a/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.tsx +++ b/plugins/catalog-react/src/components/EntityKindPicker/EntityKindPicker.tsx @@ -16,7 +16,7 @@ import React, { useEffect, useState } from 'react'; import { Alert } from '@material-ui/lab'; -import { useEntityListProvider } from '../../hooks'; +import { useEntityList } from '../../hooks'; import { EntityKindFilter } from '../../filters'; /** @@ -33,7 +33,7 @@ export interface EntityKindPickerProps { export const EntityKindPicker = (props: EntityKindPickerProps) => { const { initialFilter, hidden } = props; - const { updateFilters, queryParameters } = useEntityListProvider(); + const { updateFilters, queryParameters } = useEntityList(); const [selectedKind] = useState( [queryParameters.kind].flat()[0] ?? initialFilter, ); diff --git a/plugins/catalog-react/src/components/EntityLifecyclePicker/EntityLifecyclePicker.tsx b/plugins/catalog-react/src/components/EntityLifecyclePicker/EntityLifecyclePicker.tsx index 77691ab7c5..a05914fd6d 100644 --- a/plugins/catalog-react/src/components/EntityLifecyclePicker/EntityLifecyclePicker.tsx +++ b/plugins/catalog-react/src/components/EntityLifecyclePicker/EntityLifecyclePicker.tsx @@ -28,7 +28,7 @@ import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { Autocomplete } from '@material-ui/lab'; import React, { useEffect, useMemo, useState } from 'react'; -import { useEntityListProvider } from '../../hooks/useEntityListProvider'; +import { useEntityList } from '../../hooks/useEntityListProvider'; import { EntityLifecycleFilter } from '../../filters'; /** @public */ @@ -50,7 +50,7 @@ const checkedIcon = ; export const EntityLifecyclePicker = () => { const classes = useStyles(); const { updateFilters, backendEntities, filters, queryParameters } = - useEntityListProvider(); + useEntityList(); const queryParamLifecycles = useMemo( () => [queryParameters.lifecycles].flat().filter(Boolean) as string[], diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx index 14abc358db..de21bfeee3 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { Entity, parseEntityRef } from '@backstage/catalog-model'; import { fireEvent, render } from '@testing-library/react'; import React from 'react'; import { MockEntityListContextProvider } from '../../testUtils/providers'; @@ -31,6 +31,7 @@ const sampleEntities: Entity[] = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/some-owner', target: { name: 'some-owner', namespace: 'default', @@ -39,6 +40,7 @@ const sampleEntities: Entity[] = [ }, { type: 'ownedBy', + targetRef: 'group:default/some-owner-2', target: { name: 'some-owner-2', namespace: 'default', @@ -56,6 +58,7 @@ const sampleEntities: Entity[] = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/another-owner', target: { name: 'another-owner', namespace: 'default', @@ -73,6 +76,7 @@ const sampleEntities: Entity[] = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/some-owner', target: { name: 'some-owner', namespace: 'default', @@ -96,7 +100,7 @@ describe('', () => { fireEvent.click(rendered.getByTestId('owner-picker-expand')); sampleEntities - .flatMap(e => e.relations?.map(r => r.target.name)) + .flatMap(e => e.relations?.map(r => parseEntityRef(r.targetRef).name)) .forEach(owner => { expect(rendered.getByText(owner as string)).toBeInTheDocument(); }); diff --git a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx index acb5ee9279..d025d08012 100644 --- a/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx +++ b/plugins/catalog-react/src/components/EntityOwnerPicker/EntityOwnerPicker.tsx @@ -28,7 +28,7 @@ import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { Autocomplete } from '@material-ui/lab'; import React, { useEffect, useMemo, useState } from 'react'; -import { useEntityListProvider } from '../../hooks/useEntityListProvider'; +import { useEntityList } from '../../hooks/useEntityListProvider'; import { EntityOwnerFilter } from '../../filters'; import { getEntityRelations } from '../../utils'; import { formatEntityRefTitle } from '../EntityRefLink'; @@ -52,7 +52,7 @@ const checkedIcon = ; export const EntityOwnerPicker = () => { const classes = useStyles(); const { updateFilters, backendEntities, filters, queryParameters } = - useEntityListProvider(); + useEntityList(); const queryParamOwners = useMemo( () => [queryParameters.owners].flat().filter(Boolean) as string[], diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx index f2c67f1087..1e01402ddf 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLink.tsx @@ -18,6 +18,7 @@ import { Entity, EntityName, DEFAULT_NAMESPACE, + parseEntityRef, } from '@backstage/catalog-model'; import React, { forwardRef } from 'react'; import { entityRouteRef } from '../../routes'; @@ -32,7 +33,7 @@ import { Tooltip } from '@material-ui/core'; * @public */ export type EntityRefLinkProps = { - entityRef: Entity | EntityName; + entityRef: Entity | EntityName | string; defaultKind?: string; title?: string; children?: React.ReactNode; @@ -52,7 +53,12 @@ export const EntityRefLink = forwardRef( let namespace; let name; - if ('metadata' in entityRef) { + if (typeof entityRef === 'string') { + const parsed = parseEntityRef(entityRef); + kind = parsed.kind; + namespace = parsed.namespace; + name = parsed.name; + } else if ('metadata' in entityRef) { kind = entityRef.kind; namespace = entityRef.metadata.namespace; name = entityRef.metadata.name; @@ -63,15 +69,13 @@ export const EntityRefLink = forwardRef( } kind = kind.toLocaleLowerCase('en-US'); + namespace = namespace?.toLocaleLowerCase('en-US') ?? DEFAULT_NAMESPACE; - const routeParams = { - kind, - namespace: namespace?.toLocaleLowerCase('en-US') ?? DEFAULT_NAMESPACE, - name, - }; - const formattedEntityRefTitle = formatEntityRefTitle(entityRef, { - defaultKind, - }); + const routeParams = { kind, namespace, name }; + const formattedEntityRefTitle = formatEntityRefTitle( + { kind, namespace, name }, + { defaultKind }, + ); const link = ( diff --git a/plugins/catalog-react/src/components/EntityRefLink/format.ts b/plugins/catalog-react/src/components/EntityRefLink/format.ts index 50e018c7ab..90100245a1 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/format.ts +++ b/plugins/catalog-react/src/components/EntityRefLink/format.ts @@ -20,6 +20,7 @@ import { DEFAULT_NAMESPACE, } from '@backstage/catalog-model'; +/** @public */ export function formatEntityRefTitle( entityRef: Entity | EntityName, opts?: { defaultKind?: string }, diff --git a/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.tsx b/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.tsx index ec977fada4..fdb07658d0 100644 --- a/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.tsx +++ b/plugins/catalog-react/src/components/EntitySearchBar/EntitySearchBar.tsx @@ -26,7 +26,7 @@ import Clear from '@material-ui/icons/Clear'; import Search from '@material-ui/icons/Search'; import React, { useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; -import { useEntityListProvider } from '../../hooks/useEntityListProvider'; +import { useEntityList } from '../../hooks/useEntityListProvider'; import { EntityTextFilter } from '../../filters'; /** @public */ @@ -45,11 +45,14 @@ const useStyles = makeStyles( }, ); -/** @public */ +/** + * Renders search bar for filtering the entity list. + * @public + */ export const EntitySearchBar = () => { const classes = useStyles(); - const { filters, updateFilters } = useEntityListProvider(); + const { filters, updateFilters } = useEntityList(); const [search, setSearch] = useState(filters.text?.value ?? ''); useDebounce( diff --git a/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx index 62c33701b4..879a35ffd0 100644 --- a/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx +++ b/plugins/catalog-react/src/components/EntityTable/EntityTable.tsx @@ -17,7 +17,7 @@ import { Entity } from '@backstage/catalog-model'; import { makeStyles } from '@material-ui/core'; import React, { ReactNode } from 'react'; -import * as columnFactories from './columns'; +import { columnFactories } from './columns'; import { componentEntityColumns, systemEntityColumns } from './presets'; import { Table, TableColumn } from '@backstage/core-components'; @@ -48,7 +48,7 @@ const useStyles = makeStyles(theme => ({ * * @public */ -export function EntityTable(props: EntityTableProps) { +export const EntityTable = (props: EntityTableProps) => { const { entities, title, @@ -85,7 +85,7 @@ export function EntityTable(props: EntityTableProps) { data={entities} /> ); -} +}; EntityTable.columns = columnFactories; diff --git a/plugins/catalog-react/src/components/EntityTable/columns.tsx b/plugins/catalog-react/src/components/EntityTable/columns.tsx index f6198fa1dc..d6c3582af6 100644 --- a/plugins/catalog-react/src/components/EntityTable/columns.tsx +++ b/plugins/catalog-react/src/components/EntityTable/columns.tsx @@ -30,150 +30,136 @@ import { } from '../EntityRefLink'; /** @public */ -export function createEntityRefColumn(options: { - defaultKind?: string; -}): TableColumn { - const { defaultKind } = options; - function formatContent(entity: T): string { - return ( - entity.metadata?.title || - formatEntityRefTitle(entity, { - defaultKind, - }) - ); - } - - return { - title: 'Name', - highlight: true, - customFilterAndSearch(filter, entity) { - // TODO: We could implement this more efficiently, like searching over - // each field that is displayed individually (kind, namespace, name). - // but that might confuse the user as it will behave different than a - // simple text search. - // Another alternative would be to cache the values. But writing them - // into the entity feels bad too. - return formatContent(entity).includes(filter); - }, - customSort(entity1, entity2) { - // TODO: We could implement this more efficiently by comparing field by field. - // This has similar issues as above. - return formatContent(entity1).localeCompare(formatContent(entity2)); - }, - render: entity => ( - - ), - }; -} - -/** @public */ -export function createEntityRelationColumn({ - title, - relation, - defaultKind, - filter: entityFilter, -}: { - title: string; - relation: string; - defaultKind?: string; - filter?: { kind: string }; -}): TableColumn { - function getRelations(entity: T): EntityName[] { - return getEntityRelations(entity, relation, entityFilter); - } - - function formatContent(entity: T): string { - return getRelations(entity) - .map(r => formatEntityRefTitle(r, { defaultKind })) - .join(', '); - } - - return { - title, - customFilterAndSearch(filter, entity) { - return formatContent(entity).includes(filter); - }, - customSort(entity1, entity2) { - return formatContent(entity1).localeCompare(formatContent(entity2)); - }, - render: entity => { +export const columnFactories = Object.freeze({ + createEntityRefColumn(options: { + defaultKind?: string; + }): TableColumn { + const { defaultKind } = options; + function formatContent(entity: T): string { return ( - + entity.metadata?.title || + formatEntityRefTitle(entity, { + defaultKind, + }) ); - }, - }; -} + } -/** @public */ -export function createOwnerColumn(): TableColumn { - return createEntityRelationColumn({ - title: 'Owner', - relation: RELATION_OWNED_BY, - defaultKind: 'group', - }); -} + return { + title: 'Name', + highlight: true, + customFilterAndSearch(filter, entity) { + // TODO: We could implement this more efficiently, like searching over + // each field that is displayed individually (kind, namespace, name). + // but that might confuse the user as it will behave different than a + // simple text search. + // Another alternative would be to cache the values. But writing them + // into the entity feels bad too. + return formatContent(entity).includes(filter); + }, + customSort(entity1, entity2) { + // TODO: We could implement this more efficiently by comparing field by field. + // This has similar issues as above. + return formatContent(entity1).localeCompare(formatContent(entity2)); + }, + render: entity => ( + + ), + }; + }, + createEntityRelationColumn({ + title, + relation, + defaultKind, + filter: entityFilter, + }: { + title: string; + relation: string; + defaultKind?: string; + filter?: { kind: string }; + }): TableColumn { + function getRelations(entity: T): EntityName[] { + return getEntityRelations(entity, relation, entityFilter); + } -/** @public */ -export function createDomainColumn(): TableColumn { - return createEntityRelationColumn({ - title: 'Domain', - relation: RELATION_PART_OF, - defaultKind: 'domain', - filter: { - kind: 'domain', - }, - }); -} + function formatContent(entity: T): string { + return getRelations(entity) + .map(r => formatEntityRefTitle(r, { defaultKind })) + .join(', '); + } -/** @public */ -export function createSystemColumn(): TableColumn { - return createEntityRelationColumn({ - title: 'System', - relation: RELATION_PART_OF, - defaultKind: 'system', - filter: { - kind: 'system', - }, - }); -} - -/** @public */ -export function createMetadataDescriptionColumn< - T extends Entity, ->(): TableColumn { - return { - title: 'Description', - field: 'metadata.description', - render: entity => ( - - ), - width: 'auto', - }; -} - -/** @public */ -export function createSpecLifecycleColumn(): TableColumn { - return { - title: 'Lifecycle', - field: 'spec.lifecycle', - }; -} - -/** @public */ -export function createSpecTypeColumn(): TableColumn { - return { - title: 'Type', - field: 'spec.type', - }; -} + return { + title, + customFilterAndSearch(filter, entity) { + return formatContent(entity).includes(filter); + }, + customSort(entity1, entity2) { + return formatContent(entity1).localeCompare(formatContent(entity2)); + }, + render: entity => { + return ( + + ); + }, + }; + }, + createOwnerColumn(): TableColumn { + return this.createEntityRelationColumn({ + title: 'Owner', + relation: RELATION_OWNED_BY, + defaultKind: 'group', + }); + }, + createDomainColumn(): TableColumn { + return this.createEntityRelationColumn({ + title: 'Domain', + relation: RELATION_PART_OF, + defaultKind: 'domain', + filter: { + kind: 'domain', + }, + }); + }, + createSystemColumn(): TableColumn { + return this.createEntityRelationColumn({ + title: 'System', + relation: RELATION_PART_OF, + defaultKind: 'system', + filter: { + kind: 'system', + }, + }); + }, + createMetadataDescriptionColumn(): TableColumn { + return { + title: 'Description', + field: 'metadata.description', + render: entity => ( + + ), + width: 'auto', + }; + }, + createSpecLifecycleColumn(): TableColumn { + return { + title: 'Lifecycle', + field: 'spec.lifecycle', + }; + }, + createSpecTypeColumn(): TableColumn { + return { + title: 'Type', + field: 'spec.type', + }; + }, +}); diff --git a/plugins/catalog-react/src/components/EntityTable/index.ts b/plugins/catalog-react/src/components/EntityTable/index.ts index 2ed07f9823..5fb651fe72 100644 --- a/plugins/catalog-react/src/components/EntityTable/index.ts +++ b/plugins/catalog-react/src/components/EntityTable/index.ts @@ -15,4 +15,5 @@ */ export { EntityTable } from './EntityTable'; +export { columnFactories } from './columns'; export type { EntityTableProps } from './EntityTable'; diff --git a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx index a509b67477..f2fcd016f0 100644 --- a/plugins/catalog-react/src/components/EntityTable/presets.test.tsx +++ b/plugins/catalog-react/src/components/EntityTable/presets.test.tsx @@ -44,17 +44,19 @@ describe('systemEntityColumns', () => { relations: [ { type: RELATION_PART_OF, + targetRef: 'domain:my-namespace/my-domain', target: { - kind: 'Domain', + kind: 'domain', name: 'my-domain', namespace: 'my-namespace', }, }, { type: RELATION_OWNED_BY, + targetRef: 'group:default/test', target: { - kind: 'Group', - name: 'Test', + kind: 'group', + name: 'test', namespace: 'default', }, }, @@ -79,7 +81,7 @@ describe('systemEntityColumns', () => { await waitFor(() => { expect(getByText('my-namespace/my-system')).toBeInTheDocument(); expect(getByText('my-namespace/my-domain')).toBeInTheDocument(); - expect(getByText('Test')).toBeInTheDocument(); + expect(getByText('test')).toBeInTheDocument(); expect(getByText(/Some/)).toBeInTheDocument(); }); }); @@ -104,17 +106,19 @@ describe('componentEntityColumns', () => { relations: [ { type: RELATION_PART_OF, + targetRef: 'system:my-namespace/my-system', target: { - kind: 'System', + kind: 'system', name: 'my-system', namespace: 'my-namespace', }, }, { type: RELATION_OWNED_BY, + targetRef: 'group:default/test', target: { - kind: 'Group', - name: 'Test', + kind: 'group', + name: 'test', namespace: 'default', }, }, @@ -139,7 +143,7 @@ describe('componentEntityColumns', () => { await waitFor(() => { expect(getByText('my-namespace/my-component')).toBeInTheDocument(); expect(getByText('my-namespace/my-system')).toBeInTheDocument(); - expect(getByText('Test')).toBeInTheDocument(); + expect(getByText('test')).toBeInTheDocument(); expect(getByText('production')).toBeInTheDocument(); expect(getByText('service')).toBeInTheDocument(); expect(getByText(/Some/)).toBeInTheDocument(); diff --git a/plugins/catalog-react/src/components/EntityTable/presets.tsx b/plugins/catalog-react/src/components/EntityTable/presets.tsx index 2ddffb0b7a..6e4ddb2081 100644 --- a/plugins/catalog-react/src/components/EntityTable/presets.tsx +++ b/plugins/catalog-react/src/components/EntityTable/presets.tsx @@ -15,29 +15,21 @@ */ import { ComponentEntity, SystemEntity } from '@backstage/catalog-model'; -import { - createDomainColumn, - createEntityRefColumn, - createMetadataDescriptionColumn, - createOwnerColumn, - createSpecLifecycleColumn, - createSpecTypeColumn, - createSystemColumn, -} from './columns'; import { TableColumn } from '@backstage/core-components'; +import { columnFactories } from './columns'; export const systemEntityColumns: TableColumn[] = [ - createEntityRefColumn({ defaultKind: 'system' }), - createDomainColumn(), - createOwnerColumn(), - createMetadataDescriptionColumn(), + columnFactories.createEntityRefColumn({ defaultKind: 'system' }), + columnFactories.createDomainColumn(), + columnFactories.createOwnerColumn(), + columnFactories.createMetadataDescriptionColumn(), ]; export const componentEntityColumns: TableColumn[] = [ - createEntityRefColumn({ defaultKind: 'component' }), - createSystemColumn(), - createOwnerColumn(), - createSpecTypeColumn(), - createSpecLifecycleColumn(), - createMetadataDescriptionColumn(), + columnFactories.createEntityRefColumn({ defaultKind: 'component' }), + columnFactories.createSystemColumn(), + columnFactories.createOwnerColumn(), + columnFactories.createSpecTypeColumn(), + columnFactories.createSpecLifecycleColumn(), + columnFactories.createMetadataDescriptionColumn(), ]; diff --git a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx index a91389a653..5b5e0e7ab9 100644 --- a/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx +++ b/plugins/catalog-react/src/components/EntityTagPicker/EntityTagPicker.tsx @@ -28,7 +28,7 @@ import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import { Autocomplete } from '@material-ui/lab'; import React, { useEffect, useMemo, useState } from 'react'; -import { useEntityListProvider } from '../../hooks/useEntityListProvider'; +import { useEntityList } from '../../hooks/useEntityListProvider'; import { EntityTagFilter } from '../../filters'; /** @public */ @@ -50,7 +50,7 @@ const checkedIcon = ; export const EntityTagPicker = () => { const classes = useStyles(); const { updateFilters, backendEntities, filters, queryParameters } = - useEntityListProvider(); + useEntityList(); const queryParamTags = useMemo( () => [queryParameters.tags].flat().filter(Boolean) as string[], diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx index 9ed27bbef8..4623cf5d9e 100644 --- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx +++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx @@ -26,6 +26,7 @@ import { EntityKindFilter, EntityTypeFilter } from '../../filters'; import { alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { GetEntityFacetsResponse } from '@backstage/catalog-client'; const entities: Entity[] = [ { @@ -64,7 +65,14 @@ const apis = TestApiRegistry.from( [ catalogApiRef, { - getEntities: jest.fn().mockResolvedValue({ items: entities }), + getEntityFacets: jest.fn().mockResolvedValue({ + facets: { + 'spec.type': entities.map(e => ({ + value: (e.spec as any).type, + count: 1, + })), + }, + } as GetEntityFacetsResponse), }, ], [ diff --git a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx index 730c9a2378..3832a82877 100644 --- a/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx +++ b/plugins/catalog-react/src/components/FavoriteEntity/FavoriteEntity.tsx @@ -21,7 +21,10 @@ import StarBorder from '@material-ui/icons/StarBorder'; import React, { ComponentProps } from 'react'; import { useStarredEntity } from '../../hooks/useStarredEntity'; -type Props = ComponentProps & { entity: Entity }; +/** @public */ +export type FavoriteEntityProps = ComponentProps & { + entity: Entity; +}; const YellowStar = withStyles({ root: { @@ -29,17 +32,20 @@ const YellowStar = withStyles({ }, })(Star); +/** @public */ export const favoriteEntityTooltip = (isStarred: boolean) => isStarred ? 'Remove from favorites' : 'Add to favorites'; +/** @public */ export const favoriteEntityIcon = (isStarred: boolean) => isStarred ? : ; /** * IconButton for showing if a current entity is starred and adding/removing it from the favorite entities * @param props - MaterialUI IconButton props extended by required `entity` prop + * @public */ -export const FavoriteEntity = (props: Props) => { +export const FavoriteEntity = (props: FavoriteEntityProps) => { const { toggleStarredEntity, isStarredEntity } = useStarredEntity( props.entity, ); diff --git a/plugins/catalog-react/src/components/FavoriteEntity/index.ts b/plugins/catalog-react/src/components/FavoriteEntity/index.ts index f731ca7483..a46d04b6c7 100644 --- a/plugins/catalog-react/src/components/FavoriteEntity/index.ts +++ b/plugins/catalog-react/src/components/FavoriteEntity/index.ts @@ -19,3 +19,4 @@ export { favoriteEntityIcon, FavoriteEntity, } from './FavoriteEntity'; +export type { FavoriteEntityProps } from './FavoriteEntity'; diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx index c2fc49aa0c..4bb41c91c1 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/EntityKindIcon.tsx @@ -14,23 +14,54 @@ * limitations under the License. */ +import { parseEntityRef } from '@backstage/catalog-model'; import { useApp } from '@backstage/core-plugin-api'; import WorkIcon from '@material-ui/icons/Work'; import React from 'react'; +const DEFAULT_ICON = WorkIcon; + +function getKind( + kind: string | undefined, + entityRef: string | undefined, +): string | undefined { + if (kind) { + return kind.toLocaleLowerCase('en-US'); + } + + if (entityRef) { + try { + return parseEntityRef(entityRef).kind.toLocaleLowerCase('en-US'); + } catch { + return undefined; + } + } + + return undefined; +} + +function useIcon(kind: string | undefined, entityRef: string | undefined) { + const app = useApp(); + + const actualKind = getKind(kind, entityRef); + if (!actualKind) { + return DEFAULT_ICON; + } + + const icon = app.getSystemIcon(`kind:${actualKind}`); + return icon || DEFAULT_ICON; +} + export function EntityKindIcon(props: { - kind: string; + kind?: string; + entityRef?: string; x?: number; y?: number; width?: number; height?: number; className?: string; }) { - const app = useApp(); - - const { kind, ...otherProps } = props; - const Icon = - app.getSystemIcon(`kind:${kind.toLocaleLowerCase('en-US')}`) ?? WorkIcon; - + const { kind, entityRef, ...otherProps } = props; + const Icon = useIcon(kind, entityRef); return ; } diff --git a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx index dbb7399481..a4e8523beb 100644 --- a/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx +++ b/plugins/catalog-react/src/components/InspectEntityDialog/components/OverviewPage.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { AlphaEntity, stringifyEntityRef } from '@backstage/catalog-model'; +import { AlphaEntity } from '@backstage/catalog-model'; import { Box, DialogContentText, @@ -56,7 +56,7 @@ export function OverviewPage(props: { entity: AlphaEntity }) { } = props.entity; const groupedRelations = groupBy( - sortBy(relations, r => stringifyEntityRef(r.target)), + sortBy(relations, r => r.targetRef), 'type', ); @@ -143,12 +143,14 @@ export function OverviewPage(props: { entity: AlphaEntity }) {
{type}}> {groupRelations.map(group => ( - + - + } + primary={ + + } /> ))} diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx index 5c267916ea..48aa2f4012 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx @@ -41,13 +41,6 @@ const useStyles = makeStyles({ }, }); -type Props = { - open: boolean; - onConfirm: () => any; - onClose: () => any; - entity: Entity; -}; - const Contents = ({ entity, onConfirm, @@ -248,23 +241,30 @@ const Contents = ({ return Internal error: Unknown state; }; -export const UnregisterEntityDialog = ({ - open, - onConfirm, - onClose, - entity, -}: Props) => ( - - - Are you sure you want to unregister this entity? - - - - - - - - -); +/** @public */ +export type UnregisterEntityDialogProps = { + open: boolean; + onConfirm: () => any; + onClose: () => any; + entity: Entity; +}; + +/** @public */ +export const UnregisterEntityDialog = (props: UnregisterEntityDialogProps) => { + const { open, onConfirm, onClose, entity } = props; + return ( + + + Are you sure you want to unregister this entity? + + + + + + + + + ); +}; diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/index.ts b/plugins/catalog-react/src/components/UnregisterEntityDialog/index.ts index 8fc750e706..66eb6961ae 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/index.ts +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/index.ts @@ -15,3 +15,4 @@ */ export { UnregisterEntityDialog } from './UnregisterEntityDialog'; +export type { UnregisterEntityDialogProps } from './UnregisterEntityDialog'; diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx index 44fc3886ea..25af7564d1 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx @@ -39,7 +39,7 @@ function defer(): { promise: Promise; resolve: (value: T) => void } { describe('useUnregisterEntityDialogState', () => { const catalogApiMock = { - getOriginLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), getEntities: jest.fn(), removeLocationById: jest.fn(), removeEntityByUid: jest.fn(), @@ -65,9 +65,7 @@ describe('useUnregisterEntityDialogState', () => { resolveLocation = deferredLocation.resolve; resolveColocatedEntities = deferredColocatedEntities.resolve; - catalogApiMock.getOriginLocationByEntity.mockReturnValue( - deferredLocation.promise, - ); + catalogApiMock.getLocationByRef.mockReturnValue(deferredLocation.promise); catalogApiMock.getEntities.mockReturnValue( deferredColocatedEntities.promise.then(items => ({ items })), ); diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.ts b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.ts index c637490268..6420ebd5ae 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.ts +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.ts @@ -67,7 +67,7 @@ export function useUnregisterEntityDialogState( // Load the prerequisite data: what entities that are colocated with us, and // what location that spawned us const prerequisites = useAsync(async () => { - const locationPromise = catalogApi.getOriginLocationByEntity(entity); + const locationPromise = catalogApi.getLocationByRef(locationRef!); let colocatedEntitiesPromise: Promise; if (!locationRef) { diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx index 630a995bbe..316a0f2c06 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx @@ -103,6 +103,7 @@ const backendEntities: Entity[] = [ relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/testuser', target: { kind: 'User', namespace: 'default', name: 'testUser' }, }, ], @@ -136,6 +137,7 @@ const backendEntities: Entity[] = [ relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/testuser', target: { kind: 'User', namespace: 'default', name: 'testUser' }, }, ], diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx index de23f75e47..d281bb31a8 100644 --- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx +++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.tsx @@ -36,7 +36,7 @@ import { compact } from 'lodash'; import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { UserListFilter } from '../../filters'; import { - useEntityListProvider, + useEntityList, useStarredEntities, useEntityOwnership, } from '../../hooks'; @@ -118,15 +118,15 @@ function getFilterGroups(orgName: string | undefined): ButtonGroup[] { ]; } -type UserListPickerProps = { +/** @public */ +export type UserListPickerProps = { initialFilter?: UserListFilterKind; availableFilters?: UserListFilterKind[]; }; -export const UserListPicker = ({ - initialFilter, - availableFilters, -}: UserListPickerProps) => { +/** @public */ +export const UserListPicker = (props: UserListPickerProps) => { + const { initialFilter, availableFilters } = props; const classes = useStyles(); const configApi = useApi(configApiRef); const orgName = configApi.getOptionalString('organization.name') ?? 'Company'; @@ -136,7 +136,7 @@ export const UserListPicker = ({ backendEntities, queryParameters, loading: loadingBackendEntities, - } = useEntityListProvider(); + } = useEntityList(); // Remove group items that aren't in availableFilters and exclude // any now-empty groups. diff --git a/plugins/catalog-react/src/components/UserListPicker/index.ts b/plugins/catalog-react/src/components/UserListPicker/index.ts index 45cb471197..269c4ca304 100644 --- a/plugins/catalog-react/src/components/UserListPicker/index.ts +++ b/plugins/catalog-react/src/components/UserListPicker/index.ts @@ -15,4 +15,5 @@ */ export { UserListPicker } from './UserListPicker'; +export type { UserListPickerProps } from './UserListPicker'; export type { CatalogReactUserListPickerClassKey } from './UserListPicker'; diff --git a/plugins/catalog-react/src/filters.test.ts b/plugins/catalog-react/src/filters.test.ts index 668e3b4bc3..cdc2ce2150 100644 --- a/plugins/catalog-react/src/filters.test.ts +++ b/plugins/catalog-react/src/filters.test.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { Entity, TemplateEntityV1beta2 } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; +import { TemplateEntityV1beta2 } from '@backstage/plugin-scaffolder-common'; import { EntityTextFilter } from './filters'; const entities: Entity[] = [ diff --git a/plugins/catalog-react/src/filters.ts b/plugins/catalog-react/src/filters.ts index 2804d5d6bd..5d2c2e531b 100644 --- a/plugins/catalog-react/src/filters.ts +++ b/plugins/catalog-react/src/filters.ts @@ -19,6 +19,10 @@ import { formatEntityRefTitle } from './components/EntityRefLink'; import { EntityFilter, UserListFilterKind } from './types'; import { getEntityRelations } from './utils'; +/** + * Filter entities based on Kind. + * @public + */ export class EntityKindFilter implements EntityFilter { constructor(readonly value: string) {} @@ -31,6 +35,10 @@ export class EntityKindFilter implements EntityFilter { } } +/** + * Filters entities based on type + * @public + */ export class EntityTypeFilter implements EntityFilter { constructor(readonly value: string | string[]) {} @@ -48,6 +56,10 @@ export class EntityTypeFilter implements EntityFilter { } } +/** + * Filters entities based on tag. + * @public + */ export class EntityTagFilter implements EntityFilter { constructor(readonly values: string[]) {} @@ -60,6 +72,10 @@ export class EntityTagFilter implements EntityFilter { } } +/** + * Filters entities where the text matches spec, title or tags. + * @public + */ export class EntityTextFilter implements EntityFilter { constructor(readonly value: string) {} @@ -81,6 +97,10 @@ export class EntityTextFilter implements EntityFilter { } } +/** + * Filter matching entities that are owned by group. + * @public + */ export class EntityOwnerFilter implements EntityFilter { constructor(readonly values: string[]) {} @@ -97,6 +117,10 @@ export class EntityOwnerFilter implements EntityFilter { } } +/** + * Filters entities on lifecycle. + * @public + */ export class EntityLifecycleFilter implements EntityFilter { constructor(readonly values: string[]) {} @@ -109,6 +133,10 @@ export class EntityLifecycleFilter implements EntityFilter { } } +/** + * Filters entities based on whatever the user has starred or owns them. + * @public + */ export class UserListFilter implements EntityFilter { constructor( readonly value: UserListFilterKind, diff --git a/plugins/catalog-react/src/hooks/index.ts b/plugins/catalog-react/src/hooks/index.ts index 06791f5706..3e6ef7c945 100644 --- a/plugins/catalog-react/src/hooks/index.ts +++ b/plugins/catalog-react/src/hooks/index.ts @@ -14,13 +14,13 @@ * limitations under the License. */ export { - EntityContext, useEntity, useEntityFromUrl, EntityProvider, AsyncEntityProvider, } from './useEntity'; export type { + EntityLoadingStatus, EntityProviderProps, AsyncEntityProviderProps, } from './useEntity'; @@ -29,18 +29,19 @@ export { EntityListContext, EntityListProvider, useEntityListProvider, + useEntityList, +} from './useEntityListProvider'; +export type { + DefaultEntityFilters, + EntityListContextProps, } from './useEntityListProvider'; -export type { DefaultEntityFilters } from './useEntityListProvider'; export { useEntityTypeFilter } from './useEntityTypeFilter'; +export type { EntityTypeReturn } from './useEntityTypeFilter'; export { useEntityKinds } from './useEntityKinds'; export { useOwnUser } from './useOwnUser'; export { useRelatedEntities } from './useRelatedEntities'; export { useStarredEntities } from './useStarredEntities'; export { useStarredEntity } from './useStarredEntity'; -export { - loadCatalogOwnerRefs, - useEntityOwnership, - loadIdentityOwnerRefs, -} from './useEntityOwnership'; +export { loadCatalogOwnerRefs, useEntityOwnership } from './useEntityOwnership'; export { useOwnedEntities } from './useOwnedEntities'; export { useEntityPermission } from './useEntityPermission'; diff --git a/plugins/catalog-react/src/hooks/useEntity.test.tsx b/plugins/catalog-react/src/hooks/useEntity.test.tsx index 9e73ed1475..f61283f2cf 100644 --- a/plugins/catalog-react/src/hooks/useEntity.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.test.tsx @@ -16,12 +16,7 @@ import React from 'react'; import { renderHook } from '@testing-library/react-hooks'; -import { - useEntity, - EntityProvider, - AsyncEntityProvider, - EntityContext, -} from './useEntity'; +import { useEntity, EntityProvider, AsyncEntityProvider } from './useEntity'; import { Entity } from '@backstage/catalog-model'; describe('EntityProvider', () => { @@ -103,38 +98,3 @@ describe('AsyncEntityProvider', () => { expect(result.current.refresh).toBe(undefined); }); }); - -describe('EntityContext.Provider', () => { - it('should provide no entity', async () => { - const { result } = renderHook(() => useEntity(), { - wrapper: ({ children }) => ( - - ), - }); - - expect(result.current.entity).toBe(undefined); - expect(result.current.loading).toBe(false); - expect(result.current.error).toBe(undefined); - expect(result.current.refresh).toBe(undefined); - }); - - it('should provide an entity', async () => { - const entity = { kind: 'MyEntity' } as Entity; - const { result } = renderHook(() => useEntity(), { - wrapper: ({ children }) => ( - - ), - }); - - expect(result.current.entity).toBe(entity); - expect(result.current.loading).toBe(false); - expect(result.current.error).toBe(undefined); - expect(result.current.refresh).toBe(undefined); - }); -}); diff --git a/plugins/catalog-react/src/hooks/useEntity.tsx b/plugins/catalog-react/src/hooks/useEntity.tsx index 6ad61a5d81..5cb67cb79a 100644 --- a/plugins/catalog-react/src/hooks/useEntity.tsx +++ b/plugins/catalog-react/src/hooks/useEntity.tsx @@ -20,39 +20,20 @@ import { createVersionedValueMap, useVersionedContext, } from '@backstage/version-bridge'; -import React, { - ReactNode, - useEffect, - createContext, - Provider, - Context, -} from 'react'; +import React, { ReactNode, useEffect } from 'react'; import { useNavigate } from 'react-router'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; import { catalogApiRef } from '../api'; import { useEntityCompoundName } from './useEntityCompoundName'; -type EntityLoadingStatus = { +/** @public */ +export type EntityLoadingStatus = { entity?: Entity; loading: boolean; error?: Error; refresh?: VoidFunction; }; -/** - * @public - * @deprecated use `useEntity` and `EntityProvider` or `AsyncEntityProvider` instead. - */ -export const EntityContext: Context = - createContext({ - entity: undefined, - loading: true, - error: undefined, - refresh: () => {}, - }); -// We grab this for use in the new provider, since we're overriding it later on -const OldEntityProvider = EntityContext.Provider; - // This context has support for multiple concurrent versions of this package. // It is currently used in parallel with the old context in order to provide // a smooth transition, but will eventually be the only context we use. @@ -88,11 +69,9 @@ export const AsyncEntityProvider = ({ // We provide both the old and the new context, since // consumers might be doing things like `useContext(EntityContext)` return ( - - - {children} - - + + {children} + ); }; @@ -121,18 +100,9 @@ export const EntityProvider = ({ entity, children }: EntityProviderProps) => ( /> ); -// This is used for forwards compatibility with the new entity context -const CompatibilityProvider = ({ - value, - children, -}: { - value: EntityLoadingStatus; - children: ReactNode; -}) => { - return ; -}; -EntityContext.Provider = CompatibilityProvider as Provider; - +/** @public + * @deprecated will be deleted shortly due to low external usage, re-implement if needed. + */ export const useEntityFromUrl = (): EntityLoadingStatus => { const { kind, namespace, name } = useEntityCompoundName(); const navigate = useNavigate(); diff --git a/plugins/catalog-react/src/hooks/useEntityCompoundName.ts b/plugins/catalog-react/src/hooks/useEntityCompoundName.ts index 57e1c08a23..481c18f8a0 100644 --- a/plugins/catalog-react/src/hooks/useEntityCompoundName.ts +++ b/plugins/catalog-react/src/hooks/useEntityCompoundName.ts @@ -18,6 +18,8 @@ import { useRouteRefParams } from '@backstage/core-plugin-api'; /** * Grabs entity kind, namespace, and name from the location + * @public + * @deprecated use {@link @backstage/core-plugin-api#useRouteRefParams} instead */ export const useEntityCompoundName = () => { const { kind, namespace, name } = useRouteRefParams(entityRouteRef); diff --git a/plugins/catalog-react/src/hooks/useEntityKinds.test.tsx b/plugins/catalog-react/src/hooks/useEntityKinds.test.tsx index cdcac8c194..c96301b3c4 100644 --- a/plugins/catalog-react/src/hooks/useEntityKinds.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityKinds.test.tsx @@ -16,45 +16,21 @@ import React, { PropsWithChildren } from 'react'; import { CatalogApi } from '@backstage/catalog-client'; -import { Entity } from '@backstage/catalog-model'; import { catalogApiRef } from '../api'; import { renderHook } from '@testing-library/react-hooks'; import { useEntityKinds } from './useEntityKinds'; import { TestApiProvider } from '@backstage/test-utils'; -const entities: Entity[] = [ - { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'component-1', - }, - }, - { - apiVersion: '1', - kind: 'Component', - metadata: { - name: 'component-2', - }, - }, - { - apiVersion: '1', - kind: 'Template', - metadata: { - name: 'template', - }, - }, - { - apiVersion: '1', - kind: 'System', - metadata: { - name: 'system', - }, - }, -]; - const mockCatalogApi: Partial = { - getEntities: jest.fn().mockResolvedValue({ items: entities }), + getEntityFacets: jest.fn().mockResolvedValue({ + facets: { + kind: [ + { value: 'Template', count: 2 }, + { value: 'System', count: 1 }, + { value: 'Component', count: 3 }, + ], + }, + }), }; const wrapper = ({ children }: PropsWithChildren<{}>) => { @@ -66,24 +42,10 @@ const wrapper = ({ children }: PropsWithChildren<{}>) => { }; describe('useEntityKinds', () => { - it('does not return duplicate kinds', async () => { + it('gets entity kinds', async () => { const { result, waitForValueToChange } = renderHook( () => useEntityKinds(), - { - wrapper, - }, - ); - await waitForValueToChange(() => result.current); - expect(result.current.kinds).toBeDefined(); - expect(result.current.kinds!.length).toBe(3); - }); - - it('sorts entity kinds', async () => { - const { result, waitForValueToChange } = renderHook( - () => useEntityKinds(), - { - wrapper, - }, + { wrapper }, ); await waitForValueToChange(() => result.current); expect(result.current.kinds).toEqual(['Component', 'System', 'Template']); diff --git a/plugins/catalog-react/src/hooks/useEntityKinds.ts b/plugins/catalog-react/src/hooks/useEntityKinds.ts index 1d5b2fec52..3dfc31eb88 100644 --- a/plugins/catalog-react/src/hooks/useEntityKinds.ts +++ b/plugins/catalog-react/src/hooks/useEntityKinds.ts @@ -18,7 +18,11 @@ import useAsync from 'react-use/lib/useAsync'; import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef } from '../api'; -// Retrieve a list of unique entity kinds present in the catalog +/** + * Retrieve a list of unique entity kinds present in the catalog + * @public + * @deprecated and will be removed due to low utility value. + */ export function useEntityKinds() { const catalogApi = useApi(catalogApiRef); @@ -27,11 +31,9 @@ export function useEntityKinds() { loading, value: kinds, } = useAsync(async () => { - const entities = await catalogApi - .getEntities({ fields: ['kind'] }) - .then(response => response.items); - - return [...new Set(entities.map(e => e.kind))].sort(); + return await catalogApi + .getEntityFacets({ facets: ['kind'] }) + .then(response => response.facets.kind?.map(f => f.value).sort() || []); }); return { error, loading, kinds }; } diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index 548561108d..c8ca29b07f 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -33,10 +33,7 @@ import { DefaultStarredEntitiesApi, starredEntitiesApiRef } from '../apis'; import { EntityKindPicker, UserListPicker } from '../components'; import { EntityKindFilter, EntityTypeFilter, UserListFilter } from '../filters'; import { UserListFilterKind } from '../types'; -import { - EntityListProvider, - useEntityListProvider, -} from './useEntityListProvider'; +import { EntityListProvider, useEntityList } from './useEntityListProvider'; const entities: Entity[] = [ { @@ -48,6 +45,7 @@ const entities: Entity[] = [ relations: [ { type: 'ownedBy', + targetRef: 'user:default/guest', target: { name: 'guest', namespace: 'default', @@ -129,12 +127,9 @@ describe('', () => { }); it('resolves backend filters', async () => { - const { result, waitForValueToChange } = renderHook( - () => useEntityListProvider(), - { - wrapper, - }, - ); + const { result, waitForValueToChange } = renderHook(() => useEntityList(), { + wrapper, + }); await waitForValueToChange(() => result.current.backendEntities); expect(result.current.backendEntities.length).toBe(2); expect(mockCatalogApi.getEntities).toHaveBeenCalledWith({ @@ -143,7 +138,7 @@ describe('', () => { }); it('resolves frontend filters', async () => { - const { result, waitFor } = renderHook(() => useEntityListProvider(), { + const { result, waitFor } = renderHook(() => useEntityList(), { wrapper, initialProps: { userFilter: 'all', @@ -173,7 +168,7 @@ describe('', () => { const query = qs.stringify({ filters: { kind: 'component', type: 'service' }, }); - const { result, waitFor } = renderHook(() => useEntityListProvider(), { + const { result, waitFor } = renderHook(() => useEntityList(), { wrapper, initialProps: { location: `/catalog?${query}`, @@ -187,7 +182,7 @@ describe('', () => { }); it('does not fetch when only frontend filters change', async () => { - const { result, waitFor } = renderHook(() => useEntityListProvider(), { + const { result, waitFor } = renderHook(() => useEntityList(), { wrapper, }); @@ -214,7 +209,7 @@ describe('', () => { it('debounces multiple filter changes', async () => { const { result, waitForNextUpdate, waitForValueToChange } = renderHook( - () => useEntityListProvider(), + () => useEntityList(), { wrapper, }, @@ -233,7 +228,7 @@ describe('', () => { it('returns an error on catalogApi failure', async () => { const { result, waitForValueToChange, waitFor } = renderHook( - () => useEntityListProvider(), + () => useEntityList(), { wrapper, }, diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx index 9b4d850208..f448e41b82 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.tsx @@ -43,6 +43,7 @@ import { EntityFilter } from '../types'; import { reduceCatalogFilters, reduceEntityFilters } from '../utils'; import { useApi } from '@backstage/core-plugin-api'; +/** @public */ export type DefaultEntityFilters = { kind?: EntityKindFilter; type?: EntityTypeFilter; @@ -53,6 +54,7 @@ export type DefaultEntityFilters = { text?: EntityTextFilter; }; +/** @public */ export type EntityListContextProps< EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, > = { @@ -91,6 +93,10 @@ export type EntityListContextProps< error?: Error; }; +/** + * Creates new context for entity listing and filtering. + * @public + */ export const EntityListContext = createContext< EntityListContextProps | undefined >(undefined); @@ -101,6 +107,10 @@ type OutputState = { backendEntities: Entity[]; }; +/** + * Provides entities and filters for a catalog listing. + * @public + */ export const EntityListProvider = ({ children, }: PropsWithChildren<{}>) => { @@ -239,13 +249,29 @@ export const EntityListProvider = ({ ); }; +/** + * Hook for interacting with the entity list context provided by the {@link EntityListProvider}. + * @public + * @deprecated use {@link useEntityList} instead. + */ export function useEntityListProvider< EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, >(): EntityListContextProps { const context = useContext(EntityListContext); if (!context) - throw new Error( - 'useEntityListProvider must be used within EntityListProvider', - ); + throw new Error('useEntityList must be used within EntityListProvider'); + return context; +} + +/** + * Hook for interacting with the entity list context provided by the {@link EntityListProvider}. + * @public + */ +export function useEntityList< + EntityFilters extends DefaultEntityFilters = DefaultEntityFilters, +>(): EntityListContextProps { + const context = useContext(EntityListContext); + if (!context) + throw new Error('useEntityList must be used within EntityListProvider'); return context; } diff --git a/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx b/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx index 7f838163a2..1bb9a649ad 100644 --- a/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx @@ -26,11 +26,7 @@ import { TestApiProvider } from '@backstage/test-utils'; import { renderHook } from '@testing-library/react-hooks'; import React from 'react'; import { catalogApiRef } from '../api'; -import { - loadCatalogOwnerRefs, - loadIdentityOwnerRefs, - useEntityOwnership, -} from './useEntityOwnership'; +import { loadCatalogOwnerRefs, useEntityOwnership } from './useEntityOwnership'; describe('useEntityOwnership', () => { type MockIdentityApi = jest.Mocked>; @@ -70,10 +66,12 @@ describe('useEntityOwnership', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/user1', target: { kind: 'User', namespace: 'default', name: 'user1' }, }, { type: RELATION_OWNED_BY, + targetRef: 'group:default/group1', target: { kind: 'Group', namespace: 'default', name: 'group1' }, }, ], @@ -92,6 +90,7 @@ describe('useEntityOwnership', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:default/group1', target: { kind: 'Group', namespace: 'default', name: 'group1' }, }, ], @@ -101,18 +100,6 @@ describe('useEntityOwnership', () => { jest.resetAllMocks(); }); - describe('loadIdentityOwnerRefs', () => { - it('passes through the ownershipEntityRefs', async () => { - const refs = new Array(); - mockIdentityApi.getBackstageIdentity.mockResolvedValueOnce({ - type: 'user', - userEntityRef: 'user:default/guest', - ownershipEntityRefs: refs, - }); - await expect(loadIdentityOwnerRefs(identityApi)).resolves.toBe(refs); - }); - }); - describe('loadCatalogOwnerRefs', () => { it('loads the first user from the catalog', async () => { mockCatalogApi.getEntityByName.mockResolvedValueOnce(user2Entity); diff --git a/plugins/catalog-react/src/hooks/useEntityOwnership.ts b/plugins/catalog-react/src/hooks/useEntityOwnership.ts index 1138dada1f..77f6c4bf24 100644 --- a/plugins/catalog-react/src/hooks/useEntityOwnership.ts +++ b/plugins/catalog-react/src/hooks/useEntityOwnership.ts @@ -23,34 +23,12 @@ import { RELATION_OWNED_BY, stringifyEntityRef, } from '@backstage/catalog-model'; -import { - IdentityApi, - identityApiRef, - useApi, -} from '@backstage/core-plugin-api'; +import { identityApiRef, useApi } from '@backstage/core-plugin-api'; import { useMemo } from 'react'; import useAsync from 'react-use/lib/useAsync'; import { catalogApiRef } from '../api'; import { getEntityRelations } from '../utils/getEntityRelations'; -/** - * Takes the relevant parts of the Backstage identity, and translates them into - * a list of entity refs on string form that represent the user's ownership - * connections. - * - * @public - * @deprecated Use `ownershipEntityRefs` from `identityApi.getBackstageIdentity()` instead. - * - * @param identityApi - The IdentityApi implementation - * @returns IdentityOwner refs as a string array - */ -export async function loadIdentityOwnerRefs( - identityApi: IdentityApi, -): Promise { - const identity = await identityApi.getBackstageIdentity(); - return identity.ownershipEntityRefs; -} - /** * Takes the relevant parts of the User entity corresponding to the Backstage * identity, and translates them into a list of entity refs on string form that @@ -61,6 +39,7 @@ export async function loadIdentityOwnerRefs( * @param catalogApi - The Catalog API implementation * @param identityOwnerRefs - List of identity owner refs as strings * @returns OwnerRefs as a string array + * @deprecated Use `ownershipEntityRefs` from `identityApi.getBackstageIdentity()` instead. */ export async function loadCatalogOwnerRefs( catalogApi: CatalogApi, diff --git a/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx b/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx index c4531d9117..2159c30edc 100644 --- a/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx +++ b/plugins/catalog-react/src/hooks/useEntityTypeFilter.tsx @@ -17,12 +17,16 @@ import { useEffect, useMemo, useRef, useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; import isEqual from 'lodash/isEqual'; +import sortBy from 'lodash/sortBy'; import { useApi } from '@backstage/core-plugin-api'; import { catalogApiRef } from '../api'; -import { useEntityListProvider } from './useEntityListProvider'; +import { useEntityList } from './useEntityListProvider'; import { EntityTypeFilter } from '../filters'; -type EntityTypeReturn = { +/** @public + * @deprecated type inlined with {@link useEntityTypeFilter}. + */ +export type EntityTypeReturn = { loading: boolean; error?: Error; availableTypes: string[]; @@ -31,16 +35,23 @@ type EntityTypeReturn = { }; /** - * A hook built on top of `useEntityListProvider` for enabling selection of valid `spec.type` values + * A hook built on top of `useEntityList` for enabling selection of valid `spec.type` values * based on the selected EntityKindFilter. + * @public */ -export function useEntityTypeFilter(): EntityTypeReturn { +export function useEntityTypeFilter(): { + loading: boolean; + error?: Error; + availableTypes: string[]; + selectedTypes: string[]; + setSelectedTypes: (types: string[]) => void; +} { const catalogApi = useApi(catalogApiRef); const { filters: { kind: kindFilter, type: typeFilter }, queryParameters, updateFilters, - } = useEntityListProvider(); + } = useEntityList(); const queryParamTypes = useMemo( () => [queryParameters.type].flat().filter(Boolean) as string[], @@ -67,51 +78,34 @@ export function useEntityTypeFilter(): EntityTypeReturn { const { error, loading, - value: entities, + value: facets, } = useAsync(async () => { if (kind) { const items = await catalogApi - .getEntities({ + .getEntityFacets({ filter: { kind }, - fields: ['spec.type'], + facets: ['spec.type'], }) - .then(response => response.items); + .then(response => response.facets['spec.type'] || []); return items; } return []; }, [kind, catalogApi]); - const entitiesRef = useRef(entities); + const facetsRef = useRef(facets); useEffect(() => { - const oldEntities = entitiesRef.current; - entitiesRef.current = entities; - // Delay processing hook until kind and entity load updates have settled to generate list of types; - // This prevents reseting the type filter due to saved type value from query params not matching the + const oldFacets = facetsRef.current; + facetsRef.current = facets; + // Delay processing hook until kind and facets load updates have settled to generate list of types; + // This prevents resetting the type filter due to saved type value from query params not matching the // empty set of type values while values are still being loaded; also only run this hook on changes - // to entities - if (loading || !kind || oldEntities === entities) { + // to facets + if (loading || !kind || oldFacets === facets || !facets) { return; } - // Resolve the unique set of types from returned entities; could be optimized by a new endpoint - // in the catalog-backend that does this, rather than loading entities with redundant types. - if (!entities) return; - - // Sort by entity count descending, so the most common types appear on top - const countByType = entities.reduce((acc, entity) => { - if (typeof entity.spec?.type !== 'string') return acc; - - const entityType = entity.spec.type.toLocaleLowerCase('en-US'); - if (!acc[entityType]) { - acc[entityType] = 0; - } - acc[entityType] += 1; - return acc; - }, {} as Record); - - const newTypes = Object.entries(countByType) - .sort(([, count1], [, count2]) => count2 - count1) - .map(([type]) => type); + // Sort by facet count descending, so the most common types appear on top + const newTypes = sortBy(facets, f => -f.count).map(f => f.value); setAvailableTypes(newTypes); // Update type filter to only valid values when the list of available types has changed @@ -121,7 +115,7 @@ export function useEntityTypeFilter(): EntityTypeReturn { if (!isEqual(selectedTypes, stillValidTypes)) { setSelectedTypes(stillValidTypes); } - }, [loading, kind, selectedTypes, setSelectedTypes, entities]); + }, [loading, kind, selectedTypes, setSelectedTypes, facets]); useEffect(() => { updateFilters({ diff --git a/plugins/catalog-react/src/hooks/useOwnUser.ts b/plugins/catalog-react/src/hooks/useOwnUser.ts index 497f065f04..74c44bc638 100644 --- a/plugins/catalog-react/src/hooks/useOwnUser.ts +++ b/plugins/catalog-react/src/hooks/useOwnUser.ts @@ -25,6 +25,8 @@ import { identityApiRef, useApi } from '@backstage/core-plugin-api'; /** * Get the catalog User entity (if any) that matches the logged-in user. + * @public + * @deprecated due to low external usage. */ export function useOwnUser(): AsyncState { const catalogApi = useApi(catalogApiRef); diff --git a/plugins/catalog-react/src/hooks/useOwnedEntities.ts b/plugins/catalog-react/src/hooks/useOwnedEntities.ts index dad73740c1..a2258cf138 100644 --- a/plugins/catalog-react/src/hooks/useOwnedEntities.ts +++ b/plugins/catalog-react/src/hooks/useOwnedEntities.ts @@ -14,10 +14,7 @@ * limitations under the License. */ import { catalogApiRef } from './../api'; -import { - loadCatalogOwnerRefs, - loadIdentityOwnerRefs, -} from './useEntityOwnership'; +import { loadCatalogOwnerRefs } from './useEntityOwnership'; import { identityApiRef, useApi } from '@backstage/core-plugin-api'; import { RELATION_OWNED_BY } from '@backstage/catalog-model'; import { GetEntitiesResponse } from '@backstage/catalog-client'; @@ -32,6 +29,7 @@ import { useMemo } from 'react'; * @public * * @param allowedKinds - Array of allowed kinds to filter the entities + * @deprecated Use `ownershipEntityRefs` from `identityApi.getBackstageIdentity()` instead. */ export function useOwnedEntities(allowedKinds?: string[]): { loading: boolean; @@ -41,7 +39,8 @@ export function useOwnedEntities(allowedKinds?: string[]): { const catalogApi = useApi(catalogApiRef); const { loading, value: refs } = useAsync(async () => { - const identityRefs = await loadIdentityOwnerRefs(identityApi); + const identity = await identityApi.getBackstageIdentity(); + const identityRefs = identity.ownershipEntityRefs; const catalogRefs = await loadCatalogOwnerRefs(catalogApi, identityRefs); const catalogs = await catalogApi.getEntities( allowedKinds diff --git a/plugins/catalog-react/src/hooks/useRelatedEntities.ts b/plugins/catalog-react/src/hooks/useRelatedEntities.ts index 942941e3d5..4cf5690ac3 100644 --- a/plugins/catalog-react/src/hooks/useRelatedEntities.ts +++ b/plugins/catalog-react/src/hooks/useRelatedEntities.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity, EntityRelation } from '@backstage/catalog-model'; +import { Entity, parseEntityRef } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; import { chunk, groupBy } from 'lodash'; import useAsync from 'react-use/lib/useAsync'; @@ -21,30 +21,31 @@ import { catalogApiRef } from '../api'; const BATCH_SIZE = 20; +/** @public */ export function useRelatedEntities( entity: Entity, - { type, kind }: { type?: string; kind?: string }, + relationFilter: { type?: string; kind?: string }, ): { entities: Entity[] | undefined; loading: boolean; error: Error | undefined; } { + const filterByTypeLower = relationFilter?.type?.toLocaleLowerCase('en-US'); + const filterByKindLower = relationFilter?.kind?.toLocaleLowerCase('en-US'); + const catalogApi = useApi(catalogApiRef); const { loading, value: entities, error, } = useAsync(async () => { - const relations = - entity.relations && - entity.relations.filter( + const relations = entity.relations + ?.map(r => ({ type: r.type, target: parseEntityRef(r.targetRef) })) + .filter( r => - (!type || - r.type.toLocaleLowerCase('en-US') === - type.toLocaleLowerCase('en-US')) && - (!kind || - r.target.kind.toLocaleLowerCase('en-US') === - kind.toLocaleLowerCase('en-US')), + (!filterByTypeLower || + r.type.toLocaleLowerCase('en-US') === filterByTypeLower) && + (!filterByKindLower || r.target.kind === filterByKindLower), ); if (!relations) { @@ -56,7 +57,7 @@ export function useRelatedEntities( // `filter=kind=component,namespace=default,name=example1&filter=kind=component,namespace=default,name=example2` // with grouping, we can generate a query a string like // `filter=kind=component,namespace=default,name=example1,example2` - const relationsByKindAndNamespace: EntityRelation[][] = Object.values( + const relationsByKindAndNamespace = Object.values( groupBy(relations, ({ target }) => { return `${target.kind}:${target.namespace}`.toLocaleLowerCase('en-US'); }), @@ -95,7 +96,7 @@ export function useRelatedEntities( ); return results.flatMap(r => r.items); - }, [entity, type]); + }, [entity, filterByTypeLower, filterByKindLower]); return { entities, diff --git a/plugins/catalog-react/src/hooks/useStarredEntities.ts b/plugins/catalog-react/src/hooks/useStarredEntities.ts index 066040bf02..5ddabfdd2e 100644 --- a/plugins/catalog-react/src/hooks/useStarredEntities.ts +++ b/plugins/catalog-react/src/hooks/useStarredEntities.ts @@ -30,6 +30,7 @@ function getEntityRef(entityOrRef: Entity | EntityName | string): string { : stringifyEntityRef(entityOrRef); } +/** @public */ export function useStarredEntities(): { starredEntities: Set; toggleStarredEntity: (entityOrRef: Entity | EntityName | string) => void; diff --git a/plugins/catalog-react/src/hooks/useStarredEntity.ts b/plugins/catalog-react/src/hooks/useStarredEntity.ts index 0233432196..2c647163aa 100644 --- a/plugins/catalog-react/src/hooks/useStarredEntity.ts +++ b/plugins/catalog-react/src/hooks/useStarredEntity.ts @@ -29,6 +29,7 @@ function getEntityRef(entityOrRef: Entity | EntityName | string): string { : stringifyEntityRef(entityOrRef); } +/** @public */ export function useStarredEntity(entityOrRef: Entity | EntityName | string): { toggleStarredEntity: () => void; isStarredEntity: boolean; diff --git a/plugins/catalog-react/src/routes.ts b/plugins/catalog-react/src/routes.ts index 57801fab4a..b9aa0de531 100644 --- a/plugins/catalog-react/src/routes.ts +++ b/plugins/catalog-react/src/routes.ts @@ -21,6 +21,7 @@ import { getOrCreateGlobalSingleton } from '@backstage/version-bridge'; // TODO(Rugvip): Move these route refs back to the catalog plugin once we're all ported to using external routes /** * @deprecated Use an `ExternalRouteRef` instead, which can point to `catalogPlugin.routes.catalogIndex`. + * @public */ export const rootRoute = createRouteRef({ id: 'catalog', @@ -28,6 +29,7 @@ export const rootRoute = createRouteRef({ /** * @deprecated Use an `ExternalRouteRef` instead, which can point to `catalogPlugin.routes.catalogIndex`. + * @public */ export const catalogRouteRef = rootRoute; @@ -39,6 +41,7 @@ export const catalogRouteRef = rootRoute; * * If you want to replace the `EntityPage` from `@backstage/catalog-plugin` in your app, * you need to use the `entityRouteRef` as the mount point instead of your own. + * @public */ export const entityRouteRef = getOrCreateGlobalSingleton( 'catalog:entity-route-ref', @@ -51,11 +54,14 @@ export const entityRouteRef = getOrCreateGlobalSingleton( /** * @deprecated use `entityRouteRef` instead. + * @public */ export const entityRoute = entityRouteRef; -// Utility function to get suitable route params for entityRoute, given an -// entity instance +/** + * Utility function to get suitable route params for entityRoute, given an + * @public + */ export function entityRouteParams(entity: Entity) { return { kind: entity.kind.toLocaleLowerCase('en-US'), diff --git a/plugins/catalog-react/src/testUtils/providers.tsx b/plugins/catalog-react/src/testUtils/providers.tsx index 3fb295298c..5867026a8c 100644 --- a/plugins/catalog-react/src/testUtils/providers.tsx +++ b/plugins/catalog-react/src/testUtils/providers.tsx @@ -26,6 +26,7 @@ import { EntityListContextProps, } from '../hooks/useEntityListProvider'; +/** @public */ export const MockEntityListContextProvider = ({ children, value, diff --git a/plugins/catalog-react/src/types.ts b/plugins/catalog-react/src/types.ts index 964858cb10..41c7d75f0d 100644 --- a/plugins/catalog-react/src/types.ts +++ b/plugins/catalog-react/src/types.ts @@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; +/** @public */ export type EntityFilter = { /** * Get filters to add to the catalog-backend request. These are a dot-delimited field with @@ -37,10 +38,11 @@ export type EntityFilter = { /** * Serialize the filter value to a string for query params. The UI component responsible for - * handling this filter should retrieve this from useEntityListProvider.queryParameters. The + * handling this filter should retrieve this from useEntityList.queryParameters. The * value restored should be in the precedence: queryParameters `>` initialValue prop `>` default. */ toQueryValue?: () => string | string[]; }; +/** @public */ export type UserListFilterKind = 'owned' | 'starred' | 'all'; diff --git a/plugins/catalog-react/src/utils/filters.ts b/plugins/catalog-react/src/utils/filters.ts index 109e864c52..68cd94fd6e 100644 --- a/plugins/catalog-react/src/utils/filters.ts +++ b/plugins/catalog-react/src/utils/filters.ts @@ -17,6 +17,10 @@ import { Entity } from '@backstage/catalog-model'; import { EntityFilter } from '../types'; +/** + * @public + * @deprecated will be made private. + */ export function reduceCatalogFilters( filters: EntityFilter[], ): Record { @@ -28,6 +32,10 @@ export function reduceCatalogFilters( }, {} as Record); } +/** + * @public + * @deprecated will be made private. + */ export function reduceEntityFilters( filters: EntityFilter[], ): (entity: Entity) => boolean { diff --git a/plugins/catalog-react/src/utils/getEntityMetadataUrl.ts b/plugins/catalog-react/src/utils/getEntityMetadataUrl.ts index 3b44816ac1..a5cef7d71e 100644 --- a/plugins/catalog-react/src/utils/getEntityMetadataUrl.ts +++ b/plugins/catalog-react/src/utils/getEntityMetadataUrl.ts @@ -20,10 +20,12 @@ import { Entity, } from '@backstage/catalog-model'; +/** @public */ export function getEntityMetadataViewUrl(entity: Entity): string | undefined { return entity.metadata.annotations?.[ANNOTATION_VIEW_URL]; } +/** @public */ export function getEntityMetadataEditUrl(entity: Entity): string | undefined { return entity.metadata.annotations?.[ANNOTATION_EDIT_URL]; } diff --git a/plugins/catalog-react/src/utils/getEntityRelations.test.ts b/plugins/catalog-react/src/utils/getEntityRelations.test.ts index 22c695043f..ea0ba59399 100644 --- a/plugins/catalog-react/src/utils/getEntityRelations.test.ts +++ b/plugins/catalog-react/src/utils/getEntityRelations.test.ts @@ -31,17 +31,19 @@ describe('getEntityRelations', () => { relations: [ { type: RELATION_MEMBER_OF, - target: { kind: 'group', name: 'member' }, + targetRef: 'group:default/member', + target: { kind: 'group', namespace: 'default', name: 'member' }, }, { type: RELATION_CHILD_OF, - target: { kind: 'group', name: 'child' }, + targetRef: 'group:default/child', + target: { kind: 'group', namespace: 'default', name: 'child' }, }, ], } as Entity; expect(getEntityRelations(entity, RELATION_MEMBER_OF)).toEqual([ - { kind: 'group', name: 'member' }, + { kind: 'group', namespace: 'default', name: 'member' }, ]); }); @@ -50,17 +52,19 @@ describe('getEntityRelations', () => { relations: [ { type: RELATION_MEMBER_OF, - target: { kind: 'group', name: 'member' }, + targetRef: 'group:default/member', + target: { kind: 'group', namespace: 'default', name: 'member' }, }, { type: RELATION_MEMBER_OF, - target: { kind: 'user', name: 'child' }, + targetRef: 'user:default/child', + target: { kind: 'user', namespace: 'default', name: 'child' }, }, ], } as Entity; expect( getEntityRelations(entity, RELATION_MEMBER_OF, { kind: 'Group' }), - ).toEqual([{ kind: 'group', name: 'member' }]); + ).toEqual([{ kind: 'group', namespace: 'default', name: 'member' }]); }); }); diff --git a/plugins/catalog-react/src/utils/getEntityRelations.ts b/plugins/catalog-react/src/utils/getEntityRelations.ts index 0045fac4f5..45cbc6967f 100644 --- a/plugins/catalog-react/src/utils/getEntityRelations.ts +++ b/plugins/catalog-react/src/utils/getEntityRelations.ts @@ -14,10 +14,13 @@ * limitations under the License. */ -import { Entity, EntityName } from '@backstage/catalog-model'; +import { Entity, EntityName, parseEntityRef } from '@backstage/catalog-model'; +// TODO(freben): This should be returning entity refs instead /** * Get the related entity references. + * + * @public */ export function getEntityRelations( entity: Entity | undefined, @@ -27,10 +30,10 @@ export function getEntityRelations( let entityNames = entity?.relations ?.filter(r => r.type === relationType) - ?.map(r => r.target) || []; + .map(r => parseEntityRef(r.targetRef)) || []; if (filter?.kind) { - entityNames = entityNames?.filter( + entityNames = entityNames.filter( e => e.kind.toLocaleLowerCase('en-US') === filter.kind.toLocaleLowerCase('en-US'), diff --git a/plugins/catalog-react/src/utils/getEntitySourceLocation.ts b/plugins/catalog-react/src/utils/getEntitySourceLocation.ts index dd4d5c07f6..d2141449a8 100644 --- a/plugins/catalog-react/src/utils/getEntitySourceLocation.ts +++ b/plugins/catalog-react/src/utils/getEntitySourceLocation.ts @@ -21,11 +21,13 @@ import { } from '@backstage/catalog-model'; import { ScmIntegrationRegistry } from '@backstage/integration'; +/** @public */ export type EntitySourceLocation = { locationTargetUrl: string; integrationType?: string; }; +/** @public */ export function getEntitySourceLocation( entity: Entity, scmIntegrationsApi: ScmIntegrationRegistry, diff --git a/plugins/catalog-react/src/utils/isOwnerOf.test.ts b/plugins/catalog-react/src/utils/isOwnerOf.test.ts index 2cd6cfafa3..d3053646d8 100644 --- a/plugins/catalog-react/src/utils/isOwnerOf.test.ts +++ b/plugins/catalog-react/src/utils/isOwnerOf.test.ts @@ -31,6 +31,7 @@ describe('isOwnerOf', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/user', target: { kind: 'user', namespace: 'default', name: 'user' }, }, ], @@ -46,6 +47,7 @@ describe('isOwnerOf', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:default/group', target: { kind: 'group', namespace: 'default', name: 'group' }, }, ], @@ -54,6 +56,7 @@ describe('isOwnerOf', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/group', target: { kind: 'group', namespace: 'default', name: 'group' }, }, ], diff --git a/plugins/catalog-react/src/utils/isOwnerOf.ts b/plugins/catalog-react/src/utils/isOwnerOf.ts index 38ad90e4a6..3ef6b4b2a0 100644 --- a/plugins/catalog-react/src/utils/isOwnerOf.ts +++ b/plugins/catalog-react/src/utils/isOwnerOf.ts @@ -25,6 +25,7 @@ import { getEntityRelations } from './getEntityRelations'; /** * Get the related entity references. + * @public */ export function isOwnerOf(owner: Entity, owned: Entity) { const possibleOwners = new Set( diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index cb13e27bd0..3c70959b78 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,66 @@ # @backstage/plugin-catalog +## 0.9.0 + +### Minor Changes + +- e7b9c3d713: Removed `columnFactories`, which was accidentally exported on the previous release. +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. +- 3c4376e5e7: **BREAKING**: Removed the old `plugin` export, use `catalogPlugin` instead. + +### Patch Changes + +- 2d339b5f2c: Internalize deprecated `useEntityFromUrl` hook +- a686702dbe: Renamed `CatalogResultListItem` to `CatalogSearchResultListItem` along with its prop type, leaving the old names in place as a deprecations. +- 852d5ff758: Added `EntitySwitchProps` type for `EntitySwitch`. +- a8331830f1: Remove use of deprecated `useEntityKinds` hook. +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- b776ce5aab: Replaced use of deprecated `useEntityListProvider` hook with `useEntityList`. +- 3334ad47d4: Removed usage of `EntityContext`. +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration-react@0.1.23 + +## 0.8.0 + +### Minor Changes + +- ae7edbea45: Removed `CatalogClientWrapper` as usage have been replaced by the `fetchApiRef`. +- 37092662cb: **BREAKING** Completely removed the `EntitySystemDiagramCard` component which was deprecated in a previous release. Any remaining references to this component are now broken and should be replaced with `EntityCatalogGraphCard`, which can be imported from package `@backstage/plugin-catalog-graph`. +- 7657eeae22: Removed deprecated `Router` and `EntityPageLayout` exports. + `Router` is replaced by plugin extensions and `EntityPageLayout` is replaced by `CatalogEntityPage`. +- 0a033351be: **BREAKING**: + + - The `CatalogResultListItem` `result` prop is now of the more narrow and correct type `IndexableDocument`, rather than `any`. + - The individual table column factories (e.g. `createNameColumn`) are now no longer available directly, but only through `CatalogTable.columns`. + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 538ca90790: Use updated type names from `@backstage/catalog-client` +- 0d03e42a89: Replaced use of deprecated `compareEntityToRef` utility. +- 244d24ebc4: Import `Location` from the `@backstage/catalog-client` package. +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- 27eccab216: Replaces use of deprecated catalog-model constants. +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/catalog-client@0.7.0 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/integration-react@0.1.22 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/search-common@0.2.3 + - @backstage/theme@0.2.15 + - @backstage/plugin-catalog-common@0.1.3 + ## 0.7.12 ### Patch Changes diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index 99bd727961..bd5edcbfa6 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -11,9 +11,9 @@ import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { IconComponent } from '@backstage/core-plugin-api'; +import { IndexableDocument } from '@backstage/search-common'; import { InfoCardVariants } from '@backstage/core-components'; import { Overrides } from '@material-ui/core/styles/overrides'; -import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -23,28 +23,38 @@ import { TableProps } from '@backstage/core-components'; import { TabProps } from '@material-ui/core'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; -// Warning: (ae-forgotten-export) The symbol "AboutCardProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "AboutCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function AboutCard({ variant }: AboutCardProps): JSX.Element; +// @public @deprecated (undocumented) +export function AboutCard(props: AboutCardProps): JSX.Element; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "AboutContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const AboutContent: ({ entity }: Props) => JSX.Element; +// @public +export interface AboutCardProps { + // (undocumented) + variant?: InfoCardVariants; +} -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "AboutField" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const AboutField: ({ - label, - value, - gridSizes, - children, -}: Props_2) => JSX.Element; +export function AboutContent(props: AboutContentProps): JSX.Element; + +// @public +export interface AboutContentProps { + // (undocumented) + entity: Entity; +} + +// @public (undocumented) +export function AboutField(props: AboutFieldProps): JSX.Element; + +// @public +export interface AboutFieldProps { + // (undocumented) + children?: React_2.ReactNode; + // (undocumented) + gridSizes?: Record; + // (undocumented) + label: string; + // (undocumented) + value?: string; +} // @public (undocumented) export type BackstageOverrides = Overrides & { @@ -53,28 +63,23 @@ export type BackstageOverrides = Overrides & { >; }; -// Warning: (ae-missing-release-tag) "CatalogEntityPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const CatalogEntityPage: () => JSX.Element; -// Warning: (ae-missing-release-tag) "CatalogIndexPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const CatalogIndexPage: (props: DefaultCatalogPageProps) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "CatalogKindHeaderProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "CatalogKindHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const CatalogKindHeader: ({ - initialFilter, -}: CatalogKindHeaderProps) => JSX.Element; +export function CatalogKindHeader(props: CatalogKindHeaderProps): JSX.Element; + +// @public +export interface CatalogKindHeaderProps { + // (undocumented) + initialFilter?: string; +} -// Warning: (ae-missing-release-tag) "catalogPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -const catalogPlugin: BackstagePlugin< +export const catalogPlugin: BackstagePlugin< { catalogIndex: RouteRef; catalogEntity: RouteRef<{ @@ -95,27 +100,57 @@ const catalogPlugin: BackstagePlugin< >; } >; -export { catalogPlugin }; -export { catalogPlugin as plugin }; -// Warning: (ae-missing-release-tag) "CatalogResultListItem" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public @deprecated (undocumented) +export const CatalogResultListItem: typeof CatalogSearchResultListItem; + +// @public @deprecated (undocumented) +export type CatalogResultListItemProps = CatalogSearchResultListItemProps; + // @public (undocumented) -export const CatalogResultListItem: ({ result }: any) => JSX.Element; +export function CatalogSearchResultListItem( + props: CatalogSearchResultListItemProps, +): JSX.Element; + +// @public +export interface CatalogSearchResultListItemProps { + // (undocumented) + result: IndexableDocument; +} -// Warning: (ae-missing-release-tag) "CatalogTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const CatalogTable: { - ({ columns, actions }: CatalogTableProps): JSX.Element; - columns: typeof columnFactories; + (props: CatalogTableProps): JSX.Element; + columns: Readonly<{ + createNameColumn( + options?: + | { + defaultKind?: string | undefined; + } + | undefined, + ): TableColumn; + createSystemColumn(): TableColumn; + createOwnerColumn(): TableColumn; + createSpecTypeColumn(): TableColumn; + createSpecLifecycleColumn(): TableColumn; + createMetadataDescriptionColumn(): TableColumn; + createTagsColumn(): TableColumn; + }>; }; -// Warning: (ae-missing-release-tag) "EntityRow" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public +export interface CatalogTableProps { + // (undocumented) + actions?: TableProps['actions']; + // (undocumented) + columns?: TableColumn[]; +} + // @public (undocumented) -export type CatalogTableRow = { +export interface CatalogTableRow { + // (undocumented) entity: Entity; + // (undocumented) resolved: { name: string; partOfSystemRelationTitle?: string; @@ -123,230 +158,217 @@ export type CatalogTableRow = { ownedByRelationsTitle?: string; ownedByRelations: EntityName[]; }; -}; - -// Warning: (ae-missing-release-tag) "createMetadataDescriptionColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function createMetadataDescriptionColumn(): TableColumn; - -// Warning: (ae-forgotten-export) The symbol "NameColumnProps" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "createNameColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function createNameColumn( - props?: NameColumnProps, -): TableColumn; - -// Warning: (ae-missing-release-tag) "createOwnerColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function createOwnerColumn(): TableColumn; - -// Warning: (ae-missing-release-tag) "createSpecLifecycleColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function createSpecLifecycleColumn(): TableColumn; - -// Warning: (ae-missing-release-tag) "createSpecTypeColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function createSpecTypeColumn(): TableColumn; - -// Warning: (ae-missing-release-tag) "createSystemColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function createSystemColumn(): TableColumn; - -// Warning: (ae-missing-release-tag) "createTagsColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export function createTagsColumn(): TableColumn; +} // @public -export type DefaultCatalogPageProps = { - initiallySelectedFilter?: UserListFilterKind; - columns?: TableColumn[]; +export interface DefaultCatalogPageProps { + // (undocumented) actions?: TableProps['actions']; -}; + // (undocumented) + columns?: TableColumn[]; + // (undocumented) + initiallySelectedFilter?: UserListFilterKind; +} -// Warning: (ae-missing-release-tag) "EntityAboutCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const EntityAboutCard: AboutCard; +export interface DependencyOfComponentsCardProps { + // (undocumented) + title?: string; + // (undocumented) + variant?: 'gridItem'; +} -// Warning: (ae-missing-release-tag) "EntityDependencyOfComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const EntityDependencyOfComponentsCard: ({ - variant, - title, -}: { - variant?: 'gridItem' | undefined; - title?: string | undefined; -}) => JSX.Element; +export interface DependsOnComponentsCardProps { + // (undocumented) + title?: string; + // (undocumented) + variant?: 'gridItem'; +} -// Warning: (ae-missing-release-tag) "EntityDependsOnComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const EntityDependsOnComponentsCard: ({ - variant, - title, -}: { - variant?: 'gridItem' | undefined; - title?: string | undefined; -}) => JSX.Element; +export interface DependsOnResourcesCardProps { + // (undocumented) + variant?: 'gridItem'; +} -// Warning: (ae-missing-release-tag) "EntityDependsOnResourcesCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const EntityDependsOnResourcesCard: ({ - variant, -}: { - variant?: 'gridItem' | undefined; -}) => JSX.Element; +export const EntityAboutCard: (props: AboutCardProps) => JSX.Element; -// Warning: (ae-missing-release-tag) "EntityHasComponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const EntityHasComponentsCard: ({ - variant, -}: { - variant?: 'gridItem' | undefined; -}) => JSX.Element; +export const EntityDependencyOfComponentsCard: ( + props: DependencyOfComponentsCardProps, +) => JSX.Element; -// Warning: (ae-missing-release-tag) "EntityHasResourcesCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const EntityHasResourcesCard: ({ - variant, -}: { - variant?: 'gridItem' | undefined; -}) => JSX.Element; +export const EntityDependsOnComponentsCard: ( + props: DependsOnComponentsCardProps, +) => JSX.Element; -// Warning: (ae-missing-release-tag) "EntityHasSubcomponentsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const EntityHasSubcomponentsCard: ({ - variant, -}: { - variant?: 'gridItem' | undefined; -}) => JSX.Element; +export const EntityDependsOnResourcesCard: ( + props: DependsOnResourcesCardProps, +) => JSX.Element; -// Warning: (ae-missing-release-tag) "EntityHasSystemsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const EntityHasSystemsCard: ({ - variant, -}: { - variant?: 'gridItem' | undefined; -}) => JSX.Element; +export const EntityHasComponentsCard: ( + props: HasComponentsCardProps, +) => JSX.Element; + +// @public (undocumented) +export const EntityHasResourcesCard: ( + props: HasResourcesCardProps, +) => JSX.Element; + +// @public (undocumented) +export const EntityHasSubcomponentsCard: ( + props: HasSubcomponentsCardProps, +) => JSX.Element; + +// @public (undocumented) +export const EntityHasSystemsCard: (props: HasSystemsCardProps) => JSX.Element; -// Warning: (ae-missing-release-tag) "EntityLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export const EntityLayout: { - ({ - UNSTABLE_extraContextMenuItems, - UNSTABLE_contextMenuOptions, - children, - }: EntityLayoutProps): JSX.Element; - Route: (props: SubRoute) => null; + (props: EntityLayoutProps): JSX.Element; + Route: (props: EntityLayoutRouteProps) => null; }; -// Warning: (ae-missing-release-tag) "EntityLinksCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// @public (undocumented) +export interface EntityLayoutProps { + // (undocumented) + children?: React_2.ReactNode; + // Warning: (ae-forgotten-export) The symbol "contextMenuOptions" needs to be exported by the entry point index.d.ts + // + // (undocumented) + UNSTABLE_contextMenuOptions?: contextMenuOptions; + // Warning: (ae-forgotten-export) The symbol "ExtraContextMenuItem" needs to be exported by the entry point index.d.ts + // + // (undocumented) + UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[]; +} + +// @public (undocumented) +export type EntityLayoutRouteProps = { + path: string; + title: string; + children: JSX.Element; + if?: (entity: Entity) => boolean; + tabProps?: TabProps< + React_2.ElementType, + { + component?: React_2.ElementType; + } + >; +}; + +// Warning: (ae-forgotten-export) The symbol "EntityLinksCard" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export const EntityLinksCard: ({ - cols, - variant, -}: { - entity?: Entity | undefined; - cols?: number | ColumnBreakpoints | undefined; - variant?: 'gridItem' | undefined; -}) => JSX.Element; +export const EntityLinksCard: EntityLinksCard_2; + +// @public (undocumented) +export interface EntityLinksCardProps { + // Warning: (ae-forgotten-export) The symbol "ColumnBreakpoints" needs to be exported by the entry point index.d.ts + // + // (undocumented) + cols?: ColumnBreakpoints | number; + // (undocumented) + variant?: 'gridItem'; +} // @public (undocumented) export type EntityLinksEmptyStateClassKey = 'code'; -// Warning: (ae-missing-release-tag) "EntityListContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const EntityListContainer: ({ - children, -}: PropsWithChildren<{}>) => JSX.Element; +export function EntityListContainer(props: { + children: React_2.ReactNode; +}): JSX.Element; -// Warning: (ae-missing-release-tag) "EntityOrphanWarning" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public -export const EntityOrphanWarning: () => JSX.Element; +export function EntityOrphanWarning(): JSX.Element; -// Warning: (ae-missing-release-tag) "EntityProcessingErrorsPanel" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public -export const EntityProcessingErrorsPanel: () => JSX.Element | null; +export function EntityProcessingErrorsPanel(): JSX.Element | null; -// Warning: (ae-missing-release-tag) "EntitySwitch" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export const EntitySwitch: { - ({ children }: PropsWithChildren<{}>): JSX.Element | null; - Case: (_: { - if?: - | (( - entity: Entity, - context: { - apis: ApiHolder; - }, - ) => boolean | Promise) - | undefined; - children: ReactNode; - }) => null; + (props: EntitySwitchProps): JSX.Element | null; + Case: (_props: EntitySwitchCaseProps) => null; }; -// Warning: (ae-missing-release-tag) "FilterContainer" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const FilterContainer: ({ - children, -}: PropsWithChildren<{}>) => JSX.Element; +export interface EntitySwitchCaseProps { + // (undocumented) + children: ReactNode; + // (undocumented) + if?: ( + entity: Entity, + context: { + apis: ApiHolder; + }, + ) => boolean | Promise; +} -// Warning: (ae-missing-release-tag) "FilteredEntityLayout" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const FilteredEntityLayout: ({ - children, -}: PropsWithChildren<{}>) => JSX.Element; +// @public +export interface EntitySwitchProps { + // (undocumented) + children: ReactNode; +} -// Warning: (ae-missing-release-tag) "hasCatalogProcessingErrors" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const hasCatalogProcessingErrors: ( +export function FilterContainer(props: { + children: React_2.ReactNode; +}): JSX.Element; + +// @public (undocumented) +export function FilteredEntityLayout(props: { + children: React_2.ReactNode; +}): JSX.Element; + +// @public +export function hasCatalogProcessingErrors( entity: Entity, context: { apis: ApiHolder; }, -) => Promise; +): Promise; -// Warning: (ae-missing-release-tag) "isComponentType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) +export interface HasComponentsCardProps { + // (undocumented) + variant?: 'gridItem'; +} + +// @public (undocumented) +export interface HasResourcesCardProps { + // (undocumented) + variant?: 'gridItem'; +} + +// @public (undocumented) +export interface HasSubcomponentsCardProps { + // (undocumented) + variant?: 'gridItem'; +} + +// @public (undocumented) +export interface HasSystemsCardProps { + // (undocumented) + variant?: 'gridItem'; +} + +// @public export function isComponentType(type: string): (entity: Entity) => boolean; -// Warning: (ae-missing-release-tag) "isKind" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function isKind(kind: string): (entity: Entity) => boolean; -// Warning: (ae-missing-release-tag) "isNamespace" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function isNamespace(namespace: string): (entity: Entity) => boolean; -// Warning: (ae-missing-release-tag) "isOrphan" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const isOrphan: (entity: Entity) => boolean; +// @public +export function isOrphan(entity: Entity): boolean; // @public (undocumented) export type PluginCatalogComponentsNameToClassKey = { @@ -354,19 +376,22 @@ export type PluginCatalogComponentsNameToClassKey = { PluginCatalogSystemDiagramCard: SystemDiagramCardClassKey; }; -// Warning: (ae-missing-release-tag) "RelatedEntitiesCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) -export const RelatedEntitiesCard: (props: { - variant?: 'gridItem' | undefined; +export const RelatedEntitiesCard: ( + props: RelatedEntitiesCardProps, +) => JSX.Element; + +// @public (undocumented) +export type RelatedEntitiesCardProps = { + variant?: 'gridItem'; title: string; columns: TableColumn[]; - entityKind?: string | undefined; + entityKind?: string; relationType: string; emptyMessage: string; emptyHelpLink: string; asRenderableEntities: (entities: Entity[]) => T[]; -}) => JSX.Element; +}; // @public (undocumented) export type SystemDiagramCardClassKey = @@ -375,12 +400,4 @@ export type SystemDiagramCardClassKey = | 'componentNode' | 'apiNode' | 'resourceNode'; - -// Warnings were encountered during analysis: -// -// src/components/CatalogTable/CatalogTable.d.ts:10:5 - (ae-forgotten-export) The symbol "CatalogTableProps" needs to be exported by the entry point index.d.ts -// src/components/CatalogTable/CatalogTable.d.ts:11:5 - (ae-forgotten-export) The symbol "columnFactories" needs to be exported by the entry point index.d.ts -// src/components/EntityLayout/EntityLayout.d.ts:43:5 - (ae-forgotten-export) The symbol "EntityLayoutProps" needs to be exported by the entry point index.d.ts -// src/components/EntityLayout/EntityLayout.d.ts:44:5 - (ae-forgotten-export) The symbol "SubRoute" needs to be exported by the entry point index.d.ts -// src/plugin.d.ts:22:5 - (ae-forgotten-export) The symbol "ColumnBreakpoints" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index a2492aaba7..f7800af139 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "0.7.12", + "version": "0.9.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,25 +24,26 @@ "backstage" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/integration-react": "^0.1.21", - "@backstage/plugin-catalog-common": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/integration-react": "^0.1.23", + "@backstage/plugin-catalog-common": "^0.1.4", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/search-common": "^0.2.4", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -54,11 +58,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/plugin-permission-react": "^0.3.0", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/plugin-permission-react": "^0.3.2", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx index 4ef97b3d46..9a878e3b60 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx @@ -38,7 +38,7 @@ describe('', () => { getEntityByName: jest.fn(), getEntities: jest.fn(), addLocation: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), refreshEntity: jest.fn(), } as any; @@ -63,6 +63,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.tsx index f552d50a68..25383c8ad3 100644 --- a/plugins/catalog/src/components/AboutCard/AboutCard.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutCard.tsx @@ -15,7 +15,6 @@ */ import { - Entity, ANNOTATION_LOCATION, DEFAULT_NAMESPACE, stringifyEntityRef, @@ -72,13 +71,21 @@ const useStyles = makeStyles({ }, }); -type AboutCardProps = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; +/** + * Props for {@link AboutCard}. + * + * @public + */ +export interface AboutCardProps { variant?: InfoCardVariants; -}; +} -export function AboutCard({ variant }: AboutCardProps) { +/** + * @public + * @deprecated Please use EntityAboutCard instead + */ +export function AboutCard(props: AboutCardProps) { + const { variant } = props; const classes = useStyles(); const { entity } = useEntity(); const scmIntegrationsApi = useApi(scmIntegrationsApiRef); diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx index 860ef2c532..cf9673eeeb 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.test.tsx @@ -47,6 +47,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/o', target: { kind: 'user', name: 'o', @@ -55,6 +56,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'system:default/s', target: { kind: 'system', name: 's', @@ -63,6 +65,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'domain:default/d', target: { kind: 'domain', name: 'd', @@ -152,6 +155,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -160,6 +164,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'system:default/system', target: { kind: 'system', name: 'system', @@ -257,6 +262,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -265,6 +271,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'system:default/system', target: { kind: 'system', name: 'system', @@ -273,6 +280,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'component:default/parent-software', target: { kind: 'component', name: 'parent-software', @@ -369,6 +377,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -528,6 +537,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -536,6 +546,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'system:default/system', target: { kind: 'system', name: 'system', @@ -625,6 +636,7 @@ describe('', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'user:default/guest', target: { kind: 'user', name: 'guest', @@ -633,6 +645,7 @@ describe('', () => { }, { type: RELATION_PART_OF, + targetRef: 'domain:default/domain', target: { kind: 'domain', name: 'domain', diff --git a/plugins/catalog/src/components/AboutCard/AboutContent.tsx b/plugins/catalog/src/components/AboutCard/AboutContent.tsx index 9c8260c14d..ee1dad5d39 100644 --- a/plugins/catalog/src/components/AboutCard/AboutContent.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutContent.tsx @@ -33,11 +33,18 @@ const useStyles = makeStyles({ }, }); -type Props = { +/** + * Props for {@link AboutContent}. + * + * @public + */ +export interface AboutContentProps { entity: Entity; -}; +} -export const AboutContent = ({ entity }: Props) => { +/** @public */ +export function AboutContent(props: AboutContentProps) { + const { entity } = props; const classes = useStyles(); const isSystem = entity.kind.toLocaleLowerCase('en-US') === 'system'; const isResource = entity.kind.toLocaleLowerCase('en-US') === 'resource'; @@ -154,4 +161,4 @@ export const AboutContent = ({ entity }: Props) => { ); -}; +} diff --git a/plugins/catalog/src/components/AboutCard/AboutField.tsx b/plugins/catalog/src/components/AboutCard/AboutField.tsx index c4dd107434..6ceaa4fd7a 100644 --- a/plugins/catalog/src/components/AboutCard/AboutField.tsx +++ b/plugins/catalog/src/components/AboutCard/AboutField.tsx @@ -36,14 +36,21 @@ const useStyles = makeStyles(theme => ({ }, })); -type Props = { +/** + * Props for {@link AboutField}. + * + * @public + */ +export interface AboutFieldProps { label: string; value?: string; gridSizes?: Record; children?: React.ReactNode; -}; +} -export const AboutField = ({ label, value, gridSizes, children }: Props) => { +/** @public */ +export function AboutField(props: AboutFieldProps) { + const { label, value, gridSizes, children } = props; const classes = useStyles(); const childElements = useElementFilter(children, c => c.getElements()); @@ -65,4 +72,4 @@ export const AboutField = ({ label, value, gridSizes, children }: Props) => { {content} ); -}; +} diff --git a/plugins/catalog/src/components/AboutCard/index.ts b/plugins/catalog/src/components/AboutCard/index.ts index 84f6a02380..9660a5e2e2 100644 --- a/plugins/catalog/src/components/AboutCard/index.ts +++ b/plugins/catalog/src/components/AboutCard/index.ts @@ -15,5 +15,8 @@ */ export { AboutCard } from './AboutCard'; +export type { AboutCardProps } from './AboutCard'; export { AboutContent } from './AboutContent'; +export type { AboutContentProps } from './AboutContent'; export { AboutField } from './AboutField'; +export type { AboutFieldProps } from './AboutField'; diff --git a/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx b/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx index 94b9f5eee4..fe32e78959 100644 --- a/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx +++ b/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx @@ -16,13 +16,14 @@ import React from 'react'; import { Outlet } from 'react-router'; -import { - useEntityFromUrl, - AsyncEntityProvider, -} from '@backstage/plugin-catalog-react'; +import { AsyncEntityProvider } from '@backstage/plugin-catalog-react'; +import { useEntityFromUrl } from './useEntityFromUrl'; -export const CatalogEntityPage = () => ( - - - -); +/** @public */ +export function CatalogEntityPage() { + return ( + + + + ); +} diff --git a/plugins/catalog/src/components/CatalogEntityPage/index.ts b/plugins/catalog/src/components/CatalogEntityPage/index.ts index bac02a1529..f57648ae00 100644 --- a/plugins/catalog/src/components/CatalogEntityPage/index.ts +++ b/plugins/catalog/src/components/CatalogEntityPage/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { CatalogEntityPage } from './CatalogEntityPage'; diff --git a/plugins/catalog/src/components/CatalogEntityPage/useEntityFromUrl.ts b/plugins/catalog/src/components/CatalogEntityPage/useEntityFromUrl.ts new file mode 100644 index 0000000000..605305882e --- /dev/null +++ b/plugins/catalog/src/components/CatalogEntityPage/useEntityFromUrl.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2022 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 { + errorApiRef, + useApi, + useRouteRefParams, +} from '@backstage/core-plugin-api'; +import { + catalogApiRef, + EntityLoadingStatus, + entityRouteRef, +} from '@backstage/plugin-catalog-react'; +import { useEffect } from 'react'; +import { useNavigate } from 'react-router'; +import useAsyncRetry from 'react-use/lib/useAsyncRetry'; + +export const useEntityFromUrl = (): EntityLoadingStatus => { + const { kind, namespace, name } = useRouteRefParams(entityRouteRef); + const navigate = useNavigate(); + const errorApi = useApi(errorApiRef); + const catalogApi = useApi(catalogApiRef); + + const { + value: entity, + error, + loading, + retry: refresh, + } = useAsyncRetry( + () => catalogApi.getEntityByName({ kind, namespace, name }), + [catalogApi, kind, namespace, name], + ); + + useEffect(() => { + if (!name) { + errorApi.post(new Error('No name provided!')); + navigate('/'); + } + }, [errorApi, navigate, error, loading, entity, name]); + + return { entity, loading, error, refresh }; +}; diff --git a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx index b320d6d220..45d42d0ea6 100644 --- a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx +++ b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx @@ -16,6 +16,7 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; +import { GetEntityFacetsResponse } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { catalogApiRef, @@ -23,8 +24,13 @@ import { MockEntityListContextProvider, } from '@backstage/plugin-catalog-react'; import { ApiProvider } from '@backstage/core-app-api'; -import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { + MockErrorApi, + renderWithEffects, + TestApiRegistry, +} from '@backstage/test-utils'; import { CatalogKindHeader } from './CatalogKindHeader'; +import { errorApiRef } from '@backstage/core-plugin-api'; const entities: Entity[] = [ { @@ -56,13 +62,24 @@ const entities: Entity[] = [ }, }, ]; - -const apis = TestApiRegistry.from([ - catalogApiRef, - { - getEntities: jest.fn().mockResolvedValue({ items: entities }), - }, -]); +const errorApi = new MockErrorApi(); +const apis = TestApiRegistry.from( + [ + catalogApiRef, + { + getEntityFacets: jest.fn().mockResolvedValue({ + facets: { + kind: [ + { value: 'Component', count: 2 }, + { value: 'Template', count: 1 }, + { value: 'System', count: 1 }, + ], + }, + } as GetEntityFacetsResponse), + }, + ], + [errorApiRef, errorApi], +); describe('', () => { it('renders available kinds', async () => { diff --git a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.tsx b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.tsx index 41c5888d79..ed54f1fa84 100644 --- a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.tsx +++ b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.tsx @@ -25,10 +25,12 @@ import { Theme, } from '@material-ui/core'; import { + catalogApiRef, EntityKindFilter, - useEntityKinds, - useEntityListProvider, + useEntityList, } from '@backstage/plugin-catalog-react'; +import useAsync from 'react-use/lib/useAsync'; +import { useApi } from '@backstage/core-plugin-api'; const useStyles = makeStyles((theme: Theme) => createStyles({ @@ -38,16 +40,26 @@ const useStyles = makeStyles((theme: Theme) => }), ); -type CatalogKindHeaderProps = { +/** + * Props for {@link CatalogKindHeader}. + * + * @public + */ +export interface CatalogKindHeaderProps { initialFilter?: string; -}; +} -export const CatalogKindHeader = ({ - initialFilter = 'component', -}: CatalogKindHeaderProps) => { +/** @public */ +export function CatalogKindHeader(props: CatalogKindHeaderProps) { + const { initialFilter = 'component' } = props; const classes = useStyles(); - const { kinds: allKinds = [] } = useEntityKinds(); - const { updateFilters, queryParameters } = useEntityListProvider(); + const catalogApi = useApi(catalogApiRef); + const { value: allKinds } = useAsync(async () => { + return await catalogApi + .getEntityFacets({ facets: ['kind'] }) + .then(response => response.facets.kind?.map(f => f.value).sort() || []); + }); + const { updateFilters, queryParameters } = useEntityList(); const [selectedKind, setSelectedKind] = useState( ([queryParameters.kind].flat()[0] ?? initialFilter).toLocaleLowerCase( @@ -67,7 +79,7 @@ export const CatalogKindHeader = ({ // including selectedKind if it's unknown - but allows the selectedKind to get clobbered by the // more proper catalog kind if it exists. const options = [capitalize(selectedKind)] - .concat(allKinds) + .concat(allKinds ?? []) .sort() .reduce((acc, kind) => { acc[kind.toLocaleLowerCase('en-US')] = kind; @@ -88,4 +100,4 @@ export const CatalogKindHeader = ({ ))} ); -}; +} diff --git a/plugins/catalog/src/components/CatalogKindHeader/index.ts b/plugins/catalog/src/components/CatalogKindHeader/index.ts index 0078207d58..5a5b20f1a0 100644 --- a/plugins/catalog/src/components/CatalogKindHeader/index.ts +++ b/plugins/catalog/src/components/CatalogKindHeader/index.ts @@ -13,4 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { CatalogKindHeader } from './CatalogKindHeader'; +export type { CatalogKindHeaderProps } from './CatalogKindHeader'; diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx index cf49cff244..155bcc05df 100644 --- a/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.tsx @@ -21,8 +21,8 @@ import { DefaultCatalogPageProps, } from './DefaultCatalogPage'; -export const CatalogPage = (props: DefaultCatalogPageProps) => { +export function CatalogPage(props: DefaultCatalogPageProps) { const outlet = useOutlet(); return outlet || ; -}; +} diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx index 20acff3c50..dcf819f976 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.test.tsx @@ -44,7 +44,7 @@ import DashboardIcon from '@material-ui/icons/Dashboard'; import { fireEvent, screen } from '@testing-library/react'; import React from 'react'; import { createComponentRouteRef } from '../../routes'; -import { EntityRow } from '../CatalogTable'; +import { CatalogTableRow } from '../CatalogTable'; import { DefaultCatalogPage } from './DefaultCatalogPage'; describe('DefaultCatalogPage', () => { @@ -73,7 +73,8 @@ describe('DefaultCatalogPage', () => { relations: [ { type: RELATION_OWNED_BY, - target: { kind: 'Group', name: 'tools', namespace: 'default' }, + targetRef: 'group:default/tools', + target: { kind: 'group', name: 'tools', namespace: 'default' }, }, ], }, @@ -90,8 +91,9 @@ describe('DefaultCatalogPage', () => { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/not-tools', target: { - kind: 'Group', + kind: 'group', name: 'not-tools', namespace: 'default', }, @@ -100,8 +102,8 @@ describe('DefaultCatalogPage', () => { }, ] as Entity[], }), - getLocationByEntity: () => - Promise.resolve({ id: 'id', type: 'github', target: 'url' }), + getLocationByRef: () => + Promise.resolve({ id: 'id', type: 'url', target: 'url' }), getEntityByName: async entityName => { return { apiVersion: 'backstage.io/v1alpha1', @@ -110,6 +112,7 @@ describe('DefaultCatalogPage', () => { relations: [ { type: RELATION_MEMBER_OF, + targetRef: 'group:default/tools', target: { namespace: 'default', kind: 'Group', name: 'tools' }, }, ], @@ -180,7 +183,7 @@ describe('DefaultCatalogPage', () => { }, 20_000); it('should render the custom column passed as prop', async () => { - const columns: TableColumn[] = [ + const columns: TableColumn[] = [ { title: 'Foo', field: 'entity.foo' }, { title: 'Bar', field: 'entity.bar' }, { title: 'Baz', field: 'entity.spec.lifecycle' }, @@ -208,7 +211,7 @@ describe('DefaultCatalogPage', () => { }, 20_000); it('should render the custom actions of an item passed as prop', async () => { - const actions: TableProps['actions'] = [ + const actions: TableProps['actions'] = [ () => { return { icon: () => , diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index 1453a39bbc..8bb9bae764 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -35,8 +35,7 @@ import { } from '@backstage/plugin-catalog-react'; import React from 'react'; import { createComponentRouteRef } from '../../routes'; -import { CatalogTable } from '../CatalogTable'; -import { EntityRow } from '../CatalogTable/types'; +import { CatalogTable, CatalogTableRow } from '../CatalogTable'; import { FilteredEntityLayout, EntityListContainer, @@ -45,20 +44,18 @@ import { import { CatalogKindHeader } from '../CatalogKindHeader'; /** - * DefaultCatalogPageProps + * Props for root catalog pages. + * * @public */ -export type DefaultCatalogPageProps = { +export interface DefaultCatalogPageProps { initiallySelectedFilter?: UserListFilterKind; - columns?: TableColumn[]; - actions?: TableProps['actions']; -}; + columns?: TableColumn[]; + actions?: TableProps['actions']; +} -export const DefaultCatalogPage = ({ - columns, - actions, - initiallySelectedFilter = 'owned', -}: DefaultCatalogPageProps) => { +export function DefaultCatalogPage(props: DefaultCatalogPageProps) { + const { columns, actions, initiallySelectedFilter = 'owned' } = props; const orgName = useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; const createComponentLink = useRouteRef(createComponentRouteRef); @@ -90,4 +87,4 @@ export const DefaultCatalogPage = ({ ); -}; +} diff --git a/plugins/catalog/src/components/CatalogPage/index.ts b/plugins/catalog/src/components/CatalogPage/index.ts index ca6a13c034..9a3d083f41 100644 --- a/plugins/catalog/src/components/CatalogPage/index.ts +++ b/plugins/catalog/src/components/CatalogPage/index.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { CatalogPage } from './CatalogPage'; export { DefaultCatalogPage } from './DefaultCatalogPage'; export type { DefaultCatalogPageProps } from './DefaultCatalogPage'; diff --git a/plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx similarity index 70% rename from plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx rename to plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx index f0a32ec0e2..54cfe43364 100644 --- a/plugins/catalog/src/components/CatalogResultListItem/CatalogResultListItem.tsx +++ b/plugins/catalog/src/components/CatalogSearchResultListItem/CatalogSearchResultListItem.tsx @@ -24,6 +24,7 @@ import { makeStyles, } from '@material-ui/core'; import { Link } from '@backstage/core-components'; +import { IndexableDocument } from '@backstage/search-common'; const useStyles = makeStyles({ flexContainer: { @@ -36,7 +37,21 @@ const useStyles = makeStyles({ }, }); -export const CatalogResultListItem = ({ result }: any) => { +/** + * Props for {@link CatalogSearchResultListItem}. + * + * @public + */ +export interface CatalogSearchResultListItemProps { + result: IndexableDocument; +} + +/** @public */ +export function CatalogSearchResultListItem( + props: CatalogSearchResultListItemProps, +) { + const result = props.result as any; + const classes = useStyles(); return ( @@ -57,4 +72,16 @@ export const CatalogResultListItem = ({ result }: any) => { ); -}; +} + +/** + * @public + * @deprecated use {@link CatalogSearchResultListItemProps} instead + */ +export type CatalogResultListItemProps = CatalogSearchResultListItemProps; + +/** + * @public + * @deprecated use {@link CatalogSearchResultListItem} instead + */ +export const CatalogResultListItem = CatalogSearchResultListItem; diff --git a/plugins/catalog/src/components/CatalogSearchResultListItem/index.ts b/plugins/catalog/src/components/CatalogSearchResultListItem/index.ts new file mode 100644 index 0000000000..0396117825 --- /dev/null +++ b/plugins/catalog/src/components/CatalogSearchResultListItem/index.ts @@ -0,0 +1,24 @@ +/* + * 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 { + CatalogSearchResultListItem, + CatalogResultListItem, +} from './CatalogSearchResultListItem'; +export type { + CatalogSearchResultListItemProps, + CatalogResultListItemProps, +} from './CatalogSearchResultListItem'; diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index f293710847..c1e6988016 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -21,15 +21,15 @@ import { getEntityMetadataEditUrl, getEntityMetadataViewUrl, getEntityRelations, - useEntityListProvider, + useEntityList, useStarredEntities, } from '@backstage/plugin-catalog-react'; import Edit from '@material-ui/icons/Edit'; import OpenInNew from '@material-ui/icons/OpenInNew'; import { capitalize } from 'lodash'; import React, { useMemo } from 'react'; -import * as columnFactories from './columns'; -import { EntityRow } from './types'; +import { columnFactories } from './columns'; +import { CatalogTableRow } from './types'; import { CodeSnippet, Table, @@ -38,16 +38,23 @@ import { WarningPanel, } from '@backstage/core-components'; -type CatalogTableProps = { - columns?: TableColumn[]; - actions?: TableProps['actions']; -}; +/** + * Props for {@link CatalogTable}. + * + * @public + */ +export interface CatalogTableProps { + columns?: TableColumn[]; + actions?: TableProps['actions']; +} -export const CatalogTable = ({ columns, actions }: CatalogTableProps) => { +/** @public */ +export const CatalogTable = (props: CatalogTableProps) => { + const { columns, actions } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); - const { loading, error, entities, filters } = useEntityListProvider(); + const { loading, error, entities, filters } = useEntityList(); - const defaultColumns: TableColumn[] = useMemo( + const defaultColumns: TableColumn[] = useMemo( () => [ columnFactories.createNameColumn({ defaultKind: filters.kind?.value }), columnFactories.createSystemColumn(), @@ -77,7 +84,7 @@ export const CatalogTable = ({ columns, actions }: CatalogTableProps) => { ); } - const defaultActions: TableProps['actions'] = [ + const defaultActions: TableProps['actions'] = [ ({ entity }) => { const url = getEntityMetadataViewUrl(entity); return { @@ -148,7 +155,7 @@ export const CatalogTable = ({ columns, actions }: CatalogTableProps) => { const showPagination = rows.length > 20; return ( - + isLoading={loading} columns={columns || defaultColumns} options={{ diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index 683ac33132..d243be7c6f 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -20,120 +20,115 @@ import { EntityRefLinks, } from '@backstage/plugin-catalog-react'; import { Chip } from '@material-ui/core'; -import { EntityRow } from './types'; +import { CatalogTableRow } from './types'; import { OverflowTooltip, TableColumn } from '@backstage/core-components'; import { Entity } from '@backstage/catalog-model'; -type NameColumnProps = { - defaultKind?: string; -}; +// The columnFactories symbol is not directly exported, but through the +// CatalogTable.columns field. +/** @public */ +export const columnFactories = Object.freeze({ + createNameColumn(options?: { + defaultKind?: string; + }): TableColumn { + function formatContent(entity: Entity): string { + return ( + entity.metadata?.title || + formatEntityRefTitle(entity, { + defaultKind: options?.defaultKind, + }) + ); + } -export function createNameColumn( - props?: NameColumnProps, -): TableColumn { - function formatContent(entity: Entity): string { - return ( - entity.metadata?.title || - formatEntityRefTitle(entity, { - defaultKind: props?.defaultKind, - }) - ); - } - - return { - title: 'Name', - field: 'resolved.name', - highlight: true, - customSort({ entity: entity1 }, { entity: entity2 }) { - // TODO: We could implement this more efficiently by comparing field by field. - // This has similar issues as above. - return formatContent(entity1).localeCompare(formatContent(entity2)); - }, - render: ({ entity }) => ( - - ), - }; -} - -export function createSystemColumn(): TableColumn { - return { - title: 'System', - field: 'resolved.partOfSystemRelationTitle', - render: ({ resolved }) => ( - - ), - }; -} - -export function createOwnerColumn(): TableColumn { - return { - title: 'Owner', - field: 'resolved.ownedByRelationsTitle', - render: ({ resolved }) => ( - - ), - }; -} - -export function createSpecTypeColumn(): TableColumn { - return { - title: 'Type', - field: 'entity.spec.type', - hidden: true, - }; -} - -export function createSpecLifecycleColumn(): TableColumn { - return { - title: 'Lifecycle', - field: 'entity.spec.lifecycle', - }; -} - -export function createMetadataDescriptionColumn(): TableColumn { - return { - title: 'Description', - field: 'entity.metadata.description', - render: ({ entity }) => ( - - ), - width: 'auto', - }; -} - -export function createTagsColumn(): TableColumn { - return { - title: 'Tags', - field: 'entity.metadata.tags', - cellStyle: { - padding: '0px 16px 0px 20px', - }, - render: ({ entity }) => ( - <> - {entity.metadata.tags && - entity.metadata.tags.map(t => ( - - ))} - - ), - }; -} + return { + title: 'Name', + field: 'resolved.name', + highlight: true, + customSort({ entity: entity1 }, { entity: entity2 }) { + // TODO: We could implement this more efficiently by comparing field by field. + // This has similar issues as above. + return formatContent(entity1).localeCompare(formatContent(entity2)); + }, + render: ({ entity }) => ( + + ), + }; + }, + createSystemColumn(): TableColumn { + return { + title: 'System', + field: 'resolved.partOfSystemRelationTitle', + render: ({ resolved }) => ( + + ), + }; + }, + createOwnerColumn(): TableColumn { + return { + title: 'Owner', + field: 'resolved.ownedByRelationsTitle', + render: ({ resolved }) => ( + + ), + }; + }, + createSpecTypeColumn(): TableColumn { + return { + title: 'Type', + field: 'entity.spec.type', + hidden: true, + }; + }, + createSpecLifecycleColumn(): TableColumn { + return { + title: 'Lifecycle', + field: 'entity.spec.lifecycle', + }; + }, + createMetadataDescriptionColumn(): TableColumn { + return { + title: 'Description', + field: 'entity.metadata.description', + render: ({ entity }) => ( + + ), + width: 'auto', + }; + }, + createTagsColumn(): TableColumn { + return { + title: 'Tags', + field: 'entity.metadata.tags', + cellStyle: { + padding: '0px 16px 0px 20px', + }, + render: ({ entity }) => ( + <> + {entity.metadata.tags && + entity.metadata.tags.map(t => ( + + ))} + + ), + }; + }, +}); diff --git a/plugins/catalog/src/components/CatalogTable/index.ts b/plugins/catalog/src/components/CatalogTable/index.ts index e1660d6c5a..4e1b90f80a 100644 --- a/plugins/catalog/src/components/CatalogTable/index.ts +++ b/plugins/catalog/src/components/CatalogTable/index.ts @@ -15,4 +15,5 @@ */ export { CatalogTable } from './CatalogTable'; -export type { EntityRow } from './types'; +export type { CatalogTableProps } from './CatalogTable'; +export type { CatalogTableRow } from './types'; diff --git a/plugins/catalog/src/components/CatalogTable/types.ts b/plugins/catalog/src/components/CatalogTable/types.ts index 85926becc3..7eaff6faed 100644 --- a/plugins/catalog/src/components/CatalogTable/types.ts +++ b/plugins/catalog/src/components/CatalogTable/types.ts @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { Entity, EntityName } from '@backstage/catalog-model'; -export type EntityRow = { +/** @public */ +export interface CatalogTableRow { entity: Entity; resolved: { name: string; @@ -24,4 +26,4 @@ export type EntityRow = { ownedByRelationsTitle?: string; ownedByRelations: EntityName[]; }; -}; +} diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx index e43104b67d..05d836870f 100644 --- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx +++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_DEPENDENCY_OF, }, ], diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx index 34057d7bed..b85a446b75 100644 --- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx +++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.tsx @@ -23,15 +23,16 @@ import { RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -type Props = { +/** @public */ +export interface DependencyOfComponentsCardProps { variant?: 'gridItem'; title?: string; -}; +} -export const DependencyOfComponentsCard = ({ - variant = 'gridItem', - title = 'Dependency of components', -}: Props) => { +export function DependencyOfComponentsCard( + props: DependencyOfComponentsCardProps, +) { + const { variant = 'gridItem', title = 'Dependency of components' } = props; return ( ); -}; +} diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts b/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts index bf953fe986..3d324e865d 100644 --- a/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts +++ b/plugins/catalog/src/components/DependencyOfComponentsCard/index.ts @@ -15,3 +15,4 @@ */ export { DependencyOfComponentsCard } from './DependencyOfComponentsCard'; +export type { DependencyOfComponentsCardProps } from './DependencyOfComponentsCard'; diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx index b654136d18..b7ef594522 100644 --- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx +++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_DEPENDS_ON, }, ], diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx index 7e176139ad..33e8caf870 100644 --- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx +++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.tsx @@ -23,15 +23,14 @@ import { RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -type Props = { +/** @public */ +export interface DependsOnComponentsCardProps { variant?: 'gridItem'; title?: string; -}; +} -export const DependsOnComponentsCard = ({ - variant = 'gridItem', - title = 'Depends on components', -}: Props) => { +export function DependsOnComponentsCard(props: DependsOnComponentsCardProps) { + const { variant = 'gridItem', title = 'Depends on components' } = props; return ( ); -}; +} diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/index.ts b/plugins/catalog/src/components/DependsOnComponentsCard/index.ts index 96525fe241..8292526270 100644 --- a/plugins/catalog/src/components/DependsOnComponentsCard/index.ts +++ b/plugins/catalog/src/components/DependsOnComponentsCard/index.ts @@ -15,3 +15,4 @@ */ export { DependsOnComponentsCard } from './DependsOnComponentsCard'; +export type { DependsOnComponentsCardProps } from './DependsOnComponentsCard'; diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx index 40685705aa..b8d2801e48 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx +++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Resource', + kind: 'resource', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'resource:my-namespace/target-name', type: RELATION_DEPENDS_ON, }, ], diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx index 34a8e5824d..3fe29093fe 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx +++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx @@ -23,11 +23,13 @@ import { resourceEntityColumns, } from '../RelatedEntitiesCard'; -type Props = { +/** @public */ +export interface DependsOnResourcesCardProps { variant?: 'gridItem'; -}; +} -export const DependsOnResourcesCard = ({ variant = 'gridItem' }: Props) => { +export function DependsOnResourcesCard(props: DependsOnResourcesCardProps) { + const { variant = 'gridItem' } = props; return ( { asRenderableEntities={asResourceEntities} /> ); -}; +} diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/index.ts b/plugins/catalog/src/components/DependsOnResourcesCard/index.ts index 5ff94d3542..8d638e16a2 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/index.ts +++ b/plugins/catalog/src/components/DependsOnResourcesCard/index.ts @@ -15,3 +15,4 @@ */ export { DependsOnResourcesCard } from './DependsOnResourcesCard'; +export type { DependsOnResourcesCardProps } from './DependsOnResourcesCard'; diff --git a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx index 7bda1f4629..ee677c5238 100644 --- a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx @@ -41,30 +41,31 @@ const useStyles = makeStyles({ // NOTE(freben): Intentionally not exported at this point, since it's part of // the unstable extra context menu items concept below -type ExtraContextMenuItem = { +interface ExtraContextMenuItem { title: string; Icon: IconComponent; onClick: () => void; -}; +} // unstable context menu option, eg: disable the unregister entity menu -type contextMenuOptions = { +interface contextMenuOptions { disableUnregister: boolean; -}; +} -type Props = { +interface EntityContextMenuProps { UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[]; UNSTABLE_contextMenuOptions?: contextMenuOptions; onUnregisterEntity: () => void; onInspectEntity: () => void; -}; +} -export const EntityContextMenu = ({ - UNSTABLE_extraContextMenuItems, - UNSTABLE_contextMenuOptions, - onUnregisterEntity, - onInspectEntity, -}: Props) => { +export function EntityContextMenu(props: EntityContextMenuProps) { + const { + UNSTABLE_extraContextMenuItems, + UNSTABLE_contextMenuOptions, + onUnregisterEntity, + onInspectEntity, + } = props; const [anchorEl, setAnchorEl] = useState(); const classes = useStyles(); const unregisterPermission = useEntityPermission( @@ -150,4 +151,4 @@ export const EntityContextMenu = ({ ); -}; +} diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx index 0e93e74f2d..634662f386 100644 --- a/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx +++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.tsx @@ -35,21 +35,22 @@ import { useElementFilter, } from '@backstage/core-plugin-api'; import { - EntityContext, EntityRefLinks, FavoriteEntity, getEntityRelations, InspectEntityDialog, UnregisterEntityDialog, + useEntity, useEntityCompoundName, } from '@backstage/plugin-catalog-react'; import { Box, TabProps } from '@material-ui/core'; import { Alert } from '@material-ui/lab'; -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { useLocation, useNavigate } from 'react-router'; import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; -type SubRoute = { +/** @public */ +export type EntityLayoutRouteProps = { path: string; title: string; children: JSX.Element; @@ -59,19 +60,15 @@ type SubRoute = { const dataKey = 'plugin.catalog.entityLayoutRoute'; -const Route: (props: SubRoute) => null = () => null; +const Route: (props: EntityLayoutRouteProps) => null = () => null; attachComponentData(Route, dataKey, true); +attachComponentData(Route, 'core.gatherMountPoints', true); // This causes all mount points that are discovered within this route to use the path of the route itself -// This causes all mount points that are discovered within this route to use the path of the route itself -attachComponentData(Route, 'core.gatherMountPoints', true); - -const EntityLayoutTitle = ({ - entity, - title, -}: { +function EntityLayoutTitle(props: { title: string; entity: Entity | undefined; -}) => { +}) { + const { entity, title } = props; return ( } ); -}; +} -const headerProps = ( +function headerProps( paramKind: string | undefined, paramNamespace: string | undefined, paramName: string | undefined, entity: Entity | undefined, -): { headerTitle: string; headerType: string } => { +): { headerTitle: string; headerType: string } { const kind = paramKind ?? entity?.kind ?? ''; const namespace = paramNamespace ?? entity?.metadata.namespace ?? ''; const name = @@ -110,9 +107,10 @@ const headerProps = ( return t; })(), }; -}; +} -const EntityLabels = ({ entity }: { entity: Entity }) => { +function EntityLabels(props: { entity: Entity }) { + const { entity } = props; const ownedByRelations = getEntityRelations(entity, RELATION_OWNED_BY); return ( <> @@ -133,26 +131,27 @@ const EntityLabels = ({ entity }: { entity: Entity }) => { )} ); -}; +} // NOTE(freben): Intentionally not exported at this point, since it's part of // the unstable extra context menu items concept below -type ExtraContextMenuItem = { +interface ExtraContextMenuItem { title: string; Icon: IconComponent; onClick: () => void; -}; +} // unstable context menu option, eg: disable the unregister entity menu -type contextMenuOptions = { +interface contextMenuOptions { disableUnregister: boolean; -}; +} -type EntityLayoutProps = { +/** @public */ +export interface EntityLayoutProps { UNSTABLE_extraContextMenuItems?: ExtraContextMenuItem[]; UNSTABLE_contextMenuOptions?: contextMenuOptions; children?: React.ReactNode; -}; +} /** * EntityLayout is a compound component, which allows you to define a layout for @@ -168,14 +167,17 @@ type EntityLayoutProps = { * * * ``` + * + * @public */ -export const EntityLayout = ({ - UNSTABLE_extraContextMenuItems, - UNSTABLE_contextMenuOptions, - children, -}: EntityLayoutProps) => { +export const EntityLayout = (props: EntityLayoutProps) => { + const { + UNSTABLE_extraContextMenuItems, + UNSTABLE_contextMenuOptions, + children, + } = props; const { kind, namespace, name } = useEntityCompoundName(); - const { entity, loading, error } = useContext(EntityContext); + const { entity, loading, error } = useEntity(); const location = useLocation(); const routes = useElementFilter( children, @@ -186,18 +188,18 @@ export const EntityLayout = ({ withStrictError: 'Child of EntityLayout must be an EntityLayout.Route', }) - .getElements() // all nodes, element data, maintain structure or not? - .flatMap(({ props }) => { - if (props.if && entity && !props.if(entity)) { + .getElements() // all nodes, element data, maintain structure or not? + .flatMap(({ props: elementProps }) => { + if (elementProps.if && entity && !elementProps.if(entity)) { return []; } return [ { - path: props.path, - title: props.title, - children: props.children, - tabProps: props.tabProps, + path: elementProps.path, + title: elementProps.title, + children: elementProps.children, + tabProps: elementProps.tabProps, }, ]; }), diff --git a/plugins/catalog/src/components/EntityLayout/index.ts b/plugins/catalog/src/components/EntityLayout/index.ts index 3b0962ae36..251d213637 100644 --- a/plugins/catalog/src/components/EntityLayout/index.ts +++ b/plugins/catalog/src/components/EntityLayout/index.ts @@ -13,4 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { EntityLayout } from './EntityLayout'; +export type { EntityLayoutProps, EntityLayoutRouteProps } from './EntityLayout'; diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx index 20229ceb14..4c4a1ac908 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksCard.tsx @@ -14,25 +14,23 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; import LanguageIcon from '@material-ui/icons/Language'; import React from 'react'; import { EntityLinksEmptyState } from './EntityLinksEmptyState'; import { LinksGridList } from './LinksGridList'; import { ColumnBreakpoints } from './types'; - import { IconComponent, useApp } from '@backstage/core-plugin-api'; import { InfoCard } from '@backstage/core-components'; -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; +/** @public */ +export interface EntityLinksCardProps { cols?: ColumnBreakpoints | number; variant?: 'gridItem'; -}; +} -export const EntityLinksCard = ({ cols = undefined, variant }: Props) => { +export function EntityLinksCard(props: EntityLinksCardProps) { + const { cols = undefined, variant } = props; const { entity } = useEntity(); const app = useApp(); @@ -57,4 +55,4 @@ export const EntityLinksCard = ({ cols = undefined, variant }: Props) => { )} ); -}; +} diff --git a/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx b/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx index 54325ffb5d..7f95a66700 100644 --- a/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/EntityLinksEmptyState.tsx @@ -40,7 +40,7 @@ const useStyles = makeStyles( { name: 'PluginCatalogEntityLinksEmptyState' }, ); -export const EntityLinksEmptyState = () => { +export function EntityLinksEmptyState() { const classes = useStyles(); return ( @@ -68,4 +68,4 @@ export const EntityLinksEmptyState = () => { ); -}; +} diff --git a/plugins/catalog/src/components/EntityLinksCard/IconLink.tsx b/plugins/catalog/src/components/EntityLinksCard/IconLink.tsx index 9a0302a64e..9afdd1f1da 100644 --- a/plugins/catalog/src/components/EntityLinksCard/IconLink.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/IconLink.tsx @@ -17,7 +17,6 @@ import { makeStyles, Box, Typography } from '@material-ui/core'; import LanguageIcon from '@material-ui/icons/Language'; import React from 'react'; - import { Link } from '@backstage/core-components'; import { IconComponent } from '@backstage/core-plugin-api'; @@ -32,15 +31,12 @@ const useStyles = makeStyles({ }, }); -export const IconLink = ({ - href, - text, - Icon, -}: { +export function IconLink(props: { href: string; text?: string; Icon?: IconComponent; -}) => { +}) { + const { href, text, Icon } = props; const classes = useStyles(); return ( @@ -57,4 +53,4 @@ export const IconLink = ({ ); -}; +} diff --git a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx index 3df38c4523..7861b177ce 100644 --- a/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx +++ b/plugins/catalog/src/components/EntityLinksCard/LinksGridList.tsx @@ -21,18 +21,19 @@ import { ColumnBreakpoints } from './types'; import { useDynamicColumns } from './useDynamicColumns'; import { IconComponent } from '@backstage/core-plugin-api'; -export type LinksGridListItem = { +export interface LinksGridListItem { href: string; text?: string; Icon?: IconComponent; -}; +} -type Props = { +interface LinksGridListProps { items: LinksGridListItem[]; cols?: ColumnBreakpoints | number; -}; +} -export const LinksGridList = ({ items, cols = undefined }: Props) => { +export function LinksGridList(props: LinksGridListProps) { + const { items, cols = undefined } = props; const numOfCols = useDynamicColumns(cols); return ( @@ -44,4 +45,4 @@ export const LinksGridList = ({ items, cols = undefined }: Props) => { ))} ); -}; +} diff --git a/plugins/catalog/src/components/EntityLinksCard/index.ts b/plugins/catalog/src/components/EntityLinksCard/index.ts index fc53904a47..dcd4366066 100644 --- a/plugins/catalog/src/components/EntityLinksCard/index.ts +++ b/plugins/catalog/src/components/EntityLinksCard/index.ts @@ -15,4 +15,5 @@ */ export { EntityLinksCard } from './EntityLinksCard'; +export type { EntityLinksCardProps } from './EntityLinksCard'; export type { EntityLinksEmptyStateClassKey } from './EntityLinksEmptyState'; diff --git a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.test.tsx b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.test.tsx index faaba13d78..4f16078eb9 100644 --- a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.test.tsx +++ b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.test.tsx @@ -16,7 +16,6 @@ import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; - import { EntityNotFound } from './EntityNotFound'; describe('', () => { diff --git a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx index e396e5b4aa..fca763c37b 100644 --- a/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx +++ b/plugins/catalog/src/components/EntityNotFound/EntityNotFound.tsx @@ -18,7 +18,6 @@ import React from 'react'; import { Grid, Button, Typography } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import { BackstageTheme } from '@backstage/theme'; - import { Illo } from './Illo'; const useStyles = makeStyles(theme => ({ @@ -43,7 +42,7 @@ const useStyles = makeStyles(theme => ({ }, })); -export const EntityNotFound = () => { +export function EntityNotFound() { const classes = useStyles(); return ( @@ -67,4 +66,4 @@ export const EntityNotFound = () => { ); -}; +} diff --git a/plugins/catalog/src/components/EntityNotFound/Illo/Illo.tsx b/plugins/catalog/src/components/EntityNotFound/Illo/Illo.tsx index e0e76cd5d4..a375eca02a 100644 --- a/plugins/catalog/src/components/EntityNotFound/Illo/Illo.tsx +++ b/plugins/catalog/src/components/EntityNotFound/Illo/Illo.tsx @@ -34,7 +34,7 @@ const useStyles = makeStyles(theme => ({ }, })); -export const Illo = () => { +export function Illo() { const classes = useStyles(); return ( { alt="Illustration on entity not found page" /> ); -}; +} diff --git a/plugins/catalog/src/components/EntityNotFound/index.ts b/plugins/catalog/src/components/EntityNotFound/index.ts index 700a1097ca..aa2bd8e82b 100644 --- a/plugins/catalog/src/components/EntityNotFound/index.ts +++ b/plugins/catalog/src/components/EntityNotFound/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { EntityNotFound } from './EntityNotFound'; diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx index 75155aa075..c5442f3a3d 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import userEvent from '@testing-library/user-event'; import React from 'react'; import { DeleteEntityDialog } from './DeleteEntityDialog'; @@ -21,7 +22,6 @@ import { CatalogApi } from '@backstage/catalog-client'; import { catalogApiRef } from '@backstage/plugin-catalog-react'; import { screen, waitFor } from '@testing-library/react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; - import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; describe('DeleteEntityDialog', () => { diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx index 20aa673ea3..fc1391152a 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.tsx @@ -21,19 +21,15 @@ import React, { useState } from 'react'; import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { assertError } from '@backstage/errors'; -type Props = { +interface DeleteEntityDialogProps { open: boolean; onClose: () => any; onConfirm: () => any; entity: Entity; -}; +} -export const DeleteEntityDialog = ({ - open, - onClose, - onConfirm, - entity, -}: Props) => { +export function DeleteEntityDialog(props: DeleteEntityDialogProps) { + const { open, onClose, onConfirm, entity } = props; const [busy, setBusy] = useState(false); const catalogApi = useApi(catalogApiRef); const alertApi = useApi(alertApiRef); @@ -72,4 +68,4 @@ export const DeleteEntityDialog = ({ ); -}; +} diff --git a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx index c9d2d94609..aba28c558f 100644 --- a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx +++ b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.tsx @@ -22,13 +22,23 @@ import { useNavigate } from 'react-router'; import { DeleteEntityDialog } from './DeleteEntityDialog'; import { useRouteRef } from '@backstage/core-plugin-api'; -export const isOrphan = (entity: Entity) => - entity?.metadata?.annotations?.['backstage.io/orphan'] === 'true'; +/** + * Returns true if the given entity has the orphan annotation given by the + * catalog. + * + * @public + */ +export function isOrphan(entity: Entity): boolean { + return entity?.metadata?.annotations?.['backstage.io/orphan'] === 'true'; +} /** - * Displays a warning alert if the entity is marked as orphan with the ability to delete said entity. + * Displays a warning alert if the entity is marked as orphan with the ability + * to delete said entity. + * + * @public */ -export const EntityOrphanWarning = () => { +export function EntityOrphanWarning() { const navigate = useNavigate(); const catalogLink = useRouteRef(catalogRouteRef); const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); @@ -53,4 +63,4 @@ export const EntityOrphanWarning = () => { /> ); -}; +} diff --git a/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx b/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx index d73685655c..2eaf505ec0 100644 --- a/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx +++ b/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.tsx @@ -41,12 +41,12 @@ const errorFilter = (i: EntityStatusItem) => i.level === 'error' && i.type === ENTITY_STATUS_CATALOG_PROCESSING_TYPE; -type GetOwnAndAncestorsErrorsResponse = { +interface GetOwnAndAncestorsErrorsResponse { items: { errors: SerializedError[]; entity: Entity; }[]; -}; +} async function getOwnAndAncestorsErrors( entityRef: string, @@ -66,10 +66,15 @@ async function getOwnAndAncestorsErrors( return { items }; } -export const hasCatalogProcessingErrors = async ( +/** + * Returns true if the given entity has any processing errors on it. + * + * @public + */ +export async function hasCatalogProcessingErrors( entity: Entity, context: { apis: ApiHolder }, -) => { +) { const catalogApi = context.apis.get(catalogApiRef); if (!catalogApi) { throw new Error(`No implementation available for ${catalogApiRef}`); @@ -80,12 +85,14 @@ export const hasCatalogProcessingErrors = async ( catalogApi, ); return errors.items.length > 0; -}; +} /** * Displays a list of errors from the ancestors of the current entity. + * + * @public */ -export const EntityProcessingErrorsPanel = () => { +export function EntityProcessingErrorsPanel() { const { entity } = useEntity(); const entityRef = stringifyEntityRef(entity); const catalogApi = useApi(catalogApiRef); @@ -123,4 +130,4 @@ export const EntityProcessingErrorsPanel = () => { ))} ); -}; +} diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx index 87ee81c5f4..4433517247 100644 --- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx +++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.tsx @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; -import React, { PropsWithChildren, ReactNode } from 'react'; +import React, { ReactNode, ReactElement } from 'react'; import { attachComponentData, useApiHolder, @@ -27,34 +27,46 @@ import useAsync from 'react-use/lib/useAsync'; const ENTITY_SWITCH_KEY = 'core.backstage.entitySwitch'; -const EntitySwitchCase = (_: { +/** @public */ +export interface EntitySwitchCaseProps { if?: ( entity: Entity, context: { apis: ApiHolder }, ) => boolean | Promise; children: ReactNode; -}) => null; +} -attachComponentData(EntitySwitchCase, ENTITY_SWITCH_KEY, true); +const EntitySwitchCaseComponent = (_props: EntitySwitchCaseProps) => null; -type SwitchCase = { +attachComponentData(EntitySwitchCaseComponent, ENTITY_SWITCH_KEY, true); + +interface EntitySwitchCase { if?: ( entity: Entity, context: { apis: ApiHolder }, ) => boolean | Promise; children: JSX.Element; -}; +} type SwitchCaseResult = { if: boolean | Promise; children: JSX.Element; }; -export const EntitySwitch = ({ children }: PropsWithChildren<{}>) => { +/** + * Props for the {@link EntitySwitch} component. + * @public + */ +export interface EntitySwitchProps { + children: ReactNode; +} + +/** @public */ +export const EntitySwitch = (props: EntitySwitchProps) => { const { entity } = useEntity(); const apis = useApiHolder(); const results = useElementFilter( - children, + props.children, collection => collection .selectByComponentData({ @@ -62,9 +74,9 @@ export const EntitySwitch = ({ children }: PropsWithChildren<{}>) => { withStrictError: 'Child of EntitySwitch is not an EntitySwitch.Case', }) .getElements() - .flatMap((element: React.ReactElement) => { + .flatMap((element: ReactElement) => { const { if: condition, children: elementsChildren } = - element.props as SwitchCase; + element.props as EntitySwitchCase; return [ { if: condition?.(entity, { apis }) ?? true, @@ -110,4 +122,4 @@ function AsyncEntitySwitch({ results }: { results: SwitchCaseResult[] }) { return value; } -EntitySwitch.Case = EntitySwitchCase; +EntitySwitch.Case = EntitySwitchCaseComponent; diff --git a/plugins/catalog/src/components/EntitySwitch/conditions.ts b/plugins/catalog/src/components/EntitySwitch/conditions.ts index dd95a256bc..a02170cc6f 100644 --- a/plugins/catalog/src/components/EntitySwitch/conditions.ts +++ b/plugins/catalog/src/components/EntitySwitch/conditions.ts @@ -22,10 +22,18 @@ function strCmp(a: string | undefined, b: string | undefined): boolean { ); } +/** + * For use in EntitySwitch.Case. Matches if the entity is of a given kind. + * @public + */ export function isKind(kind: string) { return (entity: Entity) => strCmp(entity?.kind, kind); } +/** + * For use in EntitySwitch.Case. Matches if the entity is a Component of a given spec.type. + * @public + */ export function isComponentType(type: string) { return (entity: Entity) => { if (!strCmp(entity?.kind, 'component')) { @@ -36,6 +44,10 @@ export function isComponentType(type: string) { }; } +/** + * For use in EntitySwitch.Case. Matches if the entity is in a given namespace. + * @public + */ export function isNamespace(namespace: string) { return (entity: Entity) => strCmp(entity?.metadata?.namespace, namespace); } diff --git a/plugins/catalog/src/components/EntitySwitch/index.ts b/plugins/catalog/src/components/EntitySwitch/index.ts index bf4caf0e66..6cccf4be6e 100644 --- a/plugins/catalog/src/components/EntitySwitch/index.ts +++ b/plugins/catalog/src/components/EntitySwitch/index.ts @@ -15,4 +15,5 @@ */ export { EntitySwitch } from './EntitySwitch'; +export type { EntitySwitchProps, EntitySwitchCaseProps } from './EntitySwitch'; export { isKind, isNamespace, isComponentType } from './conditions'; diff --git a/plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx b/plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx index a39ef76b87..d210d551fb 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx +++ b/plugins/catalog/src/components/FilteredEntityLayout/EntityListContainer.tsx @@ -15,10 +15,13 @@ */ import { Grid } from '@material-ui/core'; -import React, { PropsWithChildren } from 'react'; +import React from 'react'; -export const EntityListContainer = ({ children }: PropsWithChildren<{}>) => ( - - {children} - -); +/** @public */ +export function EntityListContainer(props: { children: React.ReactNode }) { + return ( + + {props.children} + + ); +} diff --git a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx index 683dbf8b40..4c0d17599d 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx +++ b/plugins/catalog/src/components/FilteredEntityLayout/FilterContainer.tsx @@ -25,9 +25,10 @@ import { useTheme, } from '@material-ui/core'; import FilterListIcon from '@material-ui/icons/FilterList'; -import React, { useState, PropsWithChildren } from 'react'; +import React, { useState } from 'react'; -export const FilterContainer = ({ children }: PropsWithChildren<{}>) => { +/** @public */ +export function FilterContainer(props: { children: React.ReactNode }) { const isMidSizeScreen = useMediaQuery(theme => theme.breakpoints.down('md'), ); @@ -59,13 +60,13 @@ export const FilterContainer = ({ children }: PropsWithChildren<{}>) => { > Filters - {children} + {props.children} ) : ( - {children} + {props.children} ); -}; +} diff --git a/plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx b/plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx index 628ad9fee6..8e595fe5cd 100644 --- a/plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx +++ b/plugins/catalog/src/components/FilteredEntityLayout/FilteredEntityLayout.tsx @@ -15,10 +15,13 @@ */ import { Grid } from '@material-ui/core'; -import React, { PropsWithChildren } from 'react'; +import React from 'react'; -export const FilteredEntityLayout = ({ children }: PropsWithChildren<{}>) => ( - - {children} - -); +/** @public */ +export function FilteredEntityLayout(props: { children: React.ReactNode }) { + return ( + + {props.children} + + ); +} diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx index 45f7561d6e..fbb474825f 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx index cefdb15a6e..5864028694 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx @@ -23,11 +23,13 @@ import { RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -type Props = { +/** @public */ +export interface HasComponentsCardProps { variant?: 'gridItem'; -}; +} -export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => { +export function HasComponentsCard(props: HasComponentsCardProps) { + const { variant = 'gridItem' } = props; return ( { asRenderableEntities={asComponentEntities} /> ); -}; +} diff --git a/plugins/catalog/src/components/HasComponentsCard/index.ts b/plugins/catalog/src/components/HasComponentsCard/index.ts index 73088bd575..86038b6030 100644 --- a/plugins/catalog/src/components/HasComponentsCard/index.ts +++ b/plugins/catalog/src/components/HasComponentsCard/index.ts @@ -15,3 +15,4 @@ */ export { HasComponentsCard } from './HasComponentsCard'; +export type { HasComponentsCardProps } from './HasComponentsCard'; diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx index a01922d988..c75273e484 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx @@ -76,10 +76,11 @@ describe('', () => { relations: [ { target: { - kind: 'Resource', + kind: 'resource', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'resource:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx index 3a57317a06..19a48aad25 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx @@ -23,11 +23,13 @@ import { resourceEntityHelpLink, } from '../RelatedEntitiesCard'; -type Props = { +/** @public */ +export interface HasResourcesCardProps { variant?: 'gridItem'; -}; +} -export const HasResourcesCard = ({ variant = 'gridItem' }: Props) => { +export function HasResourcesCard(props: HasResourcesCardProps) { + const { variant = 'gridItem' } = props; return ( { emptyHelpLink={resourceEntityHelpLink} /> ); -}; +} diff --git a/plugins/catalog/src/components/HasResourcesCard/index.ts b/plugins/catalog/src/components/HasResourcesCard/index.ts index 5357643a62..cdbbef680b 100644 --- a/plugins/catalog/src/components/HasResourcesCard/index.ts +++ b/plugins/catalog/src/components/HasResourcesCard/index.ts @@ -15,3 +15,4 @@ */ export { HasResourcesCard } from './HasResourcesCard'; +export type { HasResourcesCardProps } from './HasResourcesCard'; diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx index e517bd38ae..e021fea2f3 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx @@ -81,10 +81,11 @@ describe('', () => { relations: [ { target: { - kind: 'Component', + kind: 'component', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'component:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index ddeffe3fbd..0b31bbd2be 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -22,11 +22,13 @@ import { RelatedEntitiesCard, } from '../RelatedEntitiesCard'; -type Props = { +/** @public */ +export interface HasSubcomponentsCardProps { variant?: 'gridItem'; -}; +} -export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => { +export function HasSubcomponentsCard(props: HasSubcomponentsCardProps) { + const { variant = 'gridItem' } = props; return ( { emptyHelpLink="https://backstage.io/docs/features/software-catalog/descriptor-format#specsubcomponentof-optional" /> ); -}; +} diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/index.ts b/plugins/catalog/src/components/HasSubcomponentsCard/index.ts index bfc74efe71..6f43967d50 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/index.ts +++ b/plugins/catalog/src/components/HasSubcomponentsCard/index.ts @@ -15,3 +15,4 @@ */ export { HasSubcomponentsCard } from './HasSubcomponentsCard'; +export type { HasSubcomponentsCardProps } from './HasSubcomponentsCard'; diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx index 050550101f..6a2e2a0b7f 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx @@ -79,10 +79,11 @@ describe('', () => { relations: [ { target: { - kind: 'System', + kind: 'system', namespace: 'my-namespace', name: 'target-name', }, + targetRef: 'system:my-namespace/target-name', type: RELATION_HAS_PART, }, ], diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index b3e756c8dc..7ab6831e42 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -23,11 +23,13 @@ import { systemEntityHelpLink, } from '../RelatedEntitiesCard'; -type Props = { +/** @public */ +export interface HasSystemsCardProps { variant?: 'gridItem'; -}; +} -export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => { +export function HasSystemsCard(props: HasSystemsCardProps) { + const { variant = 'gridItem' } = props; return ( { emptyHelpLink={systemEntityHelpLink} /> ); -}; +} diff --git a/plugins/catalog/src/components/HasSystemsCard/index.ts b/plugins/catalog/src/components/HasSystemsCard/index.ts index e69c783bb4..71fb7539b1 100644 --- a/plugins/catalog/src/components/HasSystemsCard/index.ts +++ b/plugins/catalog/src/components/HasSystemsCard/index.ts @@ -15,3 +15,4 @@ */ export { HasSystemsCard } from './HasSystemsCard'; +export type { HasSystemsCardProps } from './HasSystemsCard'; diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx index ec6ea696e2..70854c1b21 100644 --- a/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx +++ b/plugins/catalog/src/components/RelatedEntitiesCard/RelatedEntitiesCard.tsx @@ -30,7 +30,8 @@ import { TableColumn, } from '@backstage/core-components'; -type Props = { +/** @public */ +export type RelatedEntitiesCardProps = { variant?: 'gridItem'; title: string; columns: TableColumn[]; @@ -53,7 +54,9 @@ type Props = { * * @public */ -export const RelatedEntitiesCard = (props: Props) => { +export function RelatedEntitiesCard( + props: RelatedEntitiesCardProps, +) { const { variant = 'gridItem', title, @@ -103,4 +106,4 @@ export const RelatedEntitiesCard = (props: Props) => { entities={asRenderableEntities(entities || [])} /> ); -}; +} diff --git a/plugins/catalog/src/components/RelatedEntitiesCard/index.ts b/plugins/catalog/src/components/RelatedEntitiesCard/index.ts index 8df5052de4..6012dd6553 100644 --- a/plugins/catalog/src/components/RelatedEntitiesCard/index.ts +++ b/plugins/catalog/src/components/RelatedEntitiesCard/index.ts @@ -15,4 +15,5 @@ */ export { RelatedEntitiesCard } from './RelatedEntitiesCard'; +export type { RelatedEntitiesCardProps } from './RelatedEntitiesCard'; export * from './presets'; diff --git a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx index e0ec48fbed..eb6b71c47e 100644 --- a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx +++ b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx @@ -103,10 +103,11 @@ describe('', () => { relations: [ { target: { - kind: 'Domain', + kind: 'domain', namespace: 'namespace', name: 'domain', }, + targetRef: 'domain:namespace/domain', type: RELATION_PART_OF, }, ], @@ -166,6 +167,7 @@ describe('', () => { namespace: 'namespace', name: 'alongdomainthatshouldgettruncated', }, + targetRef: 'domain:namespace/alongdomainthatshouldgettruncated', type: RELATION_PART_OF, }, ], diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index b7756024ac..fef420d6d3 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -22,10 +22,8 @@ export * from './components/AboutCard'; export * from './components/CatalogKindHeader'; -export * from './components/CatalogResultListItem'; -export { CatalogTable } from './components/CatalogTable'; -export type { EntityRow as CatalogTableRow } from './components/CatalogTable'; -export * from './components/CatalogTable/columns'; +export * from './components/CatalogSearchResultListItem'; +export * from './components/CatalogTable'; export * from './components/EntityLayout'; export * from './components/EntityOrphanWarning'; export * from './components/EntityProcessingErrorsPanel'; @@ -36,7 +34,6 @@ export { CatalogEntityPage, CatalogIndexPage, catalogPlugin, - catalogPlugin as plugin, EntityAboutCard, EntityDependencyOfComponentsCard, EntityDependsOnComponentsCard, @@ -49,6 +46,17 @@ export { RelatedEntitiesCard, } from './plugin'; -export type { EntityLinksEmptyStateClassKey } from './components/EntityLinksCard'; +export type { DependencyOfComponentsCardProps } from './components/DependencyOfComponentsCard'; +export type { DependsOnComponentsCardProps } from './components/DependsOnComponentsCard'; +export type { DependsOnResourcesCardProps } from './components/DependsOnResourcesCard'; +export type { + EntityLinksEmptyStateClassKey, + EntityLinksCardProps, +} from './components/EntityLinksCard'; export type { SystemDiagramCardClassKey } from './components/SystemDiagramCard'; export type { DefaultCatalogPageProps } from './components/CatalogPage'; +export type { HasComponentsCardProps } from './components/HasComponentsCard'; +export type { HasResourcesCardProps } from './components/HasResourcesCard'; +export type { HasSubcomponentsCardProps } from './components/HasSubcomponentsCard'; +export type { HasSystemsCardProps } from './components/HasSystemsCard'; +export type { RelatedEntitiesCardProps } from './components/RelatedEntitiesCard'; diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts index 2e4ebe9570..b4d1a61bc1 100644 --- a/plugins/catalog/src/plugin.ts +++ b/plugins/catalog/src/plugin.ts @@ -15,6 +15,7 @@ */ import { CatalogClient } from '@backstage/catalog-client'; +import { Entity } from '@backstage/catalog-model'; import { catalogApiRef, catalogRouteRef, @@ -32,7 +33,18 @@ import { fetchApiRef, storageApiRef, } from '@backstage/core-plugin-api'; +import { AboutCardProps } from './components/AboutCard'; +import { DefaultCatalogPageProps } from './components/CatalogPage'; +import { DependencyOfComponentsCardProps } from './components/DependencyOfComponentsCard'; +import { DependsOnComponentsCardProps } from './components/DependsOnComponentsCard'; +import { DependsOnResourcesCardProps } from './components/DependsOnResourcesCard'; +import { HasComponentsCardProps } from './components/HasComponentsCard'; +import { HasResourcesCardProps } from './components/HasResourcesCard'; +import { HasSubcomponentsCardProps } from './components/HasSubcomponentsCard'; +import { HasSystemsCardProps } from './components/HasSystemsCard'; +import { RelatedEntitiesCardProps } from './components/RelatedEntitiesCard'; +/** @public */ export const catalogPlugin = createPlugin({ id: 'catalog', apis: [ @@ -62,16 +74,19 @@ export const catalogPlugin = createPlugin({ }, }); -export const CatalogIndexPage = catalogPlugin.provide( - createRoutableExtension({ - name: 'CatalogIndexPage', - component: () => - import('./components/CatalogPage').then(m => m.CatalogPage), - mountPoint: catalogRouteRef, - }), -); +/** @public */ +export const CatalogIndexPage: (props: DefaultCatalogPageProps) => JSX.Element = + catalogPlugin.provide( + createRoutableExtension({ + name: 'CatalogIndexPage', + component: () => + import('./components/CatalogPage').then(m => m.CatalogPage), + mountPoint: catalogRouteRef, + }), + ); -export const CatalogEntityPage = catalogPlugin.provide( +/** @public */ +export const CatalogEntityPage: () => JSX.Element = catalogPlugin.provide( createRoutableExtension({ name: 'CatalogEntityPage', component: () => @@ -80,15 +95,18 @@ export const CatalogEntityPage = catalogPlugin.provide( }), ); -export const EntityAboutCard = catalogPlugin.provide( - createComponentExtension({ - name: 'EntityAboutCard', - component: { - lazy: () => import('./components/AboutCard').then(m => m.AboutCard), - }, - }), -); +/** @public */ +export const EntityAboutCard: (props: AboutCardProps) => JSX.Element = + catalogPlugin.provide( + createComponentExtension({ + name: 'EntityAboutCard', + component: { + lazy: () => import('./components/AboutCard').then(m => m.AboutCard), + }, + }), + ); +/** @public */ export const EntityLinksCard = catalogPlugin.provide( createComponentExtension({ name: 'EntityLinksCard', @@ -99,17 +117,22 @@ export const EntityLinksCard = catalogPlugin.provide( }), ); -export const EntityHasSystemsCard = catalogPlugin.provide( - createComponentExtension({ - name: 'EntityHasSystemsCard', - component: { - lazy: () => - import('./components/HasSystemsCard').then(m => m.HasSystemsCard), - }, - }), -); +/** @public */ +export const EntityHasSystemsCard: (props: HasSystemsCardProps) => JSX.Element = + catalogPlugin.provide( + createComponentExtension({ + name: 'EntityHasSystemsCard', + component: { + lazy: () => + import('./components/HasSystemsCard').then(m => m.HasSystemsCard), + }, + }), + ); -export const EntityHasComponentsCard = catalogPlugin.provide( +/** @public */ +export const EntityHasComponentsCard: ( + props: HasComponentsCardProps, +) => JSX.Element = catalogPlugin.provide( createComponentExtension({ name: 'EntityHasComponentsCard', component: { @@ -119,7 +142,10 @@ export const EntityHasComponentsCard = catalogPlugin.provide( }), ); -export const EntityHasSubcomponentsCard = catalogPlugin.provide( +/** @public */ +export const EntityHasSubcomponentsCard: ( + props: HasSubcomponentsCardProps, +) => JSX.Element = catalogPlugin.provide( createComponentExtension({ name: 'EntityHasSubcomponentsCard', component: { @@ -131,7 +157,10 @@ export const EntityHasSubcomponentsCard = catalogPlugin.provide( }), ); -export const EntityHasResourcesCard = catalogPlugin.provide( +/** @public */ +export const EntityHasResourcesCard: ( + props: HasResourcesCardProps, +) => JSX.Element = catalogPlugin.provide( createComponentExtension({ name: 'EntityHasResourcesCard', component: { @@ -141,7 +170,10 @@ export const EntityHasResourcesCard = catalogPlugin.provide( }), ); -export const EntityDependsOnComponentsCard = catalogPlugin.provide( +/** @public */ +export const EntityDependsOnComponentsCard: ( + props: DependsOnComponentsCardProps, +) => JSX.Element = catalogPlugin.provide( createComponentExtension({ name: 'EntityDependsOnComponentsCard', component: { @@ -153,7 +185,10 @@ export const EntityDependsOnComponentsCard = catalogPlugin.provide( }), ); -export const EntityDependencyOfComponentsCard = catalogPlugin.provide( +/** @public */ +export const EntityDependencyOfComponentsCard: ( + props: DependencyOfComponentsCardProps, +) => JSX.Element = catalogPlugin.provide( createComponentExtension({ name: 'EntityDependencyOfComponentsCard', component: { @@ -165,7 +200,10 @@ export const EntityDependencyOfComponentsCard = catalogPlugin.provide( }), ); -export const EntityDependsOnResourcesCard = catalogPlugin.provide( +/** @public */ +export const EntityDependsOnResourcesCard: ( + props: DependsOnResourcesCardProps, +) => JSX.Element = catalogPlugin.provide( createComponentExtension({ name: 'EntityDependsOnResourcesCard', component: { @@ -177,7 +215,10 @@ export const EntityDependsOnResourcesCard = catalogPlugin.provide( }), ); -export const RelatedEntitiesCard = catalogPlugin.provide( +/** @public */ +export const RelatedEntitiesCard: ( + props: RelatedEntitiesCardProps, +) => JSX.Element = catalogPlugin.provide( createComponentExtension({ name: 'RelatedEntitiesCard', component: { diff --git a/plugins/cicd-statistics/CHANGELOG.md b/plugins/cicd-statistics/CHANGELOG.md index 7258ea0570..9fda781e52 100644 --- a/plugins/cicd-statistics/CHANGELOG.md +++ b/plugins/cicd-statistics/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-cicd-statistics +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.1 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 0b9bce831b: Updated `@backstage/core-plugin-api` dependency. +- Updated dependencies + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/cicd-statistics/package.json b/plugins/cicd-statistics/package.json index a058917b09..a9fe0b0807 100644 --- a/plugins/cicd-statistics/package.json +++ b/plugins/cicd-statistics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics", "description": "A frontend plugin visualizing CI/CD pipeline statistics (build time)", - "version": "0.1.0", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,21 +24,22 @@ "backstage" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "devDependencies": { "@types/react": "^16.13.1 || ^17.0.0", "@types/luxon": "^2.0.5" }, "dependencies": { - "@backstage/catalog-model": "^0.9.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", "@date-io/luxon": "^1.3.13", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.11.2", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index fcd6237d70..cc16ef1f53 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-circleci +## 0.3.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.38 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.2.37 ### Patch Changes diff --git a/plugins/circleci/api-report.md b/plugins/circleci/api-report.md index bb4334c0f9..5cdf916514 100644 --- a/plugins/circleci/api-report.md +++ b/plugins/circleci/api-report.md @@ -87,9 +87,7 @@ export const circleCIRouteRef: RouteRef; // Warning: (ae-missing-release-tag) "EntityCircleCIContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityCircleCIContent: (_props: { - entity?: Entity | undefined; -}) => JSX.Element; +export const EntityCircleCIContent: () => JSX.Element; export { GitType }; @@ -100,9 +98,8 @@ const isCircleCIAvailable: (entity: Entity) => boolean; export { isCircleCIAvailable }; export { isCircleCIAvailable as isPluginApplicableToEntity }; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const Router: (_props: Props) => JSX.Element; +export const Router: () => JSX.Element; ``` diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 58df662e68..2005320b7d 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.2.37", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,21 +25,21 @@ "circleci" ], "scripts": { - "build": "backstage-cli plugin:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "clean": "backstage-cli clean", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", "diff": "backstage-cli plugin:diff", - "start": "backstage-cli plugin:serve", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack" + "start": "backstage-cli package start", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -52,10 +55,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/circleci/src/components/Router.tsx b/plugins/circleci/src/components/Router.tsx index 5aa8dd9df9..e16c91f2a0 100644 --- a/plugins/circleci/src/components/Router.tsx +++ b/plugins/circleci/src/components/Router.tsx @@ -27,12 +27,7 @@ import { MissingAnnotationEmptyState } from '@backstage/core-components'; export const isCircleCIAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[CIRCLECI_ANNOTATION]); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; - -export const Router = (_props: Props) => { +export const Router = () => { const { entity } = useEntity(); if (!isCircleCIAvailable(entity)) { diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 8a434df33d..f4b3c0ec6b 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-cloudbuild +## 0.3.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.36 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.2.35 ### Patch Changes diff --git a/plugins/cloudbuild/api-report.md b/plugins/cloudbuild/api-report.md index fefa4fe344..4077a8991a 100644 --- a/plugins/cloudbuild/api-report.md +++ b/plugins/cloudbuild/api-report.md @@ -149,9 +149,7 @@ export { cloudbuildPlugin as plugin }; // Warning: (ae-missing-release-tag) "EntityCloudbuildContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityCloudbuildContent: (_props: { - entity?: Entity | undefined; -}) => JSX.Element; +export const EntityCloudbuildContent: () => JSX.Element; // Warning: (ae-missing-release-tag) "EntityLatestCloudbuildRunCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -159,7 +157,6 @@ export const EntityCloudbuildContent: (_props: { export const EntityLatestCloudbuildRunCard: ({ branch, }: { - entity?: Entity | undefined; branch: string; }) => JSX.Element; @@ -169,7 +166,6 @@ export const EntityLatestCloudbuildRunCard: ({ export const EntityLatestCloudbuildsForBranchCard: ({ branch, }: { - entity?: Entity | undefined; branch: string; }) => JSX.Element; @@ -196,7 +192,6 @@ export { isCloudbuildAvailable as isPluginApplicableToEntity }; export const LatestWorkflowRunCard: ({ branch, }: { - entity?: Entity | undefined; branch: string; }) => JSX.Element; @@ -206,7 +201,6 @@ export const LatestWorkflowRunCard: ({ export const LatestWorkflowsForBranchCard: ({ branch, }: { - entity?: Entity | undefined; branch: string; }) => JSX.Element; @@ -256,11 +250,10 @@ export interface Results { buildStepOutputs: string[]; } -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const Router: (_props: Props) => JSX.Element; +export const Router: () => JSX.Element; // Warning: (ae-missing-release-tag) "Source" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 7ed6f48f71..aca7f03b5d 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cloudbuild", "description": "A Backstage plugin that integrates towards Google Cloud Build", - "version": "0.2.35", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,21 +24,21 @@ "google cloud" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +52,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cloudbuild/src/components/Cards/Cards.tsx b/plugins/cloudbuild/src/components/Cards/Cards.tsx index dad1f7d38f..82e0eb1b88 100644 --- a/plugins/cloudbuild/src/components/Cards/Cards.tsx +++ b/plugins/cloudbuild/src/components/Cards/Cards.tsx @@ -16,7 +16,6 @@ import React, { useEffect } from 'react'; import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns'; import { WorkflowRunsTable } from '../WorkflowRunsTable'; -import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { Theme, makeStyles, LinearProgress } from '@material-ui/core'; @@ -76,8 +75,6 @@ const WidgetContent = ({ export const LatestWorkflowRunCard = ({ branch = 'master', }: { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; branch: string; }) => { const { entity } = useEntity(); @@ -109,8 +106,6 @@ export const LatestWorkflowRunCard = ({ export const LatestWorkflowsForBranchCard = ({ branch = 'master', }: { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; branch: string; }) => { const { entity } = useEntity(); diff --git a/plugins/cloudbuild/src/components/Router.tsx b/plugins/cloudbuild/src/components/Router.tsx index bdeae7e8fd..e4c590bcaf 100644 --- a/plugins/cloudbuild/src/components/Router.tsx +++ b/plugins/cloudbuild/src/components/Router.tsx @@ -26,12 +26,7 @@ import { MissingAnnotationEmptyState } from '@backstage/core-components'; export const isCloudbuildAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[CLOUDBUILD_ANNOTATION]); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; - -export const Router = (_props: Props) => { +export const Router = () => { const { entity } = useEntity(); if (!isCloudbuildAvailable(entity)) { diff --git a/plugins/code-climate/.eslintrc.js b/plugins/code-climate/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/code-climate/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/code-climate/CHANGELOG.md b/plugins/code-climate/CHANGELOG.md new file mode 100644 index 0000000000..596c4343b0 --- /dev/null +++ b/plugins/code-climate/CHANGELOG.md @@ -0,0 +1,15 @@ +# @backstage/plugin-code-climate + +## 0.1.0 + +### Minor Changes + +- e621d9658b: Added Code Climate plugin + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 diff --git a/plugins/code-climate/README.md b/plugins/code-climate/README.md new file mode 100644 index 0000000000..a67e70b11a --- /dev/null +++ b/plugins/code-climate/README.md @@ -0,0 +1,85 @@ +# Code Climate Plugin + +The Code Climate Plugin displays a few stats from the quality section from [Code Climate](https://codeclimate.com). + +![Code Climate Card](./docs/code-climate-card.png) + +## Getting Started + +1. Install the Code Climate Plugin: + +```bash +# From your Backstage root directory +cd packages/app +yarn add @backstage/plugin-code-climate +``` + +2. Add the `EntityCodeClimateCard` to the EntityPage: + +```jsx +// packages/app/src/components/catalog/EntityPage.tsx + +import { EntityCodeClimateCard } from '@backstage/plugin-code-climate'; + +const overviewContent = ( + + // ... + + + + // ... + +); +``` + +3. Add the proxy config: + +```yaml +# app-config.yaml + +proxy: + '/codeclimate/api': + target: https://api.codeclimate.com/v1 + headers: + Authorization: Token token=${CODECLIMATE_TOKEN} +``` + +4. Create a new API access token (https://codeclimate.com/profile/tokens) and provide `CODECLIMATE_TOKEN` as an env variable. + +5. Add the `codeclimate.com/repo-id` annotation to your `catalog-info.yaml` file: + +```yaml +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: backstage + description: | + Backstage is an open-source developer portal that puts the developer experience first. + annotations: + codeclimate.com/repo-id: YOUR_REPO_ID +spec: + type: library + owner: CNCF + lifecycle: experimental +``` + +### Demo Mode + +The plugin provides a MockAPI that always returns dummy data instead of talking to the Code Climate backend. +You can add it by overriding the `codeClimateApiRef`: + +```ts +// packages/app/src/apis.ts + +import { createApiFactory } from '@backstage/core-plugin-api'; +import { + MockCodeClimateApi, + codeClimateApiRef, +} from '@backstage/plugin-code-climate'; + +export const apis = [ + // ... + + createApiFactory(codeClimateApiRef, new MockCodeClimateApi()), +]; +``` diff --git a/plugins/code-climate/api-report.md b/plugins/code-climate/api-report.md new file mode 100644 index 0000000000..11366384d3 --- /dev/null +++ b/plugins/code-climate/api-report.md @@ -0,0 +1,87 @@ +## API Report File for "@backstage/plugin-code-climate" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { ApiRef } from '@backstage/core-plugin-api'; +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; + +// Warning: (ae-missing-release-tag) "CodeClimateApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface CodeClimateApi { + // (undocumented) + fetchData(repoID: string): Promise; +} + +// Warning: (ae-missing-release-tag) "codeClimateApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const codeClimateApiRef: ApiRef; + +// Warning: (ae-missing-release-tag) "CodeClimateData" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type CodeClimateData = { + repoID: string; + maintainability: { + letter: string; + value: string; + }; + testCoverage: { + letter: string; + value: string; + }; + numberOfCodeSmells: number; + numberOfDuplication: number; + numberOfOtherIssues: number; +}; + +// Warning: (ae-missing-release-tag) "codeClimatePlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const codeClimatePlugin: BackstagePlugin< + { + root: RouteRef; + }, + {} +>; + +// Warning: (ae-missing-release-tag) "EntityCodeClimateCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const EntityCodeClimateCard: () => JSX.Element; + +// Warning: (ae-missing-release-tag) "MockCodeClimateApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class MockCodeClimateApi implements CodeClimateApi { + // (undocumented) + fetchData(): Promise; +} + +// Warning: (ae-missing-release-tag) "mockData" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const mockData: CodeClimateData; + +// Warning: (ae-missing-release-tag) "ProductionCodeClimateApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export class ProductionCodeClimateApi implements CodeClimateApi { + constructor(discoveryApi: DiscoveryApi); + // (undocumented) + fetchAllData(options: { + apiUrl: string; + repoID: string; + snapshotID: string; + testReportID: string; + }): Promise; + // (undocumented) + fetchData(repoID: string): Promise; +} +``` diff --git a/plugins/code-climate/dev/index.tsx b/plugins/code-climate/dev/index.tsx new file mode 100644 index 0000000000..d5fc4c28e3 --- /dev/null +++ b/plugins/code-climate/dev/index.tsx @@ -0,0 +1,93 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Entity } from '@backstage/catalog-model'; +// eslint-disable-next-line import/no-extraneous-dependencies +import { createDevApp, EntityGridItem } from '@backstage/dev-utils'; +import { Grid } from '@material-ui/core'; +import React from 'react'; +import { + EntityCodeClimateCard, + MockCodeClimateApi, + CodeClimateApi, + codeClimateApiRef, +} from '../src'; +import { CODECLIMATE_REPO_ID_ANNOTATION } from '../src/plugin'; +import { Content, Header, Page } from '@backstage/core-components'; + +const entity = (name?: string) => + ({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + annotations: { + [CODECLIMATE_REPO_ID_ANNOTATION]: name, + }, + name: name, + }, + } as Entity); + +createDevApp() + .registerApi({ + api: codeClimateApiRef, + deps: {}, + factory: () => + ({ + fetchData: async (repoID: string) => { + switch (repoID) { + case 'error': + throw new Error('Error!'); + + case 'never': + return new Promise(() => {}); + + case 'no-values': + return undefined; + + case 'with-values': + return new MockCodeClimateApi().fetchData(); + + default: + return []; + } + }, + } as CodeClimateApi), + }) + .addPage({ + title: 'Cards', + element: ( + +
+ + + + + + + + + + + + + + + + + + ), + }) + .render(); diff --git a/plugins/code-climate/docs/code-climate-card.png b/plugins/code-climate/docs/code-climate-card.png new file mode 100644 index 0000000000..b94e23eebe Binary files /dev/null and b/plugins/code-climate/docs/code-climate-card.png differ diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json new file mode 100644 index 0000000000..57b6d097ed --- /dev/null +++ b/plugins/code-climate/package.json @@ -0,0 +1,56 @@ +{ + "name": "@backstage/plugin-code-climate", + "version": "0.1.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.57", + "humanize-duration": "^3.27.1", + "luxon": "^2.0.2", + "react-router": "6.0.0-beta.0", + "react-use": "^17.2.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.14.1", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^11.2.5", + "@testing-library/user-event": "^13.1.8", + "@types/humanize-duration": "^3.27.1", + "@types/jest": "^26.0.7", + "@types/luxon": "^2.0.9", + "@types/node": "^14.14.32", + "cross-fetch": "^3.1.5", + "msw": "^0.35.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/code-climate/src/api/code-climate-api.ts b/plugins/code-climate/src/api/code-climate-api.ts new file mode 100644 index 0000000000..fb4f41ebb5 --- /dev/null +++ b/plugins/code-climate/src/api/code-climate-api.ts @@ -0,0 +1,26 @@ +/* + * 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 { CodeClimateData } from './code-climate-data'; +import { createApiRef } from '@backstage/core-plugin-api'; + +export const codeClimateApiRef = createApiRef({ + id: 'plugin.code-climate.service', +}); + +export interface CodeClimateApi { + fetchData(repoID: string): Promise; +} diff --git a/plugins/code-climate/src/api/code-climate-data.ts b/plugins/code-climate/src/api/code-climate-data.ts new file mode 100644 index 0000000000..9271905157 --- /dev/null +++ b/plugins/code-climate/src/api/code-climate-data.ts @@ -0,0 +1,184 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type CodeClimateRepoData = { + data: { + id: string; + type: string; + attributes: { + analysis_version: number; + badge_token: string; + branch: string; + created_at: string; + delegated_config_repo_id: string; + diff_coverage_enforced: boolean; + diff_coverage_threshold: number; + enable_notifications: boolean; + github_slug: string; + human_name: string; + last_activity_at: string; + test_reporter_id: string | null; + total_coverage_enforced: boolean; + vcs_database_id: string; + vcs_host: string; + score: string | null; + }; + relationships: { + latest_default_branch_snapshot: { + data: { id: string; type: string }; + }; + latest_default_branch_test_report: { + data: { id: string; type: string }; + }; + account: { + data: { id: string; type: string }; + }; + }; + links: { + self: string; + services: string; + web_coverage: string; + web_issues: string; + maintainability_badge: string; + test_coverage_badge: string; + }; + meta: { permissions: { admin: boolean } }; + }; +}; + +export type CodeClimateMaintainabilityData = { + data: { + id: string; + type: string; + attributes: { + commit_sha: string; + committed_at: string; + created_at: string; + lines_of_code: number; + ratings: [ + { + path: string; + letter: string; + measure: { + value: number; + unit: string; + meta: { + remediation_time: { value: number; unit: string }; + implementation_time: { + value: number; + unit: string; + }; + }; + }; + pillar: string; + }, + ]; + gpa: string | null; + worker_version: number; + }; + meta: { + issues_count: number; + measures: { + remediation: { value: number; unit: string }; + technical_debt_ratio: { + value: number; + unit: string; + meta: { + remediation_time: { value: number; unit: string }; + implementation_time: { + value: number; + unit: string; + }; + }; + }; + }; + }; + }; +}; + +export type CodeClimateTestCoverageData = { + data: { + id: string; + type: string; + attributes: { + branch: string; + commit_sha: string; + committed_at: string; + covered_percent: number; + lines_of_code: number; + rating: { + path: string; + letter: string; + measure: { value: number; unit: string }; + pillar: string; + }; + received_at: string; + state: string; + }; + }; +}; + +export type CodeClimateIssuesData = { + data: [ + { + id: string; + type: string; + attributes: { + categories: string[]; + check_name: string; + constant_name: string; + content: { body: string }; + description: string; + engine_name: string; + fingerprint: string; + location: { + path: string; + end_line: number; + start_line: number; + }; + other_locations: string[]; + remediation_points: number; + severity: string; + }; + meta: { permissions: { manageable: boolean } }; + }, + ]; + links: { + self: string; + next: string; + last: string; + }; + meta: { current_page: number; total_pages: number; total_count: number }; +}; + +export type CodeClimateData = { + repoID: string; + maintainability: { + letter: string; + value: string; + }; + testCoverage: { + letter: string; + value: string; + }; + numberOfCodeSmells: number; + numberOfDuplication: number; + numberOfOtherIssues: number; +}; + +export type CodeClimateApiError = { + detail: string; +}; diff --git a/plugins/code-climate/src/api/index.ts b/plugins/code-climate/src/api/index.ts new file mode 100644 index 0000000000..60fc079698 --- /dev/null +++ b/plugins/code-climate/src/api/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './mock'; +export type { CodeClimateApi } from './code-climate-api'; +export { codeClimateApiRef } from './code-climate-api'; +export type { CodeClimateData } from './code-climate-data'; +export { ProductionCodeClimateApi } from './production-api'; diff --git a/plugins/code-climate/src/api/mock/code-climate-maintainability-mock.json b/plugins/code-climate/src/api/mock/code-climate-maintainability-mock.json new file mode 100644 index 0000000000..46a7905e4b --- /dev/null +++ b/plugins/code-climate/src/api/mock/code-climate-maintainability-mock.json @@ -0,0 +1,49 @@ +{ + "data": { + "id": "6b8cc37a64b741dd9d516119", + "type": "snapshots", + "attributes": { + "commit_sha": "d670460b4b4aece5915caf5c68d12f560a9fe3e4", + "committed_at": "2022-01-14T15:17:29.311Z", + "created_at": "2022-01-31T10:07:40.415Z", + "lines_of_code": 8854, + "ratings": [ + { + "path": "/", + "letter": "B", + "measure": { + "value": 5.264879308188034, + "unit": "percent", + "meta": { + "remediation_time": { "value": 7186.52, "unit": "minute" }, + "implementation_time": { + "value": 136499.2353922225, + "unit": "minute" + } + } + }, + "pillar": "Maintainability" + } + ], + "gpa": null, + "worker_version": 69440 + }, + "meta": { + "issues_count": 127, + "measures": { + "remediation": { "value": 7186.52, "unit": "minute" }, + "technical_debt_ratio": { + "value": 5.264879308188034, + "unit": "percent", + "meta": { + "remediation_time": { "value": 7186.52, "unit": "minute" }, + "implementation_time": { + "value": 136499.2353922225, + "unit": "minute" + } + } + } + } + } + } +} diff --git a/plugins/code-climate/src/api/mock/code-climate-test-coverage-mock.json b/plugins/code-climate/src/api/mock/code-climate-test-coverage-mock.json new file mode 100644 index 0000000000..203dd81776 --- /dev/null +++ b/plugins/code-climate/src/api/mock/code-climate-test-coverage-mock.json @@ -0,0 +1,21 @@ +{ + "data": { + "id": "6b8cc37a64b741dd9d516119", + "type": "test_reports", + "attributes": { + "branch": "master", + "commit_sha": "d670460b4b4aece5915caf5c68d12f560a9fe3e4", + "committed_at": "2022-01-14T15:17:26.000Z", + "covered_percent": 88.41059602649007, + "lines_of_code": 8854, + "rating": { + "path": "/", + "letter": "B", + "measure": { "value": 88.41059602649007, "unit": "percent" }, + "pillar": "Test Coverage" + }, + "received_at": "2022-01-14T15:25:38.352Z", + "state": "done" + } + } +} diff --git a/plugins/code-climate/src/api/mock/index.ts b/plugins/code-climate/src/api/mock/index.ts new file mode 100644 index 0000000000..56ba9c7974 --- /dev/null +++ b/plugins/code-climate/src/api/mock/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { mockData } from './mock-api'; +export { MockCodeClimateApi } from './mock-api'; diff --git a/plugins/code-climate/src/api/mock/mock-api.ts b/plugins/code-climate/src/api/mock/mock-api.ts new file mode 100644 index 0000000000..74e4d83027 --- /dev/null +++ b/plugins/code-climate/src/api/mock/mock-api.ts @@ -0,0 +1,56 @@ +/* + * 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 { CodeClimateData } from '../code-climate-data'; +import { CodeClimateApi } from '../code-climate-api'; +import maintainabilityMock from './code-climate-maintainability-mock.json'; +import testCoverageMock from './code-climate-test-coverage-mock.json'; +import { Duration } from 'luxon'; +import humanizeDuration from 'humanize-duration'; + +const maintainabilityData = maintainabilityMock.data.attributes.ratings[0]; +const testCoverageData = testCoverageMock.data.attributes.rating; + +const maintainabilityValue: any = {}; +maintainabilityValue[ + maintainabilityData.measure.meta.implementation_time.unit +] = maintainabilityData.measure.meta.implementation_time.value.toFixed(); + +export const mockData: CodeClimateData = { + repoID: '6b8cc37a64b741dd9d516119', + maintainability: { + letter: maintainabilityData.letter, + value: humanizeDuration( + Duration.fromObject(maintainabilityValue).toMillis(), + { largest: 1 }, + ), + }, + testCoverage: { + letter: testCoverageData.letter, + value: testCoverageData.measure.value.toFixed(), + }, + numberOfCodeSmells: 97, + numberOfDuplication: 49, + numberOfOtherIssues: 26, +}; + +export class MockCodeClimateApi implements CodeClimateApi { + fetchData(): Promise { + return new Promise(resolve => { + setTimeout(() => resolve(mockData), 800); + }); + } +} diff --git a/plugins/code-climate/src/api/production-api.ts b/plugins/code-climate/src/api/production-api.ts new file mode 100644 index 0000000000..665d7e9f63 --- /dev/null +++ b/plugins/code-climate/src/api/production-api.ts @@ -0,0 +1,160 @@ +/* + * 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 { + CodeClimateData, + CodeClimateRepoData, + CodeClimateMaintainabilityData, + CodeClimateTestCoverageData, + CodeClimateIssuesData, +} from './code-climate-data'; +import { CodeClimateApi } from './code-climate-api'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { Duration } from 'luxon'; +import humanizeDuration from 'humanize-duration'; + +const pageQuery = encodeURIComponent('page[size]'); +const statusFilter = encodeURIComponent('filter[status][$in][]'); +const categoriesFilter = encodeURIComponent('filter[categories][$in][]'); +const basicIssuesOptions = `${pageQuery}=1&${statusFilter}=open&${statusFilter}=confirmed`; +const codeSmellsQuery = `${basicIssuesOptions}&${categoriesFilter}=Complexity`; +const duplicationQuery = `${basicIssuesOptions}&${categoriesFilter}=Duplication`; +const otherIssuesQuery = `${basicIssuesOptions}&${categoriesFilter}=Bug%20Risk`; + +export class ProductionCodeClimateApi implements CodeClimateApi { + constructor(private readonly discoveryApi: DiscoveryApi) {} + + async fetchAllData(options: { + apiUrl: string; + repoID: string; + snapshotID: string; + testReportID: string; + }): Promise { + const { apiUrl, repoID, snapshotID, testReportID } = options; + + const [ + maintainabilityResponse, + testCoverageResponse, + codeSmellsResponse, + duplicationResponse, + otherIssuesResponse, + ] = await Promise.all([ + await fetch(`${apiUrl}/repos/${repoID}/snapshots/${snapshotID}`), + await fetch(`${apiUrl}/repos/${repoID}/test_reports/${testReportID}`), + await fetch( + `${apiUrl}/repos/${repoID}/snapshots/${snapshotID}/issues?${codeSmellsQuery}`, + ), + await fetch( + `${apiUrl}/repos/${repoID}/snapshots/${snapshotID}/issues?${duplicationQuery}`, + ), + await fetch( + `${apiUrl}/repos/${repoID}/snapshots/${snapshotID}/issues?${otherIssuesQuery}`, + ), + ]); + + if ( + !maintainabilityResponse.ok || + !testCoverageResponse.ok || + !codeSmellsResponse.ok || + !duplicationResponse.ok || + !otherIssuesResponse.ok + ) { + throw new Error('Failed fetching Code Climate info'); + } + + const maintainabilityData = ( + (await maintainabilityResponse.json()) as CodeClimateMaintainabilityData + ).data.attributes.ratings[0]; + const testCoverageData = ( + (await testCoverageResponse.json()) as CodeClimateTestCoverageData + ).data.attributes.rating; + const codeSmellsData = ( + (await codeSmellsResponse.json()) as CodeClimateIssuesData + ).meta.total_count; + const duplicationData = ( + (await duplicationResponse.json()) as CodeClimateIssuesData + ).meta.total_count; + const otherIssuesData = ( + (await otherIssuesResponse.json()) as CodeClimateIssuesData + ).meta.total_count; + + return [ + maintainabilityData, + testCoverageData, + codeSmellsData, + duplicationData, + otherIssuesData, + ]; + } + + async fetchData(repoID: string): Promise { + if (!repoID) { + throw new Error('No Repo id found'); + } + + const apiUrl = `${await this.discoveryApi.getBaseUrl( + 'proxy', + )}/codeclimate/api`; + + const repoResponse = await fetch(`${apiUrl}/repos/${repoID}`); + + if (!repoResponse.ok) { + throw new Error('Failed fetching Code Climate info'); + } + + const repoData = ((await repoResponse.json()) as CodeClimateRepoData).data; + const snapshotID = + repoData.relationships.latest_default_branch_snapshot.data.id; + const testReportID = + repoData.relationships.latest_default_branch_test_report.data.id; + + const [ + maintainabilityData, + testCoverageData, + codeSmellsData, + duplicationData, + otherIssuesData, + ] = await this.fetchAllData({ + apiUrl, + repoID, + snapshotID, + testReportID, + }); + + const maintainabilityValue: any = {}; + maintainabilityValue[ + maintainabilityData.measure.meta.implementation_time.unit + ] = maintainabilityData.measure.meta.implementation_time.value.toFixed(); + + return { + repoID, + maintainability: { + letter: maintainabilityData.letter, + value: humanizeDuration( + Duration.fromObject(maintainabilityValue).toMillis(), + { largest: 1 }, + ), + }, + testCoverage: { + letter: testCoverageData.letter, + value: testCoverageData.measure.value.toFixed(), + }, + numberOfCodeSmells: codeSmellsData, + numberOfDuplication: duplicationData, + numberOfOtherIssues: otherIssuesData, + }; + } +} diff --git a/plugins/code-climate/src/components/CodeClimateCard/CodeClimateCard.tsx b/plugins/code-climate/src/components/CodeClimateCard/CodeClimateCard.tsx new file mode 100644 index 0000000000..82af326690 --- /dev/null +++ b/plugins/code-climate/src/components/CodeClimateCard/CodeClimateCard.tsx @@ -0,0 +1,25 @@ +/* + * 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 { InfoCard } from '@backstage/core-components'; +import { CodeClimateCardContents } from '../CodeClimateCardContents'; + +export const CodeClimateCard = () => ( + + + +); diff --git a/plugins/code-climate/src/components/CodeClimateCard/index.ts b/plugins/code-climate/src/components/CodeClimateCard/index.ts new file mode 100644 index 0000000000..51eaaf71af --- /dev/null +++ b/plugins/code-climate/src/components/CodeClimateCard/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { CodeClimateCard } from './CodeClimateCard'; diff --git a/plugins/code-climate/src/components/CodeClimateCardContents/CodeClimateCardContents.tsx b/plugins/code-climate/src/components/CodeClimateCardContents/CodeClimateCardContents.tsx new file mode 100644 index 0000000000..c9dc1894fc --- /dev/null +++ b/plugins/code-climate/src/components/CodeClimateCardContents/CodeClimateCardContents.tsx @@ -0,0 +1,64 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import React from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import { codeClimateApiRef } from '../../api'; +import { CodeClimateTable } from '../CodeClimateTable'; +import { CODECLIMATE_REPO_ID_ANNOTATION } from '../../plugin'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { + EmptyState, + ErrorPanel, + MissingAnnotationEmptyState, + Progress, +} from '@backstage/core-components'; +import { useApi } from '@backstage/core-plugin-api'; + +export const CodeClimateCardContents = () => { + const { entity } = useEntity(); + const codeClimateApi = useApi(codeClimateApiRef); + + const repoID = + entity?.metadata.annotations?.[CODECLIMATE_REPO_ID_ANNOTATION] ?? ''; + + const { loading, value, error } = useAsync( + () => codeClimateApi.fetchData(repoID), + [codeClimateApi, repoID], + ); + + if (loading) { + return ; + } else if (!repoID) { + return ( + + ); + } else if (error) { + return ; + } else if (!value) { + return ( + + ); + } + + return ; +}; diff --git a/plugins/code-climate/src/components/CodeClimateCardContents/index.ts b/plugins/code-climate/src/components/CodeClimateCardContents/index.ts new file mode 100644 index 0000000000..29f59be2a3 --- /dev/null +++ b/plugins/code-climate/src/components/CodeClimateCardContents/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { CodeClimateCardContents } from './CodeClimateCardContents'; diff --git a/plugins/code-climate/src/components/CodeClimateTable/CodeClimateTable.test.tsx b/plugins/code-climate/src/components/CodeClimateTable/CodeClimateTable.test.tsx new file mode 100644 index 0000000000..a2ced6f5a2 --- /dev/null +++ b/plugins/code-climate/src/components/CodeClimateTable/CodeClimateTable.test.tsx @@ -0,0 +1,37 @@ +/* + * 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 { render } from '@testing-library/react'; +import { CodeClimateTable } from './CodeClimateTable'; +import { mockData } from '../../api/mock'; +import { ThemeProvider } from '@material-ui/core'; +import { lightTheme } from '@backstage/theme'; + +describe('CodeClimateTable', () => { + it('should render values in a table', async () => { + const table = await render( + + + , + ); + expect(await table.findByText('3 months')).toBeInTheDocument(); + expect(await table.findByText('88%')).toBeInTheDocument(); + expect(await table.findByText('97')).toBeInTheDocument(); + expect(await table.findByText('49')).toBeInTheDocument(); + expect(await table.findByText('26')).toBeInTheDocument(); + }); +}); diff --git a/plugins/code-climate/src/components/CodeClimateTable/CodeClimateTable.tsx b/plugins/code-climate/src/components/CodeClimateTable/CodeClimateTable.tsx new file mode 100644 index 0000000000..e8e204ff9a --- /dev/null +++ b/plugins/code-climate/src/components/CodeClimateTable/CodeClimateTable.tsx @@ -0,0 +1,212 @@ +/* + * 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 { CodeClimateData } from '../../api'; +import { Link } from '@backstage/core-components'; +import { Box, makeStyles, Typography } from '@material-ui/core'; +import { BackstageTheme } from '@backstage/theme'; + +const letterStyle = { + color: 'white', + border: 0, + borderRadius: '3px', + fontSize: '40px', + padding: '5px 20px', +}; + +const fontSize = { + fontSize: '25px', +}; + +const letterColor = (letter: string) => { + if (letter === 'A') { + return '#45d298'; + } else if (letter === 'B') { + return '#a5d86e'; + } else if (letter === 'C') { + return '#f1ce0c'; + } else if (letter === 'D') { + return '#f29141'; + } else if (letter === 'F') { + return '#df5869'; + } + + return '#45d298'; +}; + +const useStyles = makeStyles< + BackstageTheme, + { + maintainabilityLetter: string; + testCoverageLetter: string; + } +>({ + spaceAround: { + display: 'flex', + justifyContent: 'space-around', + }, + spaceBetween: { + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + }, + maintainabilityLetterColor: { + ...letterStyle, + backgroundColor: props => letterColor(props.maintainabilityLetter), + }, + testCoverageLetterColor: { + ...letterStyle, + backgroundColor: props => letterColor(props.testCoverageLetter), + }, + fontSize: { + ...fontSize, + }, + letterDetails: { + ...fontSize, + paddingLeft: '10px', + }, + paddingSides20: { + padding: '0px 20px', + }, +}); + +export const CodeClimateTable = ({ + codeClimateData, +}: { + codeClimateData: CodeClimateData; +}) => { + const { + repoID, + maintainability: { + letter: maintainabilityLetter, + value: maintainabilityValue, + }, + testCoverage: { letter: testCoverageLetter, value: testCoverageValue }, + numberOfCodeSmells, + numberOfDuplication, + numberOfOtherIssues, + } = codeClimateData; + + const classes = useStyles({ maintainabilityLetter, testCoverageLetter }); + + if (!codeClimateData) { + return null; + } + + return ( + <> +
+
+ + Maintainability + +
+ + {maintainabilityLetter} + + + + {maintainabilityValue} + + +
+
+
+ + Test Coverage + +
+ + {testCoverageLetter} + + + + {testCoverageValue}% + + +
+
+
+ +
+ + Code Smells: + + + + {numberOfCodeSmells} + + +
+ + + Duplication: + + + + {numberOfDuplication} + + + +
+ + Other Issues: + + + + {numberOfOtherIssues} + + +
+
+ + ); +}; diff --git a/plugins/code-climate/src/components/CodeClimateTable/index.ts b/plugins/code-climate/src/components/CodeClimateTable/index.ts new file mode 100644 index 0000000000..bdf79bb94b --- /dev/null +++ b/plugins/code-climate/src/components/CodeClimateTable/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { CodeClimateTable } from './CodeClimateTable'; diff --git a/plugins/code-climate/src/index.ts b/plugins/code-climate/src/index.ts new file mode 100644 index 0000000000..b6ef174b8f --- /dev/null +++ b/plugins/code-climate/src/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * A Backstage plugin that integrates towards Code Climate + * + * @packageDocumentation + */ + +export * from './api'; +export { codeClimatePlugin, EntityCodeClimateCard } from './plugin'; diff --git a/plugins/code-climate/src/plugin.test.ts b/plugins/code-climate/src/plugin.test.ts new file mode 100644 index 0000000000..ea791f2c87 --- /dev/null +++ b/plugins/code-climate/src/plugin.test.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { codeClimatePlugin } from './plugin'; + +describe('codeclimate', () => { + it('should export plugin', () => { + expect(codeClimatePlugin).toBeDefined(); + }); +}); diff --git a/plugins/code-climate/src/plugin.ts b/plugins/code-climate/src/plugin.ts new file mode 100644 index 0000000000..dcf86f1e19 --- /dev/null +++ b/plugins/code-climate/src/plugin.ts @@ -0,0 +1,58 @@ +/* + * 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 { ProductionCodeClimateApi, codeClimateApiRef } from './api'; +import { + createApiFactory, + createPlugin, + createRouteRef, + discoveryApiRef, + identityApiRef, + createComponentExtension, +} from '@backstage/core-plugin-api'; + +export const CODECLIMATE_REPO_ID_ANNOTATION = 'codeclimate.com/repo-id'; + +export const rootRouteRef = createRouteRef({ + id: 'code-climate', +}); + +export const codeClimatePlugin = createPlugin({ + id: 'code-climate', + apis: [ + createApiFactory({ + api: codeClimateApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi }) => new ProductionCodeClimateApi(discoveryApi), + }), + ], + routes: { + root: rootRouteRef, + }, +}); + +export const EntityCodeClimateCard = codeClimatePlugin.provide( + createComponentExtension({ + name: 'EntityCodeClimateCard', + component: { + lazy: () => + import('./components/CodeClimateCard').then(m => m.CodeClimateCard), + }, + }), +); diff --git a/plugins/code-climate/src/setupTests.ts b/plugins/code-climate/src/setupTests.ts new file mode 100644 index 0000000000..43d9e09713 --- /dev/null +++ b/plugins/code-climate/src/setupTests.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/* eslint-disable import/no-extraneous-dependencies */ + +import '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 5e0bbb0c0f..d5442a0d6c 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-code-coverage-backend +## 0.1.25 + +### Patch Changes + +- 67a7c02d26: Remove usages of `EntityRef` and `parseEntityName` from `@backstage/catalog-model` +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/integration@0.7.5 + +## 0.1.24 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/integration@0.7.4 + +## 0.1.23 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + ## 0.1.22 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index c5c77f9873..7ce90f4165 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage-backend", "description": "A Backstage backend plugin that helps you keep track of your code coverage", - "version": "0.1.22", + "version": "0.1.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,22 +10,25 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -36,7 +39,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.35.0", diff --git a/plugins/code-coverage-backend/src/service/CodeCoverageDatabase.ts b/plugins/code-coverage-backend/src/service/CodeCoverageDatabase.ts index 580d560bae..9235f3a8fd 100644 --- a/plugins/code-coverage-backend/src/service/CodeCoverageDatabase.ts +++ b/plugins/code-coverage-backend/src/service/CodeCoverageDatabase.ts @@ -15,7 +15,7 @@ */ import { resolvePackagePath } from '@backstage/backend-common'; import { NotFoundError } from '@backstage/errors'; -import { parseEntityName, stringifyEntityRef } from '@backstage/catalog-model'; +import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { Knex } from 'knex'; import { v4 as uuid } from 'uuid'; import { aggregateCoverage } from './CoverageUtils'; @@ -103,7 +103,7 @@ export class CodeCoverageDatabase implements CodeCoverageStore { .map(r => JSON.parse(r.coverage)) .map(c => aggregateCoverage(c)); - const entityName = parseEntityName(entity); + const entityName = parseEntityRef(entity); return { entity: { diff --git a/plugins/code-coverage-backend/src/service/router.ts b/plugins/code-coverage-backend/src/service/router.ts index 5f136cc8e9..4922f5c887 100644 --- a/plugins/code-coverage-backend/src/service/router.ts +++ b/plugins/code-coverage-backend/src/service/router.ts @@ -34,9 +34,8 @@ import { Cobertura } from './converter/cobertura'; import { Jacoco } from './converter/jacoco'; import { Converter } from './converter'; import { - EntityRef, getEntitySourceLocation, - parseEntityName, + parseEntityRef, } from '@backstage/catalog-model'; export interface RouterOptions { @@ -78,7 +77,7 @@ export const makeRouter = async ( */ router.get('/report', async (req, res) => { const { entity } = req.query; - const entityName = parseEntityName(entity as EntityRef); + const entityName = parseEntityRef(entity as string); const entityLookup = await catalogApi.getEntityByName(entityName); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); @@ -101,7 +100,7 @@ export const makeRouter = async ( */ router.get('/history', async (req, res) => { const { entity } = req.query; - const entityName = parseEntityName(entity as EntityRef); + const entityName = parseEntityRef(entity as string); const entityLookup = await catalogApi.getEntityByName(entityName); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); @@ -120,7 +119,7 @@ export const makeRouter = async ( */ router.get('/file-content', async (req, res) => { const { entity, path } = req.query; - const entityName = parseEntityName(entity as EntityRef); + const entityName = parseEntityRef(entity as string); const entityLookup = await catalogApi.getEntityByName(entityName); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); @@ -172,7 +171,7 @@ export const makeRouter = async ( */ router.post('/report', async (req, res) => { const { entity, coverageType } = req.query; - const entityName = parseEntityName(entity as EntityRef); + const entityName = parseEntityRef(entity as string); const entityLookup = await catalogApi.getEntityByName(entityName); if (!entityLookup) { throw new NotFoundError(`No entity found matching ${entity}`); diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index 2fc3acda84..aca8a8ba26 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-code-coverage +## 0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.26 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + ## 0.1.25 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 49f092bf25..ba6d792aba 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.1.25", + "version": "0.1.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,24 +10,27 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -43,10 +46,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index a8b6f14292..83868bd498 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-config-schema +## 0.1.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.22 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + - @backstage/types@0.1.2 + ## 0.1.21 ### Patch Changes diff --git a/plugins/config-schema/dev/example-schema.json b/plugins/config-schema/dev/example-schema.json index ac5c2ad347..be297aaa8d 100644 --- a/plugins/config-schema/dev/example-schema.json +++ b/plugins/config-schema/dev/example-schema.json @@ -746,36 +746,6 @@ "description": "List of processor-specific options and attributes", "type": "object", "properties": { - "githubOrg": { - "description": "GithubOrgReaderProcessor configuration", - "type": "object", - "required": ["providers"], - "properties": { - "providers": { - "description": "The configuration parameters for each single GitHub org provider.", - "type": "array", - "items": { - "type": "object", - "required": ["target"], - "properties": { - "target": { - "description": "The prefix of the target that this matches on, e.g.\n\"https://github.com\", with no trailing slash.", - "type": "string" - }, - "apiBaseUrl": { - "description": "The base URL of the API of this provider, e.g.\n\"https://api.github.com\", with no trailing slash.\n\nMay be omitted specifically for GitHub; then it will be deduced.", - "type": "string" - }, - "token": { - "description": "The authorization token to use for requests to this provider.\n\nIf no token is specified, anonymous access is used.", - "visibility": "secret", - "type": "string" - } - } - } - } - } - }, "ldapOrg": { "description": "LdapOrgReaderProcessor configuration", "type": "object", diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 46e030dbc8..e52654b506 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-config-schema", "description": "A Backstage plugin that lets you browse the configuration schema of your app", - "version": "0.1.21", + "version": "0.1.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,23 +10,26 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/theme": "^0.2.14", - "@backstage/types": "^0.1.1", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/theme": "^0.2.15", + "@backstage/types": "^0.1.3", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -38,10 +41,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index e1f3caf8ec..1520f476b6 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-cost-insights +## 0.11.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.11.21 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + ## 0.11.20 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 45178cb475..64613102cc 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cost-insights", "description": "A Backstage plugin that helps you keep track of your cloud spend", - "version": "0.11.20", + "version": "0.11.22", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,21 +24,21 @@ "backstage" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -57,10 +60,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md index b546ab7408..9c8e4da88a 100644 --- a/plugins/explore-react/CHANGELOG.md +++ b/plugins/explore-react/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-explore-react +## 0.0.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@0.7.0 + +## 0.0.12 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-plugin-api@0.6.1 + ## 0.0.11 ### Patch Changes diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 3da9de5e08..468989d414 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore-react", "description": "A frontend library for Backstage plugins that want to interact with the explore plugin", - "version": "0.0.11", + "version": "0.0.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,20 +24,21 @@ "backstage" ], "scripts": { - "build": "backstage-cli plugin:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "clean": "backstage-cli clean", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/core-plugin-api": "^0.6.0" + "@backstage/core-plugin-api": "^0.7.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", - "@backstage/dev-utils": "^0.2.21-next.0", - "@backstage/test-utils": "^0.2.4-next.0", + "@backstage/cli": "^0.14.1", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index d417976d1c..319197565d 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-explore +## 0.3.31 + +### Patch Changes + +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/plugin-explore-react@0.0.13 + +## 0.3.30 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/plugin-explore-react@0.0.12 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.3.29 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 7cb22f5782..5eeb8e2a7c 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore", "description": "A Backstage plugin for building an exploration page of your software ecosystem", - "version": "0.3.29", + "version": "0.3.31", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,22 +24,22 @@ "backstage" ], "scripts": { - "build": "backstage-cli plugin:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "start": "backstage-cli plugin:serve" + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/plugin-explore-react": "^0.0.11", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/plugin-explore-react": "^0.0.13", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -50,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore/src/components/DefaultExplorePage/DefaultExplorePage.test.tsx b/plugins/explore/src/components/DefaultExplorePage/DefaultExplorePage.test.tsx index d10c262357..53b225daaf 100644 --- a/plugins/explore/src/components/DefaultExplorePage/DefaultExplorePage.test.tsx +++ b/plugins/explore/src/components/DefaultExplorePage/DefaultExplorePage.test.tsx @@ -24,14 +24,14 @@ describe('', () => { const catalogApi: jest.Mocked = { addLocation: jest.fn(_a => new Promise(() => {})), getEntities: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), getLocationById: jest.fn(), removeLocationById: jest.fn(), removeEntityByUid: jest.fn(), getEntityByName: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; const Wrapper = ({ children }: { children?: React.ReactNode }) => ( diff --git a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx index e36bd4ddb9..1310d9f31e 100644 --- a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx +++ b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx @@ -25,14 +25,14 @@ describe('', () => { const catalogApi: jest.Mocked = { addLocation: jest.fn(_a => new Promise(() => {})), getEntities: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), getLocationById: jest.fn(), removeLocationById: jest.fn(), removeEntityByUid: jest.fn(), getEntityByName: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; const Wrapper = ({ children }: { children?: React.ReactNode }) => ( diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx index 5d3f7c358b..255db061aa 100644 --- a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx +++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx @@ -25,14 +25,14 @@ describe('', () => { const catalogApi: jest.Mocked = { addLocation: jest.fn(_a => new Promise(() => {})), getEntities: jest.fn(), - getOriginLocationByEntity: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), getLocationById: jest.fn(), removeLocationById: jest.fn(), removeEntityByUid: jest.fn(), getEntityByName: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; const Wrapper = ({ children }: { children?: React.ReactNode }) => ( diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md index e446663842..e553158bf4 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-firehydrant +## 0.1.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.16 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 3a2a893df6: chore(deps): bump `luxon` from 2.0.2 to 2.3.0 +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/theme@0.2.15 + ## 0.1.15 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 81272e2b6d..07a3938e49 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-firehydrant", "description": "A Backstage plugin that integrates towards FireHydrant", - "version": "0.1.15", + "version": "0.1.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,35 +11,38 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", - "luxon": "^1.27.0", + "luxon": "^2.0.2", "react-use": "^17.2.4" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 6e42e95021..c55ebb1aea 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-fossa +## 0.2.32 + +### Patch Changes + +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.31 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.2.30 ### Patch Changes diff --git a/plugins/fossa/dev/index.tsx b/plugins/fossa/dev/index.tsx index 018d3e3acb..1ce9ec58b2 100644 --- a/plugins/fossa/dev/index.tsx +++ b/plugins/fossa/dev/index.tsx @@ -42,7 +42,8 @@ const entity = (name?: string) => relations: [ { type: RELATION_OWNED_BY, - target: { kind: 'Group', namespace: 'default', name }, + targetRef: `group:default/${name}`, + target: { kind: 'group', namespace: 'default', name }, }, ], } as Entity); diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 53e40aae96..f6ed9df07f 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-fossa", "description": "A Backstage plugin that integrates towards FOSSA", - "version": "0.2.30", + "version": "0.2.32", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,22 +25,22 @@ "fossa" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -50,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx b/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx index 421ff4431f..2759afd585 100644 --- a/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx +++ b/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx @@ -30,13 +30,13 @@ describe('', () => { addLocation: jest.fn(), getEntities: jest.fn(), getEntityByName: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), getLocationById: jest.fn(), - getOriginLocationByEntity: jest.fn(), removeEntityByUid: jest.fn(), removeLocationById: jest.fn(), refreshEntity: jest.fn(), getEntityAncestors: jest.fn(), + getEntityFacets: jest.fn(), }; const fossaApi: jest.Mocked = { getFindingSummary: jest.fn(), diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index 80761a4736..b23adf1239 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-gcp-projects +## 0.3.19 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + +## 0.3.18 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- e242726f78: Switches to use react-hookz in place of react-use. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/theme@0.2.15 + ## 0.3.17 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 992336300f..dc1eb7d5b0 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gcp-projects", "description": "A Backstage plugin that helps you manage projects in GCP", - "version": "0.3.17", + "version": "0.3.19", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,33 +24,33 @@ "google cloud" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/theme": "^0.2.14", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "react-router-dom": "6.0.0-beta.0", - "react-use": "^17.2.4" + "@react-hookz/web": "^12.3.0" }, "peerDependencies": { "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx index 36247ac492..b900b81695 100644 --- a/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectDetailsPage/ProjectDetailsPage.tsx @@ -27,7 +27,7 @@ import { Typography, } from '@material-ui/core'; import React from 'react'; -import useAsync from 'react-use/lib/useAsync'; +import { useAsync, useMountEffect } from '@react-hookz/web'; import { gcpApiRef } from '../../api'; import { @@ -60,19 +60,13 @@ const DetailsPage = () => { const api = useApi(gcpApiRef); const classes = useStyles(); - const { - loading, - error, - value: details, - } = useAsync( - async () => - api.getProject( - decodeURIComponent(location.search.split('projectId=')[1]), - ), - [location.search], + const [{ status, result: details, error }, { execute }] = useAsync(async () => + api.getProject(decodeURIComponent(location.search.split('projectId=')[1])), ); - if (loading) { + useMountEffect(execute); + + if (status === 'loading') { return ; } else if (error) { return ( diff --git a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx index d5a57432b3..dea12f4b60 100644 --- a/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx +++ b/plugins/gcp-projects/src/components/ProjectListPage/ProjectListPage.tsx @@ -18,7 +18,7 @@ import { Button, LinearProgress, Tooltip, Typography } from '@material-ui/core'; import React from 'react'; -import useAsync from 'react-use/lib/useAsync'; +import { useAsync, useMountEffect } from '@react-hookz/web'; import { gcpApiRef, Project } from '../../api'; import { @@ -58,9 +58,12 @@ const labels = ( const PageContents = () => { const api = useApi(gcpApiRef); - const { loading, error, value } = useAsync(() => api.listProjects()); + const [{ status, result, error }, { execute }] = useAsync(() => + api.listProjects(), + ); + useMountEffect(execute); - if (loading) { + if (status === 'loading') { return ; } else if (error) { return ( @@ -108,7 +111,7 @@ const PageContents = () => { }, ]} data={ - value?.map((project: Project) => ({ + result?.map((project: Project) => ({ id: project.projectId, name: project.name, projectNumber: project?.projectNumber || 'Error', diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index db988dbc9b..eb6f3c6460 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,26 @@ # @backstage/plugin-git-release-manager +## 0.3.13 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration@0.7.5 + +## 0.3.12 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/integration@0.7.3 + - @backstage/theme@0.2.15 + ## 0.3.11 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 381ca0dd24..0534039d52 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-git-release-manager", "description": "A Backstage plugin that helps you manage releases in git", - "version": "0.3.11", + "version": "0.3.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,21 +10,24 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/integration": "^0.7.2", - "@backstage/theme": "^0.2.14", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/integration": "^0.7.5", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -40,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index 2399292e8d..d48aa303d0 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-github-actions +## 0.5.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration@0.7.5 + +## 0.4.36 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 7efee0f94c: Fixed an issue where an error message was being displayed on the initial component render. This was due to values owner/repo not yet being set. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/integration@0.7.3 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.4.35 ### Patch Changes diff --git a/plugins/github-actions/api-report.md b/plugins/github-actions/api-report.md index fcb3209fb9..ee3e1d679d 100644 --- a/plugins/github-actions/api-report.md +++ b/plugins/github-actions/api-report.md @@ -31,9 +31,7 @@ export enum BuildStatus { // Warning: (ae-missing-release-tag) "EntityGithubActionsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityGithubActionsContent: (_props: { - entity?: Entity | undefined; -}) => JSX.Element; +export const EntityGithubActionsContent: () => JSX.Element; // Warning: (ae-missing-release-tag) "EntityLatestGithubActionRunCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -42,7 +40,6 @@ export const EntityLatestGithubActionRunCard: ({ branch, variant, }: { - entity?: Entity | undefined; branch: string; variant?: InfoCardVariants | undefined; }) => JSX.Element; @@ -54,7 +51,6 @@ export const EntityLatestGithubActionsForBranchCard: ({ branch, variant, }: { - entity?: Entity | undefined; branch: string; variant?: InfoCardVariants | undefined; }) => JSX.Element; @@ -315,7 +311,7 @@ export type Jobs = { export const LatestWorkflowRunCard: ({ branch, variant, -}: Props_3) => JSX.Element; +}: Props_2) => JSX.Element; // Warning: (ae-missing-release-tag) "LatestWorkflowsForBranchCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -323,7 +319,7 @@ export const LatestWorkflowRunCard: ({ export const LatestWorkflowsForBranchCard: ({ branch, variant, -}: Props_3) => JSX.Element; +}: Props_2) => JSX.Element; // Warning: (ae-missing-release-tag) "RecentWorkflowRunsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -335,11 +331,10 @@ export const RecentWorkflowRunsCard: ({ variant, }: Props) => JSX.Element; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const Router: (_props: Props_2) => JSX.Element; +export const Router: () => JSX.Element; // Warning: (ae-missing-release-tag) "Step" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index cb112fedb1..2802374a61 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-actions", "description": "A Backstage plugin that integrates towards GitHub Actions", - "version": "0.4.35", + "version": "0.5.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -23,22 +26,22 @@ "github actions" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/integration": "^0.7.2", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/integration": "^0.7.5", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -52,10 +55,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/github-actions/src/components/Cards/Cards.tsx b/plugins/github-actions/src/components/Cards/Cards.tsx index cb4b2190fc..61a5146bee 100644 --- a/plugins/github-actions/src/components/Cards/Cards.tsx +++ b/plugins/github-actions/src/components/Cards/Cards.tsx @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { readGitHubIntegrationConfigs } from '@backstage/integration'; import { useEntity } from '@backstage/plugin-catalog-react'; import { @@ -24,7 +23,7 @@ import { } from '@material-ui/core'; import ExternalLinkIcon from '@material-ui/icons/Launch'; import React, { useEffect } from 'react'; -import { GITHUB_ACTIONS_ANNOTATION } from '../useProjectName'; +import { GITHUB_ACTIONS_ANNOTATION } from '../getProjectNameFromEntity'; import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns'; import { WorkflowRunsTable } from '../WorkflowRunsTable'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; @@ -122,8 +121,6 @@ export const LatestWorkflowRunCard = ({ }; type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; branch: string; variant?: InfoCardVariants; }; diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx index 0f9573e051..a987a8569f 100644 --- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx +++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx @@ -75,7 +75,7 @@ describe('', () => { jest.resetAllMocks(); }); - const renderSubject = (props: RecentWorkflowRunsCardProps = { entity }) => + const renderSubject = (props: RecentWorkflowRunsCardProps = {}) => render( @@ -85,7 +85,7 @@ describe('', () => { [configApiRef, configApi], ]} > - + @@ -113,7 +113,7 @@ describe('', () => { it('requests only the required number of workflow runs', async () => { const limit = 3; - renderSubject({ entity, limit }); + renderSubject({ limit }); expect(useWorkflowRuns).toHaveBeenCalledWith( expect.objectContaining({ initialPageSize: limit }), ); @@ -128,7 +128,7 @@ describe('', () => { it('filters workflows by branch if one is specified', async () => { const branch = 'master'; - renderSubject({ entity, branch }); + renderSubject({ branch }); expect(useWorkflowRuns).toHaveBeenCalledWith( expect.objectContaining({ branch }), ); diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx index 3423bea84d..4e37a2b19b 100644 --- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx +++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.tsx @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { readGitHubIntegrationConfigs } from '@backstage/integration'; import { useEntity } from '@backstage/plugin-catalog-react'; import React, { useEffect } from 'react'; import { generatePath, Link as RouterLink } from 'react-router-dom'; -import { GITHUB_ACTIONS_ANNOTATION } from '../useProjectName'; +import { GITHUB_ACTIONS_ANNOTATION } from '../getProjectNameFromEntity'; import { useWorkflowRuns } from '../useWorkflowRuns'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { Typography } from '@material-ui/core'; @@ -34,8 +33,6 @@ import { const firstLine = (message: string): string => message.split('\n')[0]; export type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; branch?: string; dense?: boolean; limit?: number; diff --git a/plugins/github-actions/src/components/Router.tsx b/plugins/github-actions/src/components/Router.tsx index ea07213c45..793293cce8 100644 --- a/plugins/github-actions/src/components/Router.tsx +++ b/plugins/github-actions/src/components/Router.tsx @@ -20,18 +20,13 @@ import { Routes, Route } from 'react-router'; import { buildRouteRef } from '../routes'; import { WorkflowRunDetails } from './WorkflowRunDetails'; import { WorkflowRunsTable } from './WorkflowRunsTable'; -import { GITHUB_ACTIONS_ANNOTATION } from './useProjectName'; +import { GITHUB_ACTIONS_ANNOTATION } from './getProjectNameFromEntity'; import { MissingAnnotationEmptyState } from '@backstage/core-components'; export const isGithubActionsAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION]); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; - -export const Router = (_props: Props) => { +export const Router = () => { const { entity } = useEntity(); if (!isGithubActionsAvailable(entity)) { diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index aebf7dfa01..989ab27800 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -39,7 +39,7 @@ import ExternalLinkIcon from '@material-ui/icons/Launch'; import { DateTime } from 'luxon'; import React from 'react'; import { Job, Jobs, Step } from '../../api'; -import { useProjectName } from '../useProjectName'; +import { getProjectNameFromEntity } from '../getProjectNameFromEntity'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import { useWorkflowRunJobs } from './useWorkflowRunJobs'; import { useWorkflowRunsDetails } from './useWorkflowRunsDetails'; @@ -164,25 +164,25 @@ const JobsList = ({ jobs, entity }: { jobs?: Jobs; entity: Entity }) => { export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { const config = useApi(configApiRef); - const projectName = useProjectName(entity); + const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation const hostname = readGitHubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; - const [owner, repo] = projectName.value ? projectName.value.split('/') : []; + const [owner, repo] = (projectName && projectName.split('/')) || []; const details = useWorkflowRunsDetails({ hostname, owner, repo }); const jobs = useWorkflowRunJobs({ hostname, owner, repo }); - const error = projectName.error || (projectName.value && details.error); const classes = useStyles(); - if (error) { + + if (details.error && details.error.message) { return ( - Failed to load build, {error.message} + Failed to load build, {details.error.message} ); - } else if (projectName.loading || details.loading) { + } else if (details.loading) { return ; } return ( diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts index 002d379ed1..ba0f3b6ba8 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts +++ b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunJobs.ts @@ -37,7 +37,7 @@ export const useWorkflowRunJobs = ({ repo, id: parseInt(id, 10), }) - : Promise.reject('No repo/owner provided'); + : Promise.reject(new Error('No repo/owner provided')); }, [repo, owner, id]); return jobs; }; diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts index ef9b4f7ccf..10a043161e 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts +++ b/plugins/github-actions/src/components/WorkflowRunDetails/useWorkflowRunsDetails.ts @@ -37,7 +37,7 @@ export const useWorkflowRunsDetails = ({ repo, id: parseInt(id, 10), }) - : Promise.reject('No repo/owner provided'); + : Promise.reject(new Error('No repo/owner provided')); }, [repo, owner, id]); return details; }; diff --git a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx index 0f1670bbbc..bf66399288 100644 --- a/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx +++ b/plugins/github-actions/src/components/WorkflowRunLogs/WorkflowRunLogs.tsx @@ -33,7 +33,7 @@ import { import DescriptionIcon from '@material-ui/icons/Description'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import React from 'react'; -import { useProjectName } from '../useProjectName'; +import { getProjectNameFromEntity } from '../getProjectNameFromEntity'; import { useDownloadWorkflowRunLogs } from './useDownloadWorkflowRunLogs'; const useStyles = makeStyles(theme => ({ @@ -77,13 +77,13 @@ export const WorkflowRunLogs = ({ }) => { const config = useApi(configApiRef); const classes = useStyles(); - const projectName = useProjectName(entity); + const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation const hostname = readGitHubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], )[0].host; - const [owner, repo] = projectName.value ? projectName.value.split('/') : []; + const [owner, repo] = (projectName && projectName.split('/')) || []; const jobLogs = useDownloadWorkflowRunLogs({ hostname, owner, diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index 81fa036221..c8c42eb1bc 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -29,7 +29,7 @@ import { useWorkflowRuns, WorkflowRun } from '../useWorkflowRuns'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import SyncIcon from '@material-ui/icons/Sync'; import { buildRouteRef } from '../../routes'; -import { useProjectName } from '../useProjectName'; +import { getProjectNameFromEntity } from '../getProjectNameFromEntity'; import { Entity } from '@backstage/catalog-model'; import { readGitHubIntegrationConfigs } from '@backstage/integration'; @@ -157,7 +157,7 @@ export const WorkflowRunsTable = ({ branch?: string; }) => { const config = useApi(configApiRef); - const { value: projectName, loading } = useProjectName(entity); + const projectName = getProjectNameFromEntity(entity); // TODO: Get github hostname from metadata annotation const hostname = readGitHubIntegrationConfigs( config.getOptionalConfigArray('integrations.github') ?? [], @@ -172,7 +172,7 @@ export const WorkflowRunsTable = ({ }); const githubHost = hostname || 'github.com'; - const hasNoRuns = !loading && !tableProps.loading && !runs; + const hasNoRuns = !tableProps.loading && !runs; return hasNoRuns ? ( { - const { value, loading, error } = useAsync(async () => { - return entity?.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION] ?? ''; - }); - return { value, loading, error }; -}; +export const getProjectNameFromEntity = (entity: Entity) => + entity?.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION] ?? ''; diff --git a/plugins/github-actions/src/index.ts b/plugins/github-actions/src/index.ts index f520b9fbbc..ab041b4970 100644 --- a/plugins/github-actions/src/index.ts +++ b/plugins/github-actions/src/index.ts @@ -35,4 +35,4 @@ export { isGithubActionsAvailable as isPluginApplicableToEntity, } from './components/Router'; export * from './components/Cards'; -export { GITHUB_ACTIONS_ANNOTATION } from './components/useProjectName'; +export { GITHUB_ACTIONS_ANNOTATION } from './components/getProjectNameFromEntity'; diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 8bcaa8287f..e2d398c7f8 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-github-deployments +## 0.1.31 + +### Patch Changes + +- 209fd128e6: The removed `'github'` catalog location type no longer triggers a GitHub integration configuration lookup. +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration@0.7.5 + - @backstage/integration-react@0.1.23 + +## 0.1.30 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 216725b434: Updated to use new names for `parseLocationRef` and `stringifyLocationRef` +- 27eccab216: Replaces use of deprecated catalog-model constants. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/integration-react@0.1.22 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.1.29 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index e4c3e4917c..20198ee40a 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-deployments", "description": "A Backstage plugin that integrates towards GitHub Deployments", - "version": "0.1.29", + "version": "0.1.31", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,25 +10,28 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/integration-react": "^0.1.21", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/integration-react": "^0.1.23", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -40,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/github-deployments/src/api/index.ts b/plugins/github-deployments/src/api/index.ts index e0eef7cc1d..48363557ff 100644 --- a/plugins/github-deployments/src/api/index.ts +++ b/plugins/github-deployments/src/api/index.ts @@ -28,7 +28,7 @@ const getBaseUrl = ( } const location = parseLocationRef(host); - if (location.type !== 'github' && location.type !== 'url') { + if (location.type !== 'url') { return 'https://api.github.com'; } diff --git a/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx b/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx index dba5bbfe1c..dedd2c3082 100644 --- a/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx +++ b/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx @@ -254,7 +254,7 @@ describe('github-deployments', () => { entity.entity.metadata.annotations = { 'github.com/project-slug': 'org/repo', 'backstage.io/source-location': - 'github:https://my-github-1.com/org/repo/tree/master/', + 'url:https://my-github-1.com/org/repo/tree/master/', }; }); @@ -276,7 +276,7 @@ describe('github-deployments', () => { entity.entity.metadata.annotations = { 'github.com/project-slug': 'org/repo', 'backstage.io/managed-by-location': - 'github:https://my-github-2.com/org/repo/blob/master/catalog-info.yaml', + 'url:https://my-github-2.com/org/repo/blob/master/catalog-info.yaml', }; }); @@ -298,7 +298,7 @@ describe('github-deployments', () => { entity.entity.metadata.annotations = { 'github.com/project-slug': 'org/repo', 'backstage.io/managed-by-location': - 'github:https://my-github-3.com/org/repo/blob/master/catalog-info.yaml', + 'url:https://my-github-3.com/org/repo/blob/master/catalog-info.yaml', }; }); @@ -323,7 +323,7 @@ describe('github-deployments', () => { entity.entity.metadata.annotations = { 'github.com/project-slug': 'org/repo', 'backstage.io/managed-by-location': - 'github:https://my-github-unknown.com/org/repo/blob/master/catalog-info.yaml', + 'url:https://my-github-unknown.com/org/repo/blob/master/catalog-info.yaml', }; }); diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index edf8bec0ae..c2fba95783 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-gitops-profiles +## 0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + +## 0.3.17 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/theme@0.2.15 + ## 0.3.16 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 1cadbc8d65..d96e6a670d 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gitops-profiles", "description": "A Backstage plugin that helps you manage GitOps profiles", - "version": "0.3.16", + "version": "0.3.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,19 +25,19 @@ "gitops" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/theme": "^0.2.14", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -45,10 +48,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gocd/CHANGELOG.md b/plugins/gocd/CHANGELOG.md index b2e5024fea..296d7cc928 100644 --- a/plugins/gocd/CHANGELOG.md +++ b/plugins/gocd/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-gocd +## 0.1.6 + +### Patch Changes + +- 29f952c667: Updated README +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.5 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.1.4 ### Patch Changes diff --git a/plugins/gocd/README.md b/plugins/gocd/README.md index 105d93e830..d3d9c3adc8 100644 --- a/plugins/gocd/README.md +++ b/plugins/gocd/README.md @@ -4,6 +4,8 @@ Welcome to the GoCD plugin! - View recent GoCD Builds +![gocd-builds-card](./docs/gocd-plugin-screenshot.png) + ## Installation GoCD Plugin exposes an entity tab component named `EntityGoCdContent`. You can include it in the diff --git a/plugins/gocd/docs/gocd-plugin-screenshot.png b/plugins/gocd/docs/gocd-plugin-screenshot.png new file mode 100644 index 0000000000..8ddc786fa5 Binary files /dev/null and b/plugins/gocd/docs/gocd-plugin-screenshot.png differ diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index 86802a43f2..afd6c63a91 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gocd", "description": "A Backstage plugin that integrates towards GoCD", - "version": "0.1.4", + "version": "0.1.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -18,22 +21,22 @@ "directory": "plugins/gocd" }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -46,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index da7919fb5e..522c67d32c 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-graphiql +## 0.2.32 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.31 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/theme@0.2.15 + ## 0.2.30 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 185e273c3f..517d9c097d 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,13 +1,16 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.30", + "version": "0.2.32", "private": false, "publishConfig": { "access": "public", "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://github.com/backstage/backstage/tree/master/plugins/graphiql#readme", "repository": { "type": "git", @@ -21,19 +24,19 @@ "main": "src/index.ts", "types": "src/index.ts", "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/theme": "^0.2.14", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -45,10 +48,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/graphql-backend/CHANGELOG.md b/plugins/graphql-backend/CHANGELOG.md index 749ced2527..5fa09c1432 100644 --- a/plugins/graphql-backend/CHANGELOG.md +++ b/plugins/graphql-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-graphql-backend +## 0.1.17 + +### Patch Changes + +- 68913af4ff: chore(deps): bump `graphql-modules` from 1.4.4 to 2.0.0 +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-catalog-graphql@0.3.4 + +## 0.1.16 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/plugin-catalog-graphql@0.3.3 + +## 0.1.15 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 0107c9aa08: chore(deps): bump `helmet` from 4.4.1 to 5.0.2 +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/plugin-catalog-graphql@0.3.2 + - @backstage/config@0.1.14 + ## 0.1.14 ### Patch Changes diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json index a13a901646..d1d0f5a153 100644 --- a/plugins/graphql-backend/package.json +++ b/plugins/graphql-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphql-backend", "description": "An experimental Backstage backend plugin for GraphQL", - "version": "0.1.14", + "version": "0.1.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,33 +25,33 @@ "graphql" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", - "@backstage/plugin-catalog-graphql": "^0.3.1", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/plugin-catalog-graphql": "^0.3.4", "@graphql-tools/schema": "^8.3.1", - "graphql-modules": "^1.0.0", + "graphql-modules": "^2.0.0", "@types/express": "^4.17.6", "apollo-server": "^3.0.0", "apollo-server-express": "^3.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", "graphql": "^16.0.0", - "helmet": "^4.0.0", + "helmet": "^5.0.2", "reflect-metadata": "^0.1.13", "winston": "^3.2.1", "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", "msw": "^0.35.0", diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index d0c0a5bb44..93fe89340d 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-home +## 0.4.16 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/plugin-search@0.7.1 + +## 0.4.15 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 651b919dbb: Add Renderer support for the HomePageToolkit component. + + Previously `` would + result in the error `can't access property "map", props.tools is undefined`. + This change adds a context that can pass props down to the HomePageToolkit. + Also introduced is an `expanded` prop on the `ComponentAccordion` to setting + the default expanded state. See `In Accordian` story for details. + +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/plugin-search@0.7.0 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.4.14 ### Patch Changes diff --git a/plugins/home/api-report.md b/plugins/home/api-report.md index 264189ee07..6fae50151c 100644 --- a/plugins/home/api-report.md +++ b/plugins/home/api-report.md @@ -24,6 +24,7 @@ export type ClockConfig = { // @public (undocumented) export const ComponentAccordion: ({ title, + expanded, Content, Actions, Settings, @@ -31,6 +32,7 @@ export const ComponentAccordion: ({ ...childProps }: { title: string; + expanded?: boolean | undefined; Content: () => JSX.Element; Actions?: (() => JSX.Element) | undefined; Settings?: (() => JSX.Element) | undefined; diff --git a/plugins/home/package.json b/plugins/home/package.json index 80754d33c8..3f265e136c 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home", "description": "A Backstage plugin that helps you build a home page", - "version": "0.4.14", + "version": "0.4.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://github.com/backstage/backstage/tree/master/plugins/home#readme", "repository": { "type": "git", @@ -21,22 +24,22 @@ "homepage" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/plugin-search": "^0.6.2", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/plugin-search": "^0.7.1", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +52,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/home/src/componentRenderers/ComponentAccordion.tsx b/plugins/home/src/componentRenderers/ComponentAccordion.tsx index 5cf54d7a0c..ef44f7f292 100644 --- a/plugins/home/src/componentRenderers/ComponentAccordion.tsx +++ b/plugins/home/src/componentRenderers/ComponentAccordion.tsx @@ -33,10 +33,14 @@ const useStyles = makeStyles((theme: Theme) => ({ settingsIconButton: { padding: theme.spacing(0, 1, 0, 0), }, + contentContainer: { + width: '100%', + }, })); export const ComponentAccordion = ({ title, + expanded = false, Content, Actions, Settings, @@ -44,6 +48,7 @@ export const ComponentAccordion = ({ ...childProps }: { title: string; + expanded?: boolean; Content: () => JSX.Element; Actions?: () => JSX.Element; Settings?: () => JSX.Element; @@ -51,7 +56,7 @@ export const ComponentAccordion = ({ }) => { const classes = useStyles(); const [settingsIsExpanded, setSettingsIsExpanded] = React.useState(false); - const [isExpanded, setIsExpanded] = React.useState(false); + const [isExpanded, setIsExpanded] = React.useState(expanded); const handleOpenSettings = (e: any) => { e.stopPropagation(); @@ -71,7 +76,9 @@ export const ComponentAccordion = ({ )} setIsExpanded(expanded)} + onChange={(_e: any, expandedValue: boolean) => + setIsExpanded(expandedValue) + } > }> {Settings && ( @@ -85,7 +92,7 @@ export const ComponentAccordion = ({ {title} -
+
{Actions && }
diff --git a/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx b/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx index 7fbaff08f9..1955019a4a 100644 --- a/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx +++ b/plugins/home/src/homePageComponents/Toolkit/Content.test.tsx @@ -13,19 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { renderInTestApp } from '@backstage/test-utils'; import React from 'react'; import { Content } from './Content'; +import { ContextProvider } from './Context'; describe('', () => { + const tools = [ + { label: 'tool', url: '/url', icon:
icon
}, + { label: 'tool 2', url: '/url-2', icon:
icon 2
}, + ]; + test('should render list of tools', async () => { + const { getByText } = await renderInTestApp(); + + expect(getByText('tool')).toBeInTheDocument(); + expect(getByText('tool 2')).toBeInTheDocument(); + expect(getByText('tool').closest('a')).toHaveAttribute('href', '/url'); + expect(getByText('tool 2').closest('a')).toHaveAttribute('href', '/url-2'); + }); + + test('should render list of tools using context', async () => { const { getByText } = await renderInTestApp( - icon
}, - { label: 'tool 2', url: '/url-2', icon:
icon 2
}, - ]} - />, + + + , ); expect(getByText('tool')).toBeInTheDocument(); diff --git a/plugins/home/src/homePageComponents/Toolkit/Content.tsx b/plugins/home/src/homePageComponents/Toolkit/Content.tsx index 4ee1884bd0..21affc57f1 100644 --- a/plugins/home/src/homePageComponents/Toolkit/Content.tsx +++ b/plugins/home/src/homePageComponents/Toolkit/Content.tsx @@ -22,6 +22,7 @@ import { ListItemText, } from '@material-ui/core'; import React from 'react'; +import { useToolkit, Tool } from './Context'; const useStyles = makeStyles(theme => ({ toolkit: { @@ -49,12 +50,6 @@ const useStyles = makeStyles(theme => ({ }, })); -type Tool = { - label: string; - url: string; - icon: React.ReactNode; -}; - /** * Props for Toolkit content component {@link Content}. * @@ -71,10 +66,12 @@ export type ToolkitContentProps = { */ export const Content = (props: ToolkitContentProps) => { const classes = useStyles(); + const toolkit = useToolkit(); + const tools = toolkit?.tools ?? props.tools; return ( - {props.tools.map((tool: Tool) => ( + {tools.map((tool: Tool) => ( {tool.icon} (undefined); + +export const ContextProvider = ({ + children, + tools, +}: { + children: JSX.Element; + tools: Tool[]; +}) => { + const [toolsValue, _setTools] = React.useState(tools); + + const value: ToolkitContextValue = { + tools: toolsValue, + }; + + return {children}; +}; + +export const useToolkit = () => { + const value = React.useContext(Context); + return value; +}; + +export default Context; diff --git a/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx index 0a8e6fffbe..cad188c5d6 100644 --- a/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx +++ b/plugins/home/src/homePageComponents/Toolkit/Toolkit.stories.tsx @@ -14,11 +14,13 @@ * limitations under the License. */ -import { TemplateBackstageLogoIcon } from '../../templates'; -import { HomePageToolkit } from '../../plugin'; +import { InfoCard } from '@backstage/core-components'; import { wrapInTestApp } from '@backstage/test-utils'; import { Grid } from '@material-ui/core'; import React, { ComponentType } from 'react'; +import { ComponentAccordion } from '../../componentRenderers'; +import { HomePageToolkit } from '../../plugin'; +import { TemplateBackstageLogoIcon } from '../../templates'; export default { title: 'Plugins/Home/Components/Toolkit', @@ -38,3 +40,43 @@ export const Default = () => { ); }; + +export const InAccordian = () => { + const ExpandedComponentAccordion = (props: any) => ( + + ); + + return ( + + + , + })} + Renderer={ExpandedComponentAccordion} + /> + , + })} + Renderer={ComponentAccordion} + /> + , + })} + Renderer={ComponentAccordion} + /> + + + ); +}; diff --git a/plugins/home/src/homePageComponents/Toolkit/index.ts b/plugins/home/src/homePageComponents/Toolkit/index.ts index 98e0869825..67ea1f3c84 100644 --- a/plugins/home/src/homePageComponents/Toolkit/index.ts +++ b/plugins/home/src/homePageComponents/Toolkit/index.ts @@ -16,3 +16,4 @@ export { Content } from './Content'; export type { ToolkitContentProps } from './Content'; +export { ContextProvider } from './Context'; diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index 099110e39f..7e3296691f 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-ilert +## 0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.25 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.1.24 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 4c6581553c..d2250287b0 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-ilert", "description": "A Backstage plugin that integrates towards iLert", - "version": "0.1.24", + "version": "0.1.26", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,23 +10,26 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -40,10 +43,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md index 17fd3da85a..fe53f21004 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,38 @@ # @backstage/plugin-jenkins-backend +## 0.1.16 + +### Patch Changes + +- 67a7c02d26: Remove usages of `EntityRef` and `parseEntityName` from `@backstage/catalog-model` +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + +## 0.1.15 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + +## 0.1.14 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 377fce4677: The `DefaultJenkinsInfoProvider.fromConfig` now requires an implementation of the `CatalogApi` rather than a `CatalogClient` instance. +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + ## 0.1.13 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 52e8862fae..66cbe5ba24 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins-backend", "description": "A Backstage backend plugin that integrates towards Jenkins", - "version": "0.1.13", + "version": "0.1.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,21 +11,24 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "configSchema": "config.d.ts", "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -34,7 +37,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/jenkins": "^0.23.1", "@types/supertest": "^2.0.8", "msw": "^0.35.0", diff --git a/plugins/jenkins-backend/src/service/standaloneServer.ts b/plugins/jenkins-backend/src/service/standaloneServer.ts index b99bdcfd5a..7f47d57ac3 100644 --- a/plugins/jenkins-backend/src/service/standaloneServer.ts +++ b/plugins/jenkins-backend/src/service/standaloneServer.ts @@ -18,7 +18,7 @@ import { createServiceBuilder } from '@backstage/backend-common'; import { Server } from 'http'; import { Logger } from 'winston'; import { createRouter } from './router'; -import { EntityRef } from '@backstage/catalog-model'; +import { EntityName } from '@backstage/catalog-model'; import { JenkinsInfo } from './jenkinsInfoProvider'; export interface ServerOptions { @@ -35,7 +35,7 @@ export async function startStandaloneServer( const router = await createRouter({ logger, jenkinsInfoProvider: { - async getInstance(_: { entityRef: EntityRef }): Promise { + async getInstance(_: { entityRef: EntityName }): Promise { return { baseUrl: 'https://example.com/', jobFullName: 'build-foo' }; }, }, diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index 8611c9d2b7..33d1fa2508 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,34 @@ # @backstage/plugin-jenkins +## 0.6.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- 67a7c02d26: Remove usages of `EntityRef` and `parseEntityName` from `@backstage/catalog-model` +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.5.21 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.5.20 ### Patch Changes diff --git a/plugins/jenkins/api-report.md b/plugins/jenkins/api-report.md index 61c5327fa1..5db5fd0dfe 100644 --- a/plugins/jenkins/api-report.md +++ b/plugins/jenkins/api-report.md @@ -10,7 +10,6 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import type { EntityName } from '@backstage/catalog-model'; -import type { EntityRef } from '@backstage/catalog-model'; import { IdentityApi } from '@backstage/core-plugin-api'; import { InfoCardVariants } from '@backstage/core-components'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -18,9 +17,7 @@ import { RouteRef } from '@backstage/core-plugin-api'; // Warning: (ae-missing-release-tag) "EntityJenkinsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityJenkinsContent: (_props: { - entity?: Entity | undefined; -}) => JSX.Element; +export const EntityJenkinsContent: (_props: {}) => JSX.Element; // Warning: (ae-missing-release-tag) "EntityLatestJenkinsRunCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -57,7 +54,7 @@ export interface JenkinsApi { }): Promise; // Warning: (ae-forgotten-export) The symbol "Project" needs to be exported by the entry point index.d.ts getProjects(options: { - entity: EntityRef; + entity: EntityName; filter: { branch?: string; }; @@ -84,31 +81,20 @@ export class JenkinsClient implements JenkinsApi { identityApi: IdentityApi; }); // (undocumented) - getBuild({ - entity, - jobFullName, - buildNumber, - }: { + getBuild(options: { entity: EntityName; jobFullName: string; buildNumber: string; }): Promise; // (undocumented) - getProjects({ - entity, - filter, - }: { + getProjects(options: { entity: EntityName; filter: { branch?: string; }; }): Promise; // (undocumented) - retry({ - entity, - jobFullName, - buildNumber, - }: { + retry(options: { entity: EntityName; jobFullName: string; buildNumber: string; diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 7b1283cd23..ada061dba9 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins", "description": "A Backstage plugin that integrates towards Jenkins", - "version": "0.5.20", + "version": "0.6.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,22 +25,22 @@ "jenkins" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -50,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins/src/api/JenkinsApi.ts b/plugins/jenkins/src/api/JenkinsApi.ts index 65ac79b62b..dd169de78b 100644 --- a/plugins/jenkins/src/api/JenkinsApi.ts +++ b/plugins/jenkins/src/api/JenkinsApi.ts @@ -19,7 +19,7 @@ import { DiscoveryApi, IdentityApi, } from '@backstage/core-plugin-api'; -import type { EntityName, EntityRef } from '@backstage/catalog-model'; +import type { EntityName } from '@backstage/catalog-model'; import { ResponseError } from '@backstage/errors'; export const jenkinsApiRef = createApiRef({ @@ -80,7 +80,7 @@ export interface JenkinsApi { */ getProjects(options: { /** the entity whose jobs should be retrieved. */ - entity: EntityRef; + entity: EntityName; /** a filter on jobs. Currently this just takes a branch (and assumes certain structures in jenkins) */ filter: { branch?: string }; }): Promise; @@ -117,13 +117,11 @@ export class JenkinsClient implements JenkinsApi { this.identityApi = options.identityApi; } - async getProjects({ - entity, - filter, - }: { + async getProjects(options: { entity: EntityName; filter: { branch?: string }; }): Promise { + const { entity, filter } = options; const url = new URL( `${await this.discoveryApi.getBaseUrl( 'jenkins', @@ -158,15 +156,12 @@ export class JenkinsClient implements JenkinsApi { ); } - async getBuild({ - entity, - jobFullName, - buildNumber, - }: { + async getBuild(options: { entity: EntityName; jobFullName: string; buildNumber: string; }): Promise { + const { entity, jobFullName, buildNumber } = options; const url = `${await this.discoveryApi.getBaseUrl( 'jenkins', )}/v1/entity/${encodeURIComponent(entity.namespace)}/${encodeURIComponent( @@ -186,15 +181,12 @@ export class JenkinsClient implements JenkinsApi { return (await response.json()).build; } - async retry({ - entity, - jobFullName, - buildNumber, - }: { + async retry(options: { entity: EntityName; jobFullName: string; buildNumber: string; }): Promise { + const { entity, jobFullName, buildNumber } = options; const url = `${await this.discoveryApi.getBaseUrl( 'jenkins', )}/v1/entity/${encodeURIComponent(entity.namespace)}/${encodeURIComponent( diff --git a/plugins/jenkins/src/components/Router.tsx b/plugins/jenkins/src/components/Router.tsx index 0d38fe009a..b3561be2b0 100644 --- a/plugins/jenkins/src/components/Router.tsx +++ b/plugins/jenkins/src/components/Router.tsx @@ -27,10 +27,7 @@ export const isJenkinsAvailable = (entity: Entity) => Boolean(entity.metadata.annotations?.[JENKINS_ANNOTATION]) || Boolean(entity.metadata.annotations?.[LEGACY_JENKINS_ANNOTATION]); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; +type Props = {}; export const Router = (_props: Props) => { const { entity } = useEntity(); diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index cd80463672..766f6e9dfb 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-kafka-backend +## 0.2.20 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + +## 0.2.19 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + +## 0.2.18 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + ## 0.2.17 ### Patch Changes diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 01b23ba696..f10020b6d6 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka-backend", "description": "A Backstage backend plugin that integrates towards Kafka", - "version": "0.2.17", + "version": "0.2.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -23,19 +26,19 @@ ], "configSchema": "config.d.ts", "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -44,7 +47,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/jest-when": "^2.7.2", "@types/lodash": "^4.14.151", "jest-when": "^3.1.0", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index e73c3a6efb..578e05fe33 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-kafka +## 0.3.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.29 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.2.28 ### Patch Changes diff --git a/plugins/kafka/api-report.md b/plugins/kafka/api-report.md index 0750978b27..95eed1dc13 100644 --- a/plugins/kafka/api-report.md +++ b/plugins/kafka/api-report.md @@ -12,9 +12,7 @@ import { RouteRef } from '@backstage/core-plugin-api'; // Warning: (ae-missing-release-tag) "EntityKafkaContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityKafkaContent: (_props: { - entity?: Entity | undefined; -}) => JSX.Element; +export const EntityKafkaContent: (_props: {}) => JSX.Element; // Warning: (ae-missing-release-tag) "isPluginApplicableToEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 12dd578daa..ea979e37f9 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka", "description": "A Backstage plugin that integrates towards Kafka", - "version": "0.2.28", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,22 +10,25 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -36,10 +39,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/kafka/src/Router.tsx b/plugins/kafka/src/Router.tsx index c63019253b..133326b5b9 100644 --- a/plugins/kafka/src/Router.tsx +++ b/plugins/kafka/src/Router.tsx @@ -25,10 +25,7 @@ import { MissingAnnotationEmptyState } from '@backstage/core-components'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[KAFKA_CONSUMER_GROUP_ANNOTATION]); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; +type Props = {}; export const Router = (_props: Props) => { const { entity } = useEntity(); diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 457e17e989..d6473619cb 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-kubernetes-backend +## 0.4.10 + +### Patch Changes + +- 64acf65c03: Allow missing kubernetes config in development env +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/plugin-kubernetes-common@0.2.5 + +## 0.4.9 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/plugin-kubernetes-common@0.2.4 + +## 0.4.8 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 0107c9aa08: chore(deps): bump `helmet` from 4.4.1 to 5.0.2 +- fb09a59a3f: Fixed a potential issue in AWS token encoding, where they might not always be properly converted to URL-safe base64. +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/plugin-kubernetes-common@0.2.3 + ## 0.4.7 ### Patch Changes diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 9b181c8514..ea273d7632 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -83,15 +83,7 @@ export interface GKEClusterDetails extends ClusterDetails {} export class KubernetesBuilder { constructor(env: KubernetesEnvironment); // (undocumented) - build(): Promise<{ - clusterDetails: ClusterDetails[]; - clusterSupplier: KubernetesClustersSupplier; - customResources: CustomResource[]; - fetcher: KubernetesFetcher; - objectsProvider: KubernetesObjectsProvider; - router: express.Router; - serviceLocator: KubernetesServiceLocator; - }>; + build(): KubernetesBuilderReturn; // (undocumented) protected buildClusterSupplier(): KubernetesClustersSupplier; // (undocumented) @@ -142,6 +134,17 @@ export class KubernetesBuilder { setServiceLocator(serviceLocator?: KubernetesServiceLocator): this; } +// @public +export type KubernetesBuilderReturn = Promise<{ + router: express.Router; + clusterDetails: ClusterDetails[]; + clusterSupplier: KubernetesClustersSupplier; + customResources: CustomResource[]; + fetcher: KubernetesFetcher; + objectsProvider: KubernetesObjectsProvider; + serviceLocator: KubernetesServiceLocator; +}>; + // Warning: (ae-missing-release-tag) "KubernetesClustersSupplier" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index feb6a4defd..fa387a9191 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.4.7", + "version": "0.4.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -23,20 +26,20 @@ ], "configSchema": "schema.d.ts", "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-kubernetes-common": "^0.2.2", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-kubernetes-common": "^0.2.5", "@google-cloud/container": "^2.2.0", "@kubernetes/client-node": "^0.16.0", "@types/express": "^4.17.6", @@ -47,7 +50,7 @@ "express": "^4.17.1", "express-promise-router": "^4.1.0", "fs-extra": "9.1.0", - "helmet": "^4.0.0", + "helmet": "^5.0.2", "lodash": "^4.17.21", "morgan": "^1.10.0", "stream-buffers": "^3.0.2", @@ -55,7 +58,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/aws4": "^1.5.1", "supertest": "^6.1.3", "aws-sdk-mock": "^5.2.1", diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts index 4af39d4f26..9f1353ca5c 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AwsIamKubernetesAuthTranslator.ts @@ -18,19 +18,6 @@ import { sign } from 'aws4'; import { AWSClusterDetails } from '../types/types'; import { KubernetesAuthTranslator } from './types'; -const base64 = (str: string) => - Buffer.from(str.toString(), 'binary').toString('base64'); -const prepend = (prep: string) => (str: string) => prep + str; -const replace = - (search: string | RegExp, substitution: string) => (str: string) => - str.replace(search, substitution); -const pipe = - (fns: ReadonlyArray) => - (thing: string): string => - fns.reduce((val, fn) => fn(val), thing); -const removePadding = replace(/=+$/, ''); -const makeUrlSafe = pipe([replace('+', '-'), replace('/', '_')]); - type SigningCreds = { accessKeyId: string | undefined; secretAccessKey: string | undefined; @@ -117,15 +104,15 @@ export class AwsIamKubernetesAuthTranslator signQuery: true, }; - const signedRequest = sign(request, credentials); + const signed = sign(request, credentials); + const url = `https://${signed.host}${signed.path}`; + const base64Url = Buffer.from(url, 'binary').toString('base64'); + const urlSafeBase64Url = base64Url + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/=+$/, ''); - return pipe([ - (signed: any) => `https://${signed.host}${signed.path}`, - base64, - removePadding, - makeUrlSafe, - prepend('k8s-aws-v1.'), - ])(signedRequest); + return `k8s-aws-v1.${urlSafeBase64Url}`; } async decorateClusterDetailsWithAuth( diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts index 3333f7a0fc..a1eb60a239 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.ts @@ -43,6 +43,21 @@ export interface KubernetesEnvironment { config: Config; } +/** + * The return type of the `KubernetesBuilder.build` method + * + * @public + */ +export type KubernetesBuilderReturn = Promise<{ + router: express.Router; + clusterDetails: ClusterDetails[]; + clusterSupplier: KubernetesClustersSupplier; + customResources: CustomResource[]; + fetcher: KubernetesFetcher; + objectsProvider: KubernetesObjectsProvider; + serviceLocator: KubernetesServiceLocator; +}>; + export class KubernetesBuilder { private clusterSupplier?: KubernetesClustersSupplier; private objectsProvider?: KubernetesObjectsProvider; @@ -55,11 +70,23 @@ export class KubernetesBuilder { constructor(protected readonly env: KubernetesEnvironment) {} - public async build() { + public async build(): KubernetesBuilderReturn { const logger = this.env.logger; + const config = this.env.config; logger.info('Initializing Kubernetes backend'); + if (!config.has('kubernetes')) { + if (process.env.NODE_ENV !== 'development') { + throw new Error('Kubernetes configuration is missing'); + } + logger.warn( + 'Failed to initialize kubernetes backend: kubernetes config is missing', + ); + return { + router: Router(), + } as unknown as KubernetesBuilderReturn; + } const customResources = this.buildCustomResources(); const fetcher = this.fetcher ?? this.buildFetcher(); diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 785a5c619d..4016be8e1b 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-kubernetes-common +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@0.11.0 + +## 0.2.4 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/catalog-model@0.10.1 + +## 0.2.3 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/catalog-model@0.10.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index cd12eb3519..229cf01d40 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-common", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", - "version": "0.2.2", + "version": "0.2.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -24,22 +27,22 @@ "dist" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test --passWithNoTests", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "bugs": { "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", + "@backstage/catalog-model": "^0.11.0", "@kubernetes/client-node": "^0.16.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0" + "@backstage/cli": "^0.14.1" }, "jest": { "roots": [ diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 33abbb9255..c729333b2a 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,35 @@ # @backstage/plugin-kubernetes +## 0.6.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/plugin-kubernetes-common@0.2.5 + +## 0.5.8 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + - @backstage/plugin-kubernetes-common@0.2.3 + ## 0.5.7 ### Patch Changes diff --git a/plugins/kubernetes/api-report.md b/plugins/kubernetes/api-report.md index 8dc0114738..f635a61cef 100644 --- a/plugins/kubernetes/api-report.md +++ b/plugins/kubernetes/api-report.md @@ -25,9 +25,7 @@ export const clusterLinksFormatters: Record; // Warning: (ae-missing-release-tag) "EntityKubernetesContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityKubernetesContent: (_props: { - entity?: Entity | undefined; -}) => JSX.Element; +export const EntityKubernetesContent: (_props: {}) => JSX.Element; // Warning: (ae-forgotten-export) The symbol "FormatClusterLinkOptions" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "formatClusterLink" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 1a60997405..6b3c90e396 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.5.7", + "version": "0.6.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,24 +24,24 @@ "kubernetes" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/plugin-kubernetes-common": "^0.2.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/plugin-kubernetes-common": "^0.2.5", "@kubernetes/client-node": "^0.16.0", - "@backstage/theme": "^0.2.14", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -53,10 +56,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/kubernetes/src/Router.tsx b/plugins/kubernetes/src/Router.tsx index 873bf128e4..deb6475ad5 100644 --- a/plugins/kubernetes/src/Router.tsx +++ b/plugins/kubernetes/src/Router.tsx @@ -32,10 +32,7 @@ export const isKubernetesAvailable = (entity: Entity) => entity.metadata.annotations?.[KUBERNETES_LABEL_SELECTOR_QUERY_ANNOTATION], ); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; +type Props = {}; export const Router = (_props: Props) => { const { entity } = useEntity(); diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 541861b519..18dc47bb44 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-lighthouse +## 0.3.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.38 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + ## 0.2.37 ### Patch Changes diff --git a/plugins/lighthouse/api-report.md b/plugins/lighthouse/api-report.md index 724f8898a6..26db053ef0 100644 --- a/plugins/lighthouse/api-report.md +++ b/plugins/lighthouse/api-report.md @@ -70,9 +70,7 @@ export const EntityLastLighthouseAuditCard: ({ // Warning: (ae-missing-release-tag) "EntityLighthouseContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityLighthouseContent: (_props: { - entity?: Entity | undefined; -}) => JSX.Element; +export const EntityLighthouseContent: (_props: {}) => JSX.Element; // Warning: (ae-missing-release-tag) "FetchError" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 618202b54c..219f6bf8d9 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-lighthouse", "description": "A Backstage plugin that integrates towards Lighthouse", - "version": "0.2.37", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,22 +25,22 @@ "lighthouse" ], "scripts": { - "build": "backstage-cli plugin:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "clean": "backstage-cli clean", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "start": "backstage-cli plugin:serve" + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -48,10 +51,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/lighthouse/src/Router.tsx b/plugins/lighthouse/src/Router.tsx index 917001c591..1c41f522f4 100644 --- a/plugins/lighthouse/src/Router.tsx +++ b/plugins/lighthouse/src/Router.tsx @@ -36,10 +36,7 @@ export const Router = () => ( ); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; +type Props = {}; export const EmbeddedRouter = (_props: Props) => { const { entity } = useEntity(); diff --git a/plugins/newrelic-dashboard/CHANGELOG.md b/plugins/newrelic-dashboard/CHANGELOG.md index e1101d2369..98f751fb92 100644 --- a/plugins/newrelic-dashboard/CHANGELOG.md +++ b/plugins/newrelic-dashboard/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-newrelic-dashboard +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.7 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + ## 0.1.6 ### Patch Changes diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index dba9cc1148..d75a96a30e 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic-dashboard", - "version": "0.1.6", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -9,30 +9,33 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/dev-utils": "^0.2.21", + "@backstage/cli": "^0.14.1", + "@backstage/dev-utils": "^0.2.23", "@testing-library/jest-dom": "^5.10.1", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5" diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index 821de4582a..f25d105a3c 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-newrelic +## 0.3.18 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + +## 0.3.17 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/theme@0.2.15 + ## 0.3.16 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 9f9d51eae3..1f7b1a8fae 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-newrelic", "description": "A Backstage plugin that integrates towards New Relic", - "version": "0.3.16", + "version": "0.3.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,19 +25,19 @@ "newrelic" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/theme": "^0.2.14", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -44,10 +47,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index e9dfbce07d..c9cc96bbe6 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-org +## 0.5.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- cd8c6970ed: Add entity sync button to Group page +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.4.3 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 538ca90790: Use updated type names from `@backstage/catalog-client` +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.4.2 ### Patch Changes diff --git a/plugins/org/api-report.md b/plugins/org/api-report.md index b181a3aab4..723cbd43b7 100644 --- a/plugins/org/api-report.md +++ b/plugins/org/api-report.md @@ -6,11 +6,8 @@ /// import { BackstagePlugin } from '@backstage/core-plugin-api'; -import { Entity } from '@backstage/catalog-model'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; -import { GroupEntity } from '@backstage/catalog-model'; import { InfoCardVariants } from '@backstage/core-components'; -import { UserEntity } from '@backstage/catalog-model'; // Warning: (ae-missing-release-tag) "EntityGroupProfileCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -18,7 +15,6 @@ import { UserEntity } from '@backstage/catalog-model'; export const EntityGroupProfileCard: ({ variant, }: { - entity?: GroupEntity | undefined; variant?: InfoCardVariants | undefined; }) => JSX.Element; @@ -26,7 +22,6 @@ export const EntityGroupProfileCard: ({ // // @public (undocumented) export const EntityMembersListCard: (_props: { - entity?: GroupEntity | undefined; memberDisplayTitle?: string | undefined; pageSize?: number | undefined; }) => JSX.Element; @@ -38,7 +33,6 @@ export const EntityOwnershipCard: ({ variant, entityFilterKind, }: { - entity?: Entity | undefined; variant?: InfoCardVariants | undefined; entityFilterKind?: string[] | undefined; }) => JSX.Element; @@ -49,7 +43,6 @@ export const EntityOwnershipCard: ({ export const EntityUserProfileCard: ({ variant, }: { - entity?: UserEntity | undefined; variant?: InfoCardVariants | undefined; }) => JSX.Element; @@ -59,7 +52,6 @@ export const EntityUserProfileCard: ({ export const GroupProfileCard: ({ variant, }: { - entity?: GroupEntity | undefined; variant?: InfoCardVariants | undefined; }) => JSX.Element; @@ -67,7 +59,6 @@ export const GroupProfileCard: ({ // // @public (undocumented) export const MembersListCard: (_props: { - entity?: GroupEntity; memberDisplayTitle?: string; pageSize?: number; }) => JSX.Element; @@ -91,7 +82,6 @@ export const OwnershipCard: ({ variant, entityFilterKind, }: { - entity?: Entity | undefined; variant?: InfoCardVariants | undefined; entityFilterKind?: string[] | undefined; }) => JSX.Element; @@ -102,7 +92,6 @@ export const OwnershipCard: ({ export const UserProfileCard: ({ variant, }: { - entity?: UserEntity | undefined; variant?: InfoCardVariants | undefined; }) => JSX.Element; ``` diff --git a/plugins/org/package.json b/plugins/org/package.json index 947d5e8a0b..93c8a2660a 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-org", "description": "A Backstage plugin that helps you create entity pages for your organization", - "version": "0.4.2", + "version": "0.5.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,22 +10,25 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -39,11 +42,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/catalog-client": "^0.6.0", - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/catalog-client": "^0.7.2", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx index dc90a46fdf..fc45d9a8fe 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.stories.tsx @@ -15,19 +15,20 @@ */ import { GroupEntity } from '@backstage/catalog-model'; -import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { + CatalogApi, + catalogApiRef, + EntityProvider, + entityRouteRef, +} from '@backstage/plugin-catalog-react'; +import { TestApiProvider, wrapInTestApp } from '@backstage/test-utils'; import { Grid } from '@material-ui/core'; -import React from 'react'; -import { MemoryRouter } from 'react-router'; +import React, { ComponentType } from 'react'; import { GroupProfileCard } from './GroupProfileCard'; -export default { - title: 'Plugins/Org/Group Profile Card', - component: GroupProfileCard, -}; - const dummyDepartment = { type: 'childOf', + targetRef: 'group:default/department-a', target: { namespace: 'default', kind: 'group', @@ -55,14 +56,34 @@ const defaultEntity: GroupEntity = { relations: [dummyDepartment], }; +const catalogApi: Partial = { + async refreshEntity() {}, +}; + export const Default = () => ( - - - - - - + + + + - - + +
); + +export default { + title: 'Plugins/Org/Group Profile Card', + component: GroupProfileCard, + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp( + + + , + { + mountedRoutes: { + '/a': entityRouteRef, + }, + }, + ), + ], +}; diff --git a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx index b44fbe6c5c..8291dfdc1d 100644 --- a/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx +++ b/plugins/org/src/components/Cards/Group/GroupProfile/GroupProfileCard.tsx @@ -18,8 +18,11 @@ import { GroupEntity, RELATION_CHILD_OF, RELATION_PARENT_OF, + ANNOTATION_LOCATION, + stringifyEntityRef, } from '@backstage/catalog-model'; import { + catalogApiRef, EntityRefLinks, getEntityRelations, useEntity, @@ -39,14 +42,16 @@ import AccountTreeIcon from '@material-ui/icons/AccountTree'; import EmailIcon from '@material-ui/icons/Email'; import GroupIcon from '@material-ui/icons/Group'; import EditIcon from '@material-ui/icons/Edit'; +import CachedIcon from '@material-ui/icons/Cached'; import Alert from '@material-ui/lab/Alert'; -import React from 'react'; +import React, { useCallback } from 'react'; import { Avatar, InfoCard, InfoCardVariants, Link, } from '@backstage/core-components'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; const CardTitle = ({ title }: { title: string }) => ( @@ -58,17 +63,23 @@ const CardTitle = ({ title }: { title: string }) => ( export const GroupProfileCard = ({ variant, }: { - /** @deprecated The entity is now grabbed from context instead */ - entity?: GroupEntity; variant?: InfoCardVariants; }) => { + const catalogApi = useApi(catalogApiRef); + const alertApi = useApi(alertApiRef); const { entity: group } = useEntity(); + + const refreshEntity = useCallback(async () => { + await catalogApi.refreshEntity(stringifyEntityRef(group)); + alertApi.post({ message: 'Refresh scheduled', severity: 'info' }); + }, [catalogApi, alertApi, group]); + if (!group) { return Group not found; } const { - metadata: { name, description }, + metadata: { name, description, annotations }, spec: { profile }, } = group; @@ -79,6 +90,10 @@ export const GroupProfileCard = ({ kind: 'group', }); + const entityLocation = annotations?.[ANNOTATION_LOCATION]; + const allowRefresh = + entityLocation?.startsWith('url:') || entityLocation?.startsWith('file:'); + const entityMetadataEditUrl = getEntityMetadataEditUrl(group); const displayName = profile?.displayName ?? name; @@ -103,7 +118,20 @@ export const GroupProfileCard = ({ title={} subheader={description} variant={variant} - action={infoCardAction} + action={ + <> + {allowRefresh && ( + + + + )} + {infoCardAction} + + } > diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx index b6b549d934..b0a7ed8c70 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx @@ -54,6 +54,7 @@ const makeUser = ({ relations: [ { type: 'memberOf', + targetRef: 'group:default/team-a', target: { namespace: 'default', kind: 'group', diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx index 99be8571e0..cffe3e0228 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx @@ -59,6 +59,7 @@ describe('MemberTab Test', () => { relations: [ { type: 'memberOf', + targetRef: 'group:default/team-d', target: { kind: 'group', name: 'team-d', diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx index 74a4f6fc90..67bf5c2e42 100644 --- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx +++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.tsx @@ -109,8 +109,6 @@ const MemberComponent = ({ member }: { member: UserEntity }) => { }; export const MembersListCard = (_props: { - /** @deprecated The entity is now grabbed from context instead */ - entity?: GroupEntity; memberDisplayTitle?: string; pageSize?: number; }) => { diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx index a0536e1a74..c07815f39e 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx @@ -69,9 +69,10 @@ const makeComponent = ({ type, name }: { type: string; name: string }) => ({ relations: [ { type: 'ownedBy', + targetRef: 'group:default/team-a', target: { namespace: 'default', - kind: 'Group', + kind: 'group', name: 'team-a', }, }, diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx index 2030f4c100..3fc770e6b5 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx @@ -43,10 +43,11 @@ const items = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/my-team', target: { name: 'my-team', namespace: 'default', - kind: 'Group', + kind: 'group', }, }, ], @@ -62,10 +63,11 @@ const items = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/my-team', target: { name: 'my-team', namespace: 'default', - kind: 'Group', + kind: 'group', }, }, ], @@ -82,10 +84,11 @@ const items = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/my-team', target: { name: 'my-team', namespace: 'default', - kind: 'Group', + kind: 'group', }, }, ], @@ -99,10 +102,11 @@ const items = [ relations: [ { type: 'ownedBy', + targetRef: 'group:default/my-team', target: { name: 'my-team', namespace: 'default', - kind: 'Group', + kind: 'group', }, }, ], @@ -135,9 +139,10 @@ describe('OwnershipCard', () => { relations: [ { type: 'memberOf', + targetRef: 'group:default/examplegroup', target: { kind: 'group', - name: 'ExampleGroup', + name: 'examplegroup', namespace: 'default', }, }, @@ -260,8 +265,9 @@ describe('OwnershipCard', () => { relations: [ { type: 'memberOf', + targetRef: 'group:default/my-team', target: { - kind: 'Group', + kind: 'group', name: 'my-team', namespace: 'default', }, diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index c89534ea7a..2b292cbe8b 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -131,8 +131,6 @@ export const OwnershipCard = ({ variant, entityFilterKind, }: { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; variant?: InfoCardVariants; entityFilterKind?: string[]; }) => { diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx index 0cb3987c77..eb7b8fadda 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.stories.tsx @@ -15,19 +15,18 @@ */ import { UserEntity } from '@backstage/catalog-model'; -import { EntityProvider } from '@backstage/plugin-catalog-react'; +import { + EntityProvider, + entityRouteRef, +} from '@backstage/plugin-catalog-react'; +import { wrapInTestApp } from '@backstage/test-utils'; import { Grid } from '@material-ui/core'; -import React from 'react'; -import { MemoryRouter } from 'react-router'; +import React, { ComponentType } from 'react'; import { UserProfileCard } from './UserProfileCard'; -export default { - title: 'Plugins/Org/User Profile Card', - component: UserProfileCard, -}; - const dummyGroup = { type: 'memberOf', + targetRef: 'group:default/team-a', target: { namespace: 'default', kind: 'group', @@ -55,15 +54,13 @@ const defaultEntity: UserEntity = { }; export const Default = () => ( - - - - - - + + + + - - + + ); const noImageEntity: UserEntity = { @@ -84,13 +81,24 @@ const noImageEntity: UserEntity = { }; export const NoImage = () => ( - - - - - - + + + + - - + + ); + +export default { + title: 'Plugins/Org/User Profile Card', + component: UserProfileCard, + decorators: [ + (Story: ComponentType<{}>) => + wrapInTestApp(, { + mountedRoutes: { + '/a': entityRouteRef, + }, + }), + ], +}; diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx index 7b9904954f..3e5e595eef 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.test.tsx @@ -42,6 +42,7 @@ describe('UserSummary Test', () => { relations: [ { type: 'memberOf', + targetRef: 'group:default/examplegroup', target: { kind: 'group', name: 'ExampleGroup', @@ -55,7 +56,7 @@ describe('UserSummary Test', () => { const rendered = await renderWithEffects( wrapInTestApp( - + , { mountedRoutes: { @@ -70,9 +71,9 @@ describe('UserSummary Test', () => { 'src', 'https://example.com/staff/calum.jpeg', ); - expect(rendered.getByText('ExampleGroup')).toHaveAttribute( + expect(rendered.getByText('examplegroup')).toHaveAttribute( 'href', - '/catalog/default/group/ExampleGroup', + '/catalog/default/group/examplegroup', ); expect(rendered.getByText('Super awesome human')).toBeInTheDocument(); }); diff --git a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx index 0997e0a4d8..bafaad7ba0 100644 --- a/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx +++ b/plugins/org/src/components/Cards/User/UserProfileCard/UserProfileCard.tsx @@ -51,8 +51,6 @@ const CardTitle = ({ title }: { title?: string }) => export const UserProfileCard = ({ variant, }: { - /** @deprecated The entity is now grabbed from context instead */ - entity?: UserEntity; variant?: InfoCardVariants; }) => { const { entity: user } = useEntity(); diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 6fbcfc54b1..c5a4e87923 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-pagerduty +## 0.3.27 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.3.26 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- edeeac7e9d: Switch `luxon` dependency range to be `^2.0.2` rather than `2.0.2`. +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.3.25 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 7912a32f15..8a7fcee37e 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-pagerduty", "description": "A Backstage plugin that integrates towards PagerDuty", - "version": "0.3.25", + "version": "0.3.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,26 +24,26 @@ "pagerduty" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "classnames": "^2.2.6", - "luxon": "2.0.2", + "luxon": "^2.0.2", "react-router-dom": "6.0.0-beta.0", "react-use": "^17.2.4" }, @@ -49,10 +52,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 58ace0ff17..7ec949ff06 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,62 @@ # @backstage/plugin-permission-backend +## 0.5.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-permission-node@0.5.2 + - @backstage/plugin-auth-node@0.1.3 + +## 0.5.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/plugin-auth-node@0.1.2 + - @backstage/plugin-permission-common@0.5.1 + - @backstage/plugin-permission-node@0.5.1 + +## 0.5.0 + +### Minor Changes + +- e2cf0662eb: Add a warning if the permission backend is used without setting `permission.enabled=true`. + + **BREAKING** Permission backend's `createRouter` now requires a `config` option. + + ```diff + // packages/backend/src/plugins/permission.ts + + ... + export default async function createPlugin({ + ... + + config, + }: PluginEnvironment) { + return createRouter({ + ... + + config, + }); + } + ``` + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/plugin-auth-node@0.1.1 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/config@0.1.14 + - @backstage/plugin-permission-node@0.5.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index e1a4e10c5c..30f8f45dd8 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.4.3", + "version": "0.5.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -9,22 +9,25 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-auth-node": "^0.1.0", - "@backstage/plugin-permission-common": "^0.4.0", - "@backstage/plugin-permission-node": "^0.4.3", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-auth-node": "^0.1.3", + "@backstage/plugin-permission-common": "^0.5.1", + "@backstage/plugin-permission-node": "^0.5.2", "@types/express": "*", "dataloader": "^2.0.0", "express": "^4.17.1", @@ -36,7 +39,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index c0c21288be..b7162ecb06 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-permission-common +## 0.5.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + +## 0.5.0 + +### Minor Changes + +- 8c646beb24: **BREAKING** `PermissionCriteria` now requires at least one condition in `anyOf` and `allOf` arrays. This addresses some ambiguous behavior outlined in #9280. + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/errors@0.2.1 + - @backstage/config@0.1.14 + ## 0.4.0 ### Minor Changes diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index d3054fe9fe..122106da8f 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-common", "description": "Isomorphic types and client for Backstage permissions and authorization", - "version": "0.4.0", + "version": "0.5.1", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { @@ -10,6 +10,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -27,25 +30,25 @@ ], "configSchema": "config.d.ts", "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "bugs": { "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", "cross-fetch": "^3.1.5", "uuid": "^8.0.0", "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", + "@backstage/cli": "^0.14.0", "@types/jest": "^26.0.7", "msw": "^0.35.0" } diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index d79c79159f..74ea9b927b 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-permission-node +## 0.5.2 + +### Patch Changes + +- 0816f8237a: Improved error message shown when permissions are enabled without backend-to-backend authentication. +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-auth-node@0.1.3 + +## 0.5.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/plugin-auth-node@0.1.2 + - @backstage/plugin-permission-common@0.5.1 + +## 0.5.0 + +### Minor Changes + +- 8c646beb24: **BREAKING** `PermissionCriteria` now requires at least one condition in `anyOf` and `allOf` arrays. This addresses some ambiguous behavior outlined in #9280. + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/plugin-auth-node@0.1.1 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/config@0.1.14 + ## 0.4.3 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 04a567f16b..7fb5542c27 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.4.3", + "version": "0.5.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "node-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,26 +24,27 @@ "permissions" ], "scripts": { - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-auth-node": "^0.1.0", - "@backstage/plugin-permission-common": "^0.4.0", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-auth-node": "^0.1.3", + "@backstage/plugin-permission-common": "^0.5.1", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/permission-node/src/ServerPermissionClient.test.ts b/plugins/permission-node/src/ServerPermissionClient.test.ts index 9fa1eadb5e..53300061a0 100644 --- a/plugins/permission-node/src/ServerPermissionClient.test.ts +++ b/plugins/permission-node/src/ServerPermissionClient.test.ts @@ -113,7 +113,7 @@ describe('ServerPermissionClient', () => { tokenManager: ServerTokenManager.noop(), }), ).toThrowError( - 'You must configure at least one key in backend.auth.keys if permissions are enabled.', + 'Backend-to-backend authentication must be configured before enabling permissions. Read more here https://backstage.io/docs/tutorials/backend-to-backend-auth', ); }); }); diff --git a/plugins/permission-node/src/ServerPermissionClient.ts b/plugins/permission-node/src/ServerPermissionClient.ts index f22779c8e8..8c03016268 100644 --- a/plugins/permission-node/src/ServerPermissionClient.ts +++ b/plugins/permission-node/src/ServerPermissionClient.ts @@ -56,7 +56,7 @@ export class ServerPermissionClient implements PermissionAuthorizer { (tokenManager as any).isInsecureServerTokenManager ) { throw new Error( - 'You must configure at least one key in backend.auth.keys if permissions are enabled.', + 'Backend-to-backend authentication must be configured before enabling permissions. Read more here https://backstage.io/docs/tutorials/backend-to-backend-auth', ); } diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index 41c55b5a45..f83856d958 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,23 @@ # @backstage/plugin-permission-react +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@0.7.0 + +## 0.3.1 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/config@0.1.14 + ## 0.3.0 ### Minor Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index ce6e2dbf54..9b338df822 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.3.0", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -9,6 +9,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "web-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -19,17 +22,18 @@ "backstage" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-permission-common": "^0.4.0", + "@backstage/config": "^0.1.15", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-permission-common": "^0.5.1", "cross-fetch": "^3.1.5", "react-router": "6.0.0-beta.0", "react-use": "^17.2.4", @@ -40,8 +44,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0", - "@backstage/test-utils": "^0.2.4-next.0", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@types/jest": "^26.0.7" diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 7460eddd5a..84699fb11f 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,30 @@ # @backstage/plugin-proxy-backend +## 0.2.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + +## 0.2.20 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + +## 0.2.19 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/config@0.1.14 + ## 0.2.18 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 5336bbb098..8bdb7b72bd 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-proxy-backend", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", - "version": "0.2.18", + "version": "0.2.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -20,17 +23,17 @@ "backstage" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -43,7 +46,7 @@ "yup": "^0.32.9" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index 14e566219a..0292160304 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-rollbar-backend +## 0.1.24 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + +## 0.1.23 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + +## 0.1.22 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 1433045c08: Removed unused `helmet` dependency. +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/config@0.1.14 + ## 0.1.21 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 36683ef66a..3f477e46d1 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar-backend", "description": "A Backstage backend plugin that integrates towards Rollbar", - "version": "0.1.21", + "version": "0.1.24", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,17 +25,17 @@ "rollbar" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", "@types/express": "^4.17.6", "camelcase-keys": "^7.0.1", "compression": "^1.7.4", @@ -40,7 +43,6 @@ "express": "^4.17.1", "express-promise-router": "^4.1.0", "fs-extra": "9.1.0", - "helmet": "^4.0.0", "lodash": "^4.17.21", "morgan": "^1.10.0", "node-fetch": "^2.6.7", @@ -48,8 +50,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@types/supertest": "^2.0.8", "msw": "^0.36.3", "supertest": "^6.1.3" diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index df1cb19f83..d3dc72fc57 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-rollbar +## 0.4.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.3.27 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.3.26 ### Patch Changes diff --git a/plugins/rollbar/api-report.md b/plugins/rollbar/api-report.md index fbdbd36208..f666ade34c 100644 --- a/plugins/rollbar/api-report.md +++ b/plugins/rollbar/api-report.md @@ -12,18 +12,15 @@ import { Entity } from '@backstage/catalog-model'; import { IdentityApi } from '@backstage/core-plugin-api'; import { RouteRef } from '@backstage/core-plugin-api'; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "EntityPageRollbar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityPageRollbar: (_props: Props) => JSX.Element; +export const EntityPageRollbar: () => JSX.Element; // Warning: (ae-missing-release-tag) "EntityRollbarContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityRollbarContent: (_props: { - entity?: Entity | undefined; -}) => JSX.Element; +export const EntityRollbarContent: (_props: {}) => JSX.Element; // Warning: (ae-missing-release-tag) "isPluginApplicableToEntity" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -103,5 +100,5 @@ export { rollbarPlugin }; // Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const Router: (_props: Props_2) => JSX.Element; +export const Router: (_props: Props) => JSX.Element; ``` diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 5767f26c5e..acd07071e5 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar", "description": "A Backstage plugin that integrates towards Rollbar", - "version": "0.3.26", + "version": "0.4.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,21 +25,21 @@ "rollbar" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -50,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/rollbar/src/components/EntityPageRollbar/EntityPageRollbar.tsx b/plugins/rollbar/src/components/EntityPageRollbar/EntityPageRollbar.tsx index 8b51691778..626b3442e5 100644 --- a/plugins/rollbar/src/components/EntityPageRollbar/EntityPageRollbar.tsx +++ b/plugins/rollbar/src/components/EntityPageRollbar/EntityPageRollbar.tsx @@ -14,17 +14,11 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; import React from 'react'; import { RollbarProject } from '../RollbarProject/RollbarProject'; -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; - -export const EntityPageRollbar = (_props: Props) => { +export const EntityPageRollbar = () => { const { entity } = useEntity(); return ; diff --git a/plugins/rollbar/src/components/Router.tsx b/plugins/rollbar/src/components/Router.tsx index 032bf0166e..a11acb379c 100644 --- a/plugins/rollbar/src/components/Router.tsx +++ b/plugins/rollbar/src/components/Router.tsx @@ -25,10 +25,7 @@ import { MissingAnnotationEmptyState } from '@backstage/core-components'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[ROLLBAR_ANNOTATION]); -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; +type Props = {}; export const Router = (_props: Props) => { const { entity } = useEntity(); @@ -39,7 +36,7 @@ export const Router = (_props: Props) => { return ( - } /> + } /> ); }; diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 29ff4e9edb..00e5f08ba4 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.2 + +### Patch Changes + +- 0f37cdef19: Migrated over from the deprecated `spec.metadata` to `spec.templateInfo` for the `name` and the `baseUrl` of the template. +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-scaffolder-backend@0.17.0 + - @backstage/integration@0.7.5 + +## 0.2.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/integration@0.7.4 + - @backstage/types@0.1.3 + - @backstage/plugin-scaffolder-backend@0.16.1 + +## 0.2.0 + +### Minor Changes + +- 661594bf43: Updated to the latest version of `@backstage/plugin-scaffolder-backend`, meaning the `TemplateAction` now exposes the precise input type rather than `any`. + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/plugin-scaffolder-backend@0.16.0 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.1.11 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index cee138b576..2e878fc882 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", - "version": "0.1.11", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,29 +10,32 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin-module" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/plugin-scaffolder-backend": "^0.15.24", - "@backstage/config": "^0.1.13", - "@backstage/types": "^0.1.1", + "@backstage/backend-common": "^0.11.0", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/plugin-scaffolder-backend": "^0.17.0", + "@backstage/config": "^0.1.15", + "@backstage/types": "^0.1.3", "command-exists": "^1.2.9", "fs-extra": "10.0.0", "winston": "^3.2.1", "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/jest": "^26.0.7", diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts index bba51e3f01..18a6722663 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.test.ts @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const runCommand = jest.fn(); +const executeShellCommand = jest.fn(); const commandExists = jest.fn(); const fetchContents = jest.fn(); jest.mock('@backstage/plugin-scaffolder-backend', () => ({ ...jest.requireActual('@backstage/plugin-scaffolder-backend'), fetchContents, - runCommand, + executeShellCommand, })); jest.mock('command-exists', () => commandExists); @@ -89,7 +89,10 @@ describe('fetch:cookiecutter', () => { help: 'me', }, }, - baseUrl: 'somebase', + templateInfo: { + entityRef: 'template:default/cookiecutter', + baseUrl: 'somebase', + }, workspacePath: mockTmpDir, logger: getVoidLogger(), logStream: new PassThrough(), @@ -112,8 +115,8 @@ describe('fetch:cookiecutter', () => { }); }); - // Mock when runCommand is called it creats some new files in the mock filesystem - runCommand.mockImplementation(async () => { + // Mock when executeShellCommand is called it creats some new files in the mock filesystem + executeShellCommand.mockImplementation(async () => { mockFs({ [`${join(mockTmpDir, 'intermediate')}`]: { 'testfile.json': '{}', @@ -149,7 +152,7 @@ describe('fetch:cookiecutter', () => { expect.objectContaining({ reader: mockReader, integrations, - baseUrl: mockContext.baseUrl, + baseUrl: mockContext.templateInfo?.baseUrl, fetchUrl: mockContext.input.url, outputPath: join( mockTmpDir, @@ -160,12 +163,12 @@ describe('fetch:cookiecutter', () => { ); }); - it('should call out to cookiecutter using runCommand when cookiecutter is installed', async () => { + it('should call out to cookiecutter using executeShellCommand when cookiecutter is installed', async () => { commandExists.mockResolvedValue(true); await action.handler(mockContext); - expect(runCommand).toHaveBeenCalledWith( + expect(executeShellCommand).toHaveBeenCalledWith( expect.objectContaining({ command: 'cookiecutter', args: [ diff --git a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts index c4d7f1e3e8..6b0cb4695d 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts @@ -27,9 +27,9 @@ import fs from 'fs-extra'; import path, { resolve as resolvePath } from 'path'; import { Writable } from 'stream'; import { - runCommand, createTemplateAction, fetchContents, + executeShellCommand, } from '@backstage/plugin-scaffolder-backend'; export class CookiecutterRunner { @@ -89,7 +89,7 @@ export class CookiecutterRunner { () => false, ); if (cookieCutterInstalled) { - await runCommand({ + await executeShellCommand({ command: 'cookiecutter', args: ['--no-input', '-o', intermediateDir, templateDir, '--verbose'], logStream, @@ -222,7 +222,7 @@ export function createFetchCookiecutterAction(options: { await fetchContents({ reader, integrations, - baseUrl: ctx.baseUrl, + baseUrl: ctx.templateInfo?.baseUrl, fetchUrl: ctx.input.url, outputPath: templateContentsDir, }); diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 2b369835f8..5a71d8d8ec 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.3.2 + +### Patch Changes + +- 0f37cdef19: Migrated over from the deprecated `spec.metadata` to `spec.templateInfo` for the `name` and the `baseUrl` of the template. +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-scaffolder-backend@0.17.0 + - @backstage/integration@0.7.5 + +## 0.3.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/integration@0.7.4 + - @backstage/types@0.1.3 + - @backstage/plugin-scaffolder-backend@0.16.1 + +## 0.3.0 + +### Minor Changes + +- 661594bf43: Updated to the latest version of `@backstage/plugin-scaffolder-backend`, meaning the `TemplateAction` now exposes the precise input type rather than `any`. + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/plugin-scaffolder-backend@0.16.0 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.2.6 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 5f32dbadd8..f3dc8cd8e5 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", "description": "A module for the scaffolder backend that lets you template projects using Rails", - "version": "0.2.6", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,27 +11,30 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin-module" + }, "scripts": { - "build": "backstage-cli backend:build", - "start": "backstage-cli backend:dev", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/plugin-scaffolder-backend": "^0.15.24", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/types": "^0.1.1", + "@backstage/backend-common": "^0.11.0", + "@backstage/plugin-scaffolder-backend": "^0.17.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/types": "^0.1.3", "command-exists": "^1.2.9", "fs-extra": "^9.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "@types/command-exists": "^1.2.0", diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts index 0c7552f72f..0a6a996aa8 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.test.ts @@ -62,7 +62,10 @@ describe('fetch:rails', () => { help: 'me', }, }, - baseUrl: 'somebase', + templateInfo: { + baseUrl: 'somebase', + entityRef: 'template:default/myTemplate', + }, workspacePath: mockTmpDir, logger: getVoidLogger(), logStream: new PassThrough(), @@ -100,7 +103,7 @@ describe('fetch:rails', () => { expect(fetchContents).toHaveBeenCalledWith({ reader: mockReader, integrations, - baseUrl: mockContext.baseUrl, + baseUrl: mockContext.templateInfo.baseUrl, fetchUrl: mockContext.input.url, outputPath: resolvePath(mockContext.workspacePath), }); diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts index 2865361a8b..132929ee1c 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/index.ts @@ -170,7 +170,7 @@ export function createFetchRailsAction(options: { await fetchContents({ reader, integrations, - baseUrl: ctx.baseUrl, + baseUrl: ctx.templateInfo?.baseUrl, fetchUrl: ctx.input.url, outputPath: workDir, }); diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.test.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.test.ts index 03d894f832..0767e63f29 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.test.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.test.ts @@ -14,10 +14,12 @@ * limitations under the License. */ -const runCommand = jest.fn(); +const executeShellCommand = jest.fn(); const commandExists = jest.fn(); -jest.mock('@backstage/plugin-scaffolder-backend', () => ({ runCommand })); +jest.mock('@backstage/plugin-scaffolder-backend', () => ({ + executeShellCommand, +})); jest.mock('command-exists', () => commandExists); jest.mock('fs-extra'); @@ -195,7 +197,7 @@ describe('Rails Templater', () => { logStream: stream, }); - expect(runCommand).toHaveBeenCalledWith({ + expect(executeShellCommand).toHaveBeenCalledWith({ command: 'rails', args: expect.arrayContaining([ 'new', @@ -225,7 +227,7 @@ describe('Rails Templater', () => { logStream: stream, }); - expect(runCommand).toHaveBeenCalledWith({ + expect(executeShellCommand).toHaveBeenCalledWith({ command: 'rails', args: expect.arrayContaining([ 'new', diff --git a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts index f7817c7fb5..4a4672795a 100644 --- a/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts +++ b/plugins/scaffolder-backend-module-rails/src/actions/fetch/rails/railsNewRunner.ts @@ -17,7 +17,7 @@ import { ContainerRunner } from '@backstage/backend-common'; import fs from 'fs-extra'; import path from 'path'; -import { runCommand } from '@backstage/plugin-scaffolder-backend'; +import { executeShellCommand } from '@backstage/plugin-scaffolder-backend'; import commandExists from 'command-exists'; import { railsArgumentResolver, @@ -64,7 +64,7 @@ export class RailsNewRunner { railsArguments as RailsRunOptions, ); - await runCommand({ + await executeShellCommand({ command: baseCommand, args: [ ...baseArguments, diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 8c5a6ade44..f20f70b82e 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,27 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.17.0 + +## 0.2.0 + +### Minor Changes + +- 661594bf43: Updated to the latest version of `@backstage/plugin-scaffolder-backend`, meaning the `TemplateAction` now exposes the precise input type rather than `any`. + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- c82cd1b137: Bump `yeoman-environment` dependency from `^3.6.0` to `^3.9.1`. +- Updated dependencies + - @backstage/plugin-scaffolder-backend@0.16.0 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + ## 0.1.5 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index b2ce1bf571..609e8abab7 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.1.5", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,24 +10,27 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin-module" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/plugin-scaffolder-backend": "^0.15.24", - "@backstage/types": "^0.1.1", + "@backstage/config": "^0.1.15", + "@backstage/plugin-scaffolder-backend": "^0.17.0", + "@backstage/types": "^0.1.3", "winston": "^3.2.1", "yeoman-environment": "^3.9.1" }, "devDependencies": { - "@backstage/backend-common": "^0.10.7", + "@backstage/backend-common": "^0.11.0", "@types/jest": "^26.0.7" }, "files": [ diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 7c80464d71..25beb6c644 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,123 @@ # @backstage/plugin-scaffolder-backend +## 0.17.2 + +### Patch Changes + +- bug: `repoUrl` does not have a protocol in `publish:github:pull-request` + +## 0.17.1 + +### Patch Changes + +- bug: fixing `repoUrl` resolution for `publish:github:pull-request` action + +## 0.17.0 + +### Minor Changes + +- 91c6faeb7b: - **BREAKING** - the `/v2/tasks` endpoint now takes `templateRef` instead of `templateName` in the POST body. This should be a valid stringified `entityRef`. +- 7f193ff019: - **BREAKING** - `DatabaseTaskStore()` constructor is now removed. Please use the `DatabaseTaskStore.create()` method instead. + + - **BREAKING** - `TaskStore.createTask()` method now only takes one argument of type `TaskStoreCreateTaskOptions` which encapsulates the `spec` and `secrets` + + ```diff + - TaskStore.createTask(spec, secrets) + + TaskStore.createTask({ spec, secrets}) + ``` + + - **BREAKING** - `TaskBroker.dispatch()` method now only takes one argument of type `TaskBrokerDispatchOptions` which encapsulates the `spec` and `secrets` + + ```diff + - TaskBroker.dispatch(spec, secrets) + + TaskBroker.dispatch({ spec, secrets}) + ``` + +- 9d9b2bab47: - **BREAKING** - Removed the re-export of types `TaskSpec` `TaskSpecV1Beta2` and `TaskSpecV1Beta3` these should now be import from `@backstage/plugin-scaffolder-common` directly. + - **BREAKING** - Removed the `observe` method from the `TaskBroker` interface, this has now been replaced with an `Observable` implementation under `event# @backstage/plugin-scaffolder-backend. + +### Patch Changes + +- 9d9b2bab47: - **DEPRECATED** - Deprecated the `runCommand` export in favour of `executeShellCommand`. Please migrate to using the new method. + - Added a type parameter to `TaskStoreEmitOptions` to type the `body` property +- 65a7939c6c: - **DEPRECATED** - `TaskState` has been deprecated in favour of `CurrentClaimedTask` + - Narrowed the types from `JSONValue` to `JSONObject` as the usage is and should always be `JSONObject` for `complete` and `emitLog` `metadata` in `TaskContext` +- 67a7c02d26: Remove usages of `EntityRef` and `parseEntityName` from `@backstage/catalog-model` +- ed09ad8093: Updated usage of the `LocationSpec` type from `@backstage/catalog-model`, which is deprecated. +- 6981ac4ad2: - **DEPRECATED** - The `containerRunner` option passed to `createBuiltinActions` has now been deprecated. + + - **DEPRECATED** - The `createFetchCookiecutterAction` export has also been deprecated and will soon disappear from this plugin. + + The `fetch:cookiecutter` action will soon be removed from the default list of actions that are provided out of the box from the scaffolder plugin. It will still be supported, and maintained by the community, so you can install the package (`@backstage/plugin-scaffolder-backend-module-cookiecutter`) and pass it in as a custom action. Or you can migrate your templates to use [`fetch:template`](https://backstage.io/docs/features/software-templates/builtin-actions#migrating-from-fetchcookiecutter-to-fetchtemplate) with the `cookiecutterCompat` option. + +- b1744f1153: - **DEPRECATED** - `OctokitProvider` has been deprecated and will be removed in upcoming versions + This helper doesn't make sense to be export from the `plugin-scaffolder-backend` and possibly will be moved into the `integrations` package at a later date. + All implementations have been moved over to a private implementation called `getOctokitOptions` which is then passed to the `Octokit` constructor. If you're using this API you should consider duplicating the logic that lives in `getOctokitOptions` and move away from the deprecated export. +- 0f37cdef19: Migrated over from the deprecated `spec.metadata` to `spec.templateInfo` for the `name` and the `baseUrl` of the template. +- 7f193ff019: - **DEPRECATED** - `Status` has been deprecated in favour of `TaskStatus` + - **DEPRECATED** - `CompletedTaskState` has been deprecated in favour of `TaskCompletionState` + - **DEPRECATED** - `DispatchResult` has been deprecated in favour of `TaskBrokerDispatchResult` +- df61ca71dd: Implemented required `getProcessorName` method for catalog processor. +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-catalog-backend@0.22.0 + - @backstage/plugin-scaffolder-common@0.2.2 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.2 + - @backstage/integration@0.7.5 + +## 0.16.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/integration@0.7.4 + - @backstage/types@0.1.3 + - @backstage/plugin-catalog-backend@0.21.5 + - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.1 + - @backstage/plugin-scaffolder-common@0.2.1 + +## 0.16.0 + +### Minor Changes + +- 661594bf43: **BREAKING**: Updated `TemplateAction` and related types to have its type parameter extend `JsonObject` instead of `InputBase`. The `createTemplateAction` has also been updated to pass through the `TInput` type parameter to the return type, meaning the `TemplateAction` retains its type. This can lead to breakages during type checking especially within tests. +- 8db2b671c6: **BREAKING**: `ctx.token` is now `ctx.secrets.backstageToken` in Actions. Please update any of your Actions that might call out to Backstage API's with this token. +- 5a1594330e: **BREAKING** - Removed the `CatalogEntityClient` export. This is no longer provider by this package, + but you can implement one pretty simply yourself using the `CatalogApi` and applying filters to fetch templates. +- 7d3471db94: Remove the previously deprecated `scaffolder.provider` config for all providers. + This config is no longer used anywhere, and adopters should use [`integrations` config](https://backstage.io/docs/integrations) instead. + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- 3e59f90b51: Fix error handling of the `runCommand` helper to return `Error` + instance. +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 216725b434: Updated to use new names for `parseLocationRef` and `stringifyLocationRef` +- e72d371296: Use `TemplateEntityV1beta2` from `@backstage/plugin-scaffolder-common` instead + of `@backstage/catalog-model`. +- 1433045c08: Removed unused `helmet` dependency. +- 27eccab216: Replaces use of deprecated catalog-model constants. +- Updated dependencies + - @backstage/plugin-scaffolder-common@0.2.0 + - @backstage/plugin-catalog-backend@0.21.4 + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/types@0.1.2 + - @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.0 + ## 0.15.24 ### Patch Changes diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 19411df800..3bc03c4840 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -18,8 +18,9 @@ import { GithubCredentialsProvider } from '@backstage/integration'; import { JsonObject } from '@backstage/types'; import { JsonValue } from '@backstage/types'; import { Knex } from 'knex'; -import { LocationSpec } from '@backstage/catalog-model'; +import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger as Logger_2 } from 'winston'; +import { Observable } from '@backstage/types'; import { Octokit } from 'octokit'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { Schema } from 'jsonschema'; @@ -27,60 +28,50 @@ import { ScmIntegrationRegistry } from '@backstage/integration'; import { ScmIntegrations } from '@backstage/integration'; import { SpawnOptionsWithoutStdio } from 'child_process'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; -import { TaskSpecV1beta2 } from '@backstage/plugin-scaffolder-common'; -import { TaskSpecV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { TemplateEntityV1beta2 } from '@backstage/catalog-model'; +import { TemplateInfo } from '@backstage/plugin-scaffolder-common'; import { TemplateMetadata } from '@backstage/plugin-scaffolder-common'; import { UrlReader } from '@backstage/backend-common'; import { Writable } from 'stream'; -// Warning: (ae-missing-release-tag) "ActionContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type ActionContext = { baseUrl?: string; logger: Logger_2; logStream: Writable; - token?: string | undefined; secrets?: TaskSecrets; workspacePath: string; input: Input; output(name: string, value: JsonValue): void; createTemporaryDirectory(): Promise; metadata?: TemplateMetadata; + templateInfo?: TemplateInfo; }; -// Warning: (ae-missing-release-tag) "CatalogEntityClient" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// +// @public @deprecated +export type CompletedTaskState = TaskCompletionState; + // @public -export class CatalogEntityClient { - constructor(catalogClient: CatalogApi); - findTemplate( - templateName: string, - options?: { - token?: string; - }, - ): Promise; +export const createBuiltinActions: ( + options: CreateBuiltInActionsOptions, +) => TemplateAction[]; + +// @public +export interface CreateBuiltInActionsOptions { + // (undocumented) + additionalTemplateFilters?: Record; + // (undocumented) + catalogClient: CatalogApi; + // (undocumented) + config: Config; + // @deprecated (undocumented) + containerRunner?: ContainerRunner; + // (undocumented) + integrations: ScmIntegrations; + // (undocumented) + reader: UrlReader; } // @public -export type CompletedTaskState = 'failed' | 'completed'; - -// Warning: (ae-missing-release-tag) "createBuiltinActions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const createBuiltinActions: (options: { - reader: UrlReader; - integrations: ScmIntegrations; - catalogClient: CatalogApi; - containerRunner?: ContainerRunner; - config: Config; - additionalTemplateFilters?: Record; -}) => TemplateAction[]; - -// Warning: (ae-missing-release-tag) "createCatalogRegisterAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) export function createCatalogRegisterAction(options: { catalogClient: CatalogApi; integrations: ScmIntegrations; @@ -96,16 +87,12 @@ export function createCatalogRegisterAction(options: { } >; -// Warning: (ae-missing-release-tag) "createCatalogWriteAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createCatalogWriteAction(): TemplateAction<{ filePath?: string | undefined; entity: Entity; }>; -// Warning: (ae-missing-release-tag) "createDebugLogAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function createDebugLogAction(): TemplateAction<{ message?: string | undefined; @@ -114,9 +101,7 @@ export function createDebugLogAction(): TemplateAction<{ export { createFetchCookiecutterAction }; -// Warning: (ae-missing-release-tag) "createFetchPlainAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createFetchPlainAction(options: { reader: UrlReader; integrations: ScmIntegrations; @@ -125,9 +110,7 @@ export function createFetchPlainAction(options: { targetPath?: string | undefined; }>; -// Warning: (ae-missing-release-tag) "createFetchTemplateAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createFetchTemplateAction(options: { reader: UrlReader; integrations: ScmIntegrations; @@ -141,16 +124,12 @@ export function createFetchTemplateAction(options: { cookiecutterCompat?: boolean | undefined; }>; -// Warning: (ae-missing-release-tag) "createFilesystemDeleteAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const createFilesystemDeleteAction: () => TemplateAction<{ files: string[]; }>; -// Warning: (ae-missing-release-tag) "createFilesystemRenameAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const createFilesystemRenameAction: () => TemplateAction<{ files: Array<{ from: string; @@ -159,9 +138,7 @@ export const createFilesystemRenameAction: () => TemplateAction<{ }>; }>; -// Warning: (ae-missing-release-tag) "createGithubActionsDispatchAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createGithubActionsDispatchAction(options: { integrations: ScmIntegrations; githubCredentialsProvider?: GithubCredentialsProvider; @@ -177,9 +154,29 @@ export function createGithubActionsDispatchAction(options: { token?: string | undefined; }>; -// Warning: (ae-missing-release-tag) "createGithubWebhookAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) +export interface CreateGithubPullRequestActionOptions { + // (undocumented) + clientFactory?: ( + input: CreateGithubPullRequestClientFactoryInput, + ) => Promise; + // (undocumented) + githubCredentialsProvider?: GithubCredentialsProvider; + // (undocumented) + integrations: ScmIntegrationRegistry; +} + +// @public (undocumented) +export type CreateGithubPullRequestClientFactoryInput = { + integrations: ScmIntegrationRegistry; + githubCredentialsProvider?: GithubCredentialsProvider; + host: string; + owner: string; + repo: string; + token?: string; +}; + +// @public export function createGithubWebhookAction(options: { integrations: ScmIntegrationRegistry; defaultWebhookSecret?: string; @@ -195,9 +192,7 @@ export function createGithubWebhookAction(options: { token?: string | undefined; }>; -// Warning: (ae-missing-release-tag) "createPublishAzureAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createPublishAzureAction(options: { integrations: ScmIntegrationRegistry; config: Config; @@ -209,9 +204,7 @@ export function createPublishAzureAction(options: { token?: string | undefined; }>; -// Warning: (ae-missing-release-tag) "createPublishBitbucketAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createPublishBitbucketAction(options: { integrations: ScmIntegrationRegistry; config: Config; @@ -225,16 +218,12 @@ export function createPublishBitbucketAction(options: { token?: string | undefined; }>; -// Warning: (ae-missing-release-tag) "createPublishFileAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export function createPublishFileAction(): TemplateAction<{ path: string; }>; -// Warning: (ae-missing-release-tag) "createPublishGithubAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createPublishGithubAction(options: { integrations: ScmIntegrationRegistry; config: Config; @@ -257,10 +246,7 @@ export function createPublishGithubAction(options: { topics?: string[] | undefined; }>; -// Warning: (ae-forgotten-export) The symbol "CreateGithubPullRequestActionOptions" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "createPublishGithubPullRequestAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const createPublishGithubPullRequestAction: ({ integrations, githubCredentialsProvider, @@ -275,9 +261,7 @@ export const createPublishGithubPullRequestAction: ({ token?: string | undefined; }>; -// Warning: (ae-missing-release-tag) "createPublishGitlabAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createPublishGitlabAction(options: { integrations: ScmIntegrationRegistry; config: Config; @@ -289,9 +273,7 @@ export function createPublishGitlabAction(options: { token?: string | undefined; }>; -// Warning: (ae-missing-release-tag) "createPublishGitlabMergeRequestAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const createPublishGitlabMergeRequestAction: (options: { integrations: ScmIntegrationRegistry; }) => TemplateAction<{ @@ -304,14 +286,10 @@ export const createPublishGitlabMergeRequestAction: (options: { token?: string | undefined; }>; -// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export function createRouter(options: RouterOptions): Promise; -// Warning: (ae-missing-release-tag) "createTemplateAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const createTemplateAction: ( templateAction: TemplateAction, ) => TemplateAction; @@ -326,9 +304,18 @@ export type CreateWorkerOptions = { additionalTemplateFilters?: Record; }; +// @public +export interface CurrentClaimedTask { + // (undocumented) + secrets?: TaskSecrets; + // (undocumented) + spec: TaskSpec; + // (undocumented) + taskId: string; +} + // @public export class DatabaseTaskStore implements TaskStore { - constructor(options: DatabaseTaskStoreOptions); // (undocumented) claimTask(): Promise; // (undocumented) @@ -338,22 +325,23 @@ export class DatabaseTaskStore implements TaskStore { eventBody, }: { taskId: string; - status: Status; + status: TaskStatus; eventBody: JsonObject; }): Promise; - // Warning: (ae-forgotten-export) The symbol "DatabaseTaskStoreOptions" needs to be exported by the entry point index.d.ts - // // (undocumented) static create(options: DatabaseTaskStoreOptions): Promise; // (undocumented) createTask( - spec: TaskSpec, - secrets?: TaskSecrets, - ): Promise<{ - taskId: string; - }>; + options: TaskStoreCreateTaskOptions, + ): Promise; // (undocumented) - emitLogEvent({ taskId, body }: TaskStoreEmitOptions): Promise; + emitLogEvent( + options: TaskStoreEmitOptions< + { + message: string; + } & JsonObject + >, + ): Promise; // (undocumented) getTask(taskId: string): Promise; // (undocumented) @@ -371,13 +359,17 @@ export class DatabaseTaskStore implements TaskStore { } // @public -export type DispatchResult = { - taskId: string; +export type DatabaseTaskStoreOptions = { + database: Knex; }; -// Warning: (ae-missing-release-tag) "fetchContents" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public @deprecated +export type DispatchResult = TaskBrokerDispatchResult; + +// @public +export const executeShellCommand: (options: RunCommandOptions) => Promise; + +// @public export function fetchContents({ reader, integrations, @@ -388,19 +380,21 @@ export function fetchContents({ reader: UrlReader; integrations: ScmIntegrations; baseUrl?: string; - fetchUrl?: JsonValue; + fetchUrl?: string; outputPath: string; }): Promise; // Warning: (ae-missing-release-tag) "OctokitProvider" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public +// @public @deprecated export class OctokitProvider { constructor( integrations: ScmIntegrationRegistry, githubCredentialsProvider?: GithubCredentialsProvider, ); // Warning: (ae-forgotten-export) The symbol "OctokitIntegration" needs to be exported by the entry point index.d.ts + // + // @deprecated getOctokit( repoUrl: string, options?: { @@ -409,6 +403,16 @@ export class OctokitProvider { ): Promise; } +// @public (undocumented) +export interface OctokitWithPullRequestPluginClient { + // (undocumented) + createPullRequest(options: createPullRequest.Options): Promise<{ + data: { + html_url: string; + }; + } | null>; +} + // @public export interface RouterOptions { // (undocumented) @@ -433,19 +437,21 @@ export interface RouterOptions { taskWorkers?: number; } -// Warning: (ae-forgotten-export) The symbol "RunCommandOptions" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "runCommand" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export const runCommand: ({ - command, - args, - logStream, - options, -}: RunCommandOptions) => Promise; +// @public @deprecated +export const runCommand: (options: RunCommandOptions) => Promise; + +// @public (undocumented) +export type RunCommandOptions = { + command: string; + args: string[]; + options?: SpawnOptionsWithoutStdio; + logStream?: Writable; +}; // @public (undocumented) export class ScaffolderEntitiesProcessor implements CatalogProcessor { + // (undocumented) + getProcessorName(): string; // (undocumented) postProcessEntity( entity: Entity, @@ -460,7 +466,7 @@ export class ScaffolderEntitiesProcessor implements CatalogProcessor { export type SerializedTask = { id: string; spec: TaskSpec; - status: Status; + status: TaskStatus; createdAt: string; lastHeartbeatAt?: string; secrets?: TaskSecrets; @@ -475,49 +481,49 @@ export type SerializedTaskEvent = { createdAt: string; }; -// @public -export type Status = - | 'open' - | 'processing' - | 'failed' - | 'cancelled' - | 'completed'; +// @public @deprecated +export type Status = TaskStatus; // @public export interface TaskBroker { // (undocumented) claim(): Promise; // (undocumented) - dispatch(spec: TaskSpec, secrets?: TaskSecrets): Promise; + dispatch( + options: TaskBrokerDispatchOptions, + ): Promise; + // (undocumented) + event$(options: { taskId: string; after: number | undefined }): Observable<{ + events: SerializedTaskEvent[]; + }>; // (undocumented) get(taskId: string): Promise; // (undocumented) - observe( - options: { - taskId: string; - after: number | undefined; - }, - callback: ( - error: Error | undefined, - result: { - events: SerializedTaskEvent[]; - }, - ) => void, - ): { - unsubscribe: () => void; - }; - // (undocumented) - vacuumTasks(timeoutS: { timeoutS: number }): Promise; + vacuumTasks(options: { timeoutS: number }): Promise; } +// @public +export type TaskBrokerDispatchOptions = { + spec: TaskSpec; + secrets?: TaskSecrets; +}; + +// @public +export type TaskBrokerDispatchResult = { + taskId: string; +}; + +// @public +export type TaskCompletionState = 'failed' | 'completed'; + // @public export interface TaskContext { // (undocumented) - complete(result: CompletedTaskState, metadata?: JsonValue): Promise; + complete(result: TaskCompletionState, metadata?: JsonObject): Promise; // (undocumented) done: boolean; // (undocumented) - emitLog(message: string, metadata?: JsonValue): Promise; + emitLog(message: string, logMetadata?: JsonObject): Promise; // (undocumented) getWorkspaceName(): Promise; // (undocumented) @@ -532,17 +538,17 @@ export type TaskEventType = 'completion' | 'log'; // @public export class TaskManager implements TaskContext { // (undocumented) - complete(result: CompletedTaskState, metadata?: JsonObject): Promise; + complete(result: TaskCompletionState, metadata?: JsonObject): Promise; // (undocumented) static create( - state: TaskState, + task: CurrentClaimedTask, storage: TaskStore, logger: Logger_2, ): TaskManager; // (undocumented) get done(): boolean; // (undocumented) - emitLog(message: string, metadata?: JsonObject): Promise; + emitLog(message: string, logMetadata?: JsonObject): Promise; // (undocumented) getWorkspaceName(): Promise; // (undocumented) @@ -553,25 +559,19 @@ export class TaskManager implements TaskContext { // @public export type TaskSecrets = Record & { - token?: string; backstageToken?: string; }; -export { TaskSpec }; - -export { TaskSpecV1beta2 }; - -export { TaskSpecV1beta3 }; +// @public @deprecated +export type TaskState = CurrentClaimedTask; // @public -export interface TaskState { - // (undocumented) - secrets?: TaskSecrets; - // (undocumented) - spec: TaskSpec; - // (undocumented) - taskId: string; -} +export type TaskStatus = + | 'open' + | 'processing' + | 'failed' + | 'cancelled' + | 'completed'; // @public export interface TaskStore { @@ -580,16 +580,13 @@ export interface TaskStore { // (undocumented) completeTask(options: { taskId: string; - status: Status; + status: TaskStatus; eventBody: JsonObject; }): Promise; // (undocumented) createTask( - task: TaskSpec, - secrets?: TaskSecrets, - ): Promise<{ - taskId: string; - }>; + options: TaskStoreCreateTaskOptions, + ): Promise; // (undocumented) emitLogEvent({ taskId, body }: TaskStoreEmitOptions): Promise; // (undocumented) @@ -609,9 +606,20 @@ export interface TaskStore { } // @public -export type TaskStoreEmitOptions = { +export type TaskStoreCreateTaskOptions = { + spec: TaskSpec; + secrets?: TaskSecrets; +}; + +// @public +export type TaskStoreCreateTaskResult = { taskId: string; - body: JsonObject; +}; + +// @public +export type TaskStoreEmitOptions = { + taskId: string; + body: TBody; }; // @public @@ -630,8 +638,6 @@ export class TaskWorker { start(): void; } -// Warning: (ae-missing-release-tag) "TemplateAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type TemplateAction = { id: string; @@ -643,9 +649,7 @@ export type TemplateAction = { handler: (ctx: ActionContext) => Promise; }; -// Warning: (ae-missing-release-tag) "TemplateActionRegistry" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class TemplateActionRegistry { // (undocumented) get(actionId: string): TemplateAction; @@ -655,10 +659,6 @@ export class TemplateActionRegistry { register(action: TemplateAction): void; } -// Warning: (ae-missing-release-tag) "TemplateFilter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public (undocumented) export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined; - -export { TemplateMetadata }; ``` diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts index aa4ecf8053..7883284165 100644 --- a/plugins/scaffolder-backend/config.d.ts +++ b/plugins/scaffolder-backend/config.d.ts @@ -28,30 +28,5 @@ export interface Config { * The commit message used when new components are created. */ defaultCommitMessage?: string; - /** - * @deprecated Replaced by parameters for the publish:github action - */ - github?: { - [key: string]: string; - }; - /** - * @deprecated Use the Gitlab integration instead - */ - gitlab?: { - api: { [key: string]: string }; - }; - /** - * @deprecated Use the Azure integration instead - */ - azure?: { - baseUrl: string; - api: { [key: string]: string }; - }; - /** - * @deprecated Use the Bitbucket integration instead - */ - bitbucket?: { - api: { [key: string]: string }; - }; }; } diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 3161448562..51ff17c1e2 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "0.15.24", + "version": "0.17.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,26 +24,26 @@ "backstage" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean", + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", "build:assets": "node scripts/build-nunjucks.js" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/plugin-catalog-backend": "^0.21.3", - "@backstage/plugin-scaffolder-common": "^0.1.3", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.11", - "@backstage/types": "^0.1.1", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/plugin-catalog-backend": "^0.22.0", + "@backstage/plugin-scaffolder-common": "^0.2.2", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.2.2", + "@backstage/types": "^0.1.3", "@gitbeaker/core": "^34.6.0", "@gitbeaker/node": "^35.1.0", "@octokit/webhooks": "^9.14.1", @@ -55,7 +58,6 @@ "git-url-parse": "^11.6.0", "globby": "^11.0.0", "handlebars": "^4.7.6", - "helmet": "^4.0.0", "isbinaryfile": "^4.0.8", "isomorphic-git": "^1.8.0", "jsonschema": "^1.2.6", @@ -70,17 +72,19 @@ "uuid": "^8.2.0", "winston": "^3.2.1", "yaml": "^1.10.0", - "vm2": "^3.9.6" + "vm2": "^3.9.6", + "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", "@types/mock-fs": "^4.13.0", "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", + "@types/zen-observable": "^0.8.0", "esbuild": "^0.14.1", "jest-when": "^3.1.0", "mock-fs": "^5.1.0", diff --git a/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts b/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts deleted file mode 100644 index 430afa5b5d..0000000000 --- a/plugins/scaffolder-backend/src/lib/catalog/CatalogEntityClient.ts +++ /dev/null @@ -1,58 +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 { TemplateEntityV1beta2 } from '@backstage/catalog-model'; -import { CatalogApi } from '@backstage/catalog-client'; -import { ConflictError, NotFoundError } from '@backstage/errors'; - -/** - * A catalog client tailored for reading out entity data from the catalog. - */ -export class CatalogEntityClient { - constructor(private readonly catalogClient: CatalogApi) {} - - /** - * Looks up a single template using a template name. - * - * Throws a NotFoundError or ConflictError if 0 or multiple templates are found. - */ - async findTemplate( - templateName: string, - options?: { token?: string }, - ): Promise { - const { items: templates } = (await this.catalogClient.getEntities( - { - filter: { - kind: 'template', - 'metadata.name': templateName, - }, - }, - options, - )) as { items: TemplateEntityV1beta2[] }; - - if (templates.length !== 1) { - if (templates.length > 1) { - throw new ConflictError( - 'Templates lookup resulted in multiple matches', - ); - } else { - throw new NotFoundError('Template not found'); - } - } - - return templates[0]; - } -} diff --git a/plugins/scaffolder-backend/src/lib/index.ts b/plugins/scaffolder-backend/src/lib/index.ts index dbb4348a2c..20096f9c03 100644 --- a/plugins/scaffolder-backend/src/lib/index.ts +++ b/plugins/scaffolder-backend/src/lib/index.ts @@ -14,5 +14,4 @@ * limitations under the License. */ -export * from './catalog'; export * from './templating'; diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index 6d3ce6f556..e82620a90d 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -95,6 +95,7 @@ const { render, renderCompat } = (() => { })(); `; +/** @public */ export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined; export interface SecureTemplaterOptions { diff --git a/plugins/scaffolder-backend/src/processor/ScaffolderEntitiesProcessor.ts b/plugins/scaffolder-backend/src/processor/ScaffolderEntitiesProcessor.ts index 4f3c13f342..43559fcd5f 100644 --- a/plugins/scaffolder-backend/src/processor/ScaffolderEntitiesProcessor.ts +++ b/plugins/scaffolder-backend/src/processor/ScaffolderEntitiesProcessor.ts @@ -17,31 +17,32 @@ import { Entity, getEntityName, - LocationSpec, parseEntityRef, RELATION_OWNED_BY, RELATION_OWNER_OF, - entityKindSchemaValidator, } from '@backstage/catalog-model'; import { CatalogProcessor, CatalogProcessorEmit, + LocationSpec, results, } from '@backstage/plugin-catalog-backend'; import { TemplateEntityV1beta3, - templateEntityV1beta3Schema, + templateEntityV1beta3Validator, } from '@backstage/plugin-scaffolder-common'; /** @public */ export class ScaffolderEntitiesProcessor implements CatalogProcessor { - private readonly validators = [ - entityKindSchemaValidator(templateEntityV1beta3Schema), - ]; + getProcessorName(): string { + return 'ScaffolderEntitiesProcessor'; + } + + private readonly validators = [templateEntityV1beta3Validator]; async validateEntityKind(entity: Entity): Promise { for (const validator of this.validators) { - if (validator(entity)) { + if (await validator.check(entity)) { return true; } } diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts index e546d5bf9a..8a2a00d121 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/TemplateActionRegistry.ts @@ -18,6 +18,10 @@ import { JsonObject } from '@backstage/types'; import { ConflictError, NotFoundError } from '@backstage/errors'; import { TemplateAction } from './types'; +/** + * Registry of all registered template actions. + * @public + */ export class TemplateActionRegistry { private readonly actions = new Map>(); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts index 65d0e1b1e7..1b30268d9d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts @@ -20,6 +20,10 @@ import { CatalogApi } from '@backstage/catalog-client'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { createTemplateAction } from '../../createTemplateAction'; +/** + * Registers entities from a catalog descriptor file in the workspace into the software catalog. + * @public + */ export function createCatalogRegisterAction(options: { catalogClient: CatalogApi; integrations: ScmIntegrations; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts index 9b159f1611..b55e521784 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts @@ -20,6 +20,10 @@ import * as yaml from 'yaml'; import { Entity } from '@backstage/catalog-model'; import { resolveSafeChildPath } from '@backstage/backend-common'; +/** + * Writes a catalog descriptor file containing the provided entity to a path in the workspace. + * @public + */ export function createCatalogWriteAction() { return createTemplateAction<{ filePath?: string; entity: Entity }>({ id: 'catalog:write', diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index 9df2b7146e..c03ba150c2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -49,15 +49,32 @@ import { } from './github'; import { TemplateFilter } from '../../../lib'; import { TemplateAction } from '../types'; +import { getRootLogger } from '@backstage/backend-common'; -export const createBuiltinActions = (options: { +/** + * The options passed to {@link createBuiltinActions} + * @public + */ +export interface CreateBuiltInActionsOptions { reader: UrlReader; integrations: ScmIntegrations; catalogClient: CatalogApi; + /** @deprecated when the cookiecutter action is removed this won't be necessary */ containerRunner?: ContainerRunner; config: Config; additionalTemplateFilters?: Record; -}): TemplateAction[] => { +} + +/** + * A function to generate create a list of default actions that the scaffolder provides. + * Is called internally in the default setup, but can be used when adding your own actions or overriding the default ones + * + * @public + * @returns A list of actions that can be used in the scaffolder + */ +export const createBuiltinActions = ( + options: CreateBuiltInActionsOptions, +): TemplateAction[] => { const { reader, integrations, @@ -119,6 +136,11 @@ export const createBuiltinActions = (options: { ]; if (containerRunner) { + getRootLogger().warn( + `[DEPRECATED] The fetch:cookiecutter action will be removed part of the default scaffolder actions in later versions. +You can install the package seperately and remove the containerRunner from the createBuiltInActions to remove this warning, +or you can migrate to using fetch:template https://backstage.io/docs/features/software-templates/builtin-actions#migrating-from-fetchcookiecutter-to-fetchtemplate`, + ); actions.push( createFetchCookiecutterAction({ reader, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts index 36ea842a66..59d4c333c6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts @@ -19,8 +19,14 @@ import { relative, join } from 'path'; import { createTemplateAction } from '../../createTemplateAction'; /** + * Writes a message into the log or lists all files in the workspace + * + * @remarks + * * This task is useful for local development and testing of both the scaffolder * and scaffolder templates. + * + * @public */ export function createDebugLogAction() { return createTemplateAction<{ message?: string; listWorkspace?: boolean }>({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts index a738afbe3f..d274ad3ba8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.test.ts @@ -50,15 +50,6 @@ describe('fetchContent helper', () => { outputPath: os.tmpdir(), }; - it('should reject non string fetchUrls', async () => { - await expect( - fetchContents({ - ...options, - fetchUrl: false, - }), - ).rejects.toThrow('Invalid url parameter, expected string, got boolean'); - }); - it('should reject absolute file locations', async () => { await expect( fetchContents({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts index b56bab0d53..a01fcee6f7 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts @@ -15,12 +15,17 @@ */ import { resolveSafeChildPath, UrlReader } from '@backstage/backend-common'; -import { JsonValue } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import fs from 'fs-extra'; import path from 'path'; +/** + * A helper function that reads the contents of a directory from the given URL. + * Can be used in your own actions, and also used behind fetch:template and fetch:plain + * + * @public + */ export async function fetchContents({ reader, integrations, @@ -31,15 +36,9 @@ export async function fetchContents({ reader: UrlReader; integrations: ScmIntegrations; baseUrl?: string; - fetchUrl?: JsonValue; + fetchUrl?: string; outputPath: string; }) { - if (typeof fetchUrl !== 'string') { - throw new InputError( - `Invalid url parameter, expected string, got ${typeof fetchUrl}`, - ); - } - let fetchUrlIsAbsolute = false; try { // eslint-disable-next-line no-new diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts index 9cac6d5ab8..6dbe08f61b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts @@ -19,6 +19,11 @@ import { ScmIntegrations } from '@backstage/integration'; import { fetchContents } from './helpers'; import { createTemplateAction } from '../../createTemplateAction'; +/** + * Downloads content and places it in the workspace, or optionally + * in a subdirectory specified by the 'targetPath' input option. + * @public + */ export function createFetchPlainAction(options: { reader: UrlReader; integrations: ScmIntegrations; @@ -59,7 +64,7 @@ export function createFetchPlainAction(options: { await fetchContents({ reader, integrations, - baseUrl: ctx.baseUrl, + baseUrl: ctx.templateInfo?.baseUrl, fetchUrl: ctx.input.url, outputPath, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts index 2f9bfd3796..8f641fe2e2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.test.ts @@ -75,7 +75,10 @@ describe('fetch:template', () => { const logger = getVoidLogger(); const mockContext = (inputPatch: Partial = {}) => ({ - baseUrl: 'base-url', + templateInfo: { + baseUrl: 'base-url', + entityRef: 'template:default/test-template', + }, input: { url: './skeleton', targetPath: './target', @@ -199,7 +202,7 @@ describe('fetch:template', () => { it('uses fetchContents to retrieve the template content', () => { expect(mockFetchContents).toHaveBeenCalledWith( expect.objectContaining({ - baseUrl: context.baseUrl, + baseUrl: context.templateInfo?.baseUrl, fetchUrl: context.input.url, }), ); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 08dfc46caf..3d3d319c57 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -28,6 +28,13 @@ import { SecureTemplater, } from '../../../../lib/templating/SecureTemplater'; +/** + * Downloads a skeleton, templates variables into file and directory names and content. + * Then places the result in the workspace, or optionally in a subdirectory + * specified by the 'targetPath' input option. + * + * @public + */ export function createFetchTemplateAction(options: { reader: UrlReader; integrations: ScmIntegrations; @@ -135,7 +142,7 @@ export function createFetchTemplateAction(options: { await fetchContents({ reader, integrations, - baseUrl: ctx.baseUrl, + baseUrl: ctx.templateInfo?.baseUrl, fetchUrl: ctx.input.url, outputPath: templateDir, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts index 068f777ec5..bf2953e75a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts @@ -18,6 +18,10 @@ import { InputError } from '@backstage/errors'; import { resolveSafeChildPath } from '@backstage/backend-common'; import fs from 'fs-extra'; +/** + * Creates new action that enables deletion of files and directories in the workspace. + * @public + */ export const createFilesystemDeleteAction = () => { return createTemplateAction<{ files: string[] }>({ id: 'fs:delete', diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts index c3e128cec5..7c8dad7559 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts @@ -19,6 +19,10 @@ import { resolveSafeChildPath } from '@backstage/backend-common'; import { InputError } from '@backstage/errors'; import fs from 'fs-extra'; +/** + * Creates a new action that allows renames of files and directories in the workspace. + * @public + */ export const createFilesystemRenameAction = () => { return createTemplateAction<{ files: Array<{ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts index 3ac66a0023..2f3c43a6b8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/OctokitProvider.ts @@ -32,6 +32,9 @@ export type OctokitIntegration = { /** * OctokitProvider provides Octokit client based on ScmIntegrationsRegistry configuration. * OctokitProvider supports GitHub credentials caching out of the box. + * + * @deprecated we are no longer providing a way from the scaffolder to generate octokit instances. + * Implement your own if you're using this method from an external package, or use the internal `getOctokitOptions` function instead */ export class OctokitProvider { private readonly integrations: ScmIntegrationRegistry; @@ -51,6 +54,9 @@ export class OctokitProvider { * gets standard Octokit client based on repository URL. * * @param repoUrl - Repository URL + * + * @deprecated we are no longer providing a way from the scaffolder to generate octokit instances. + * Implement your own if you're using this method from an external package, or use the internal `getOctokitOptions` function instead */ async getOctokit( repoUrl: string, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts index 0c8b4d4aab..b9d77559e0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubActionsDispatch.ts @@ -13,24 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { InputError } from '@backstage/errors'; import { - DefaultGithubCredentialsProvider, GithubCredentialsProvider, ScmIntegrations, } from '@backstage/integration'; +import { Octokit } from 'octokit'; import { createTemplateAction } from '../../createTemplateAction'; -import { OctokitProvider } from './OctokitProvider'; +import { parseRepoUrl } from '../publish/util'; +import { getOctokitOptions } from './helpers'; +/** + * Creates a new action that dispatches a GitHub Action workflow for a given branch or tag. + * @public + */ export function createGithubActionsDispatchAction(options: { integrations: ScmIntegrations; githubCredentialsProvider?: GithubCredentialsProvider; }) { const { integrations, githubCredentialsProvider } = options; - const octokitProvider = new OctokitProvider( - integrations, - githubCredentialsProvider || - DefaultGithubCredentialsProvider.fromIntegrations(integrations), - ); return createTemplateAction<{ repoUrl: string; @@ -90,9 +91,19 @@ export function createGithubActionsDispatchAction(options: { `Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`, ); - const { client, owner, repo } = await octokitProvider.getOctokit( - repoUrl, - { token: providedToken }, + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError('Invalid repository owner provided in repoUrl'); + } + + const client = new Octokit( + await getOctokitOptions({ + integrations, + repoUrl, + credentialsProvider: githubCredentialsProvider, + token: providedToken, + }), ); await client.rest.actions.createWorkflowDispatch({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts index 0c93ae1dd5..4547ce4492 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubWebhook.ts @@ -14,15 +14,20 @@ * limitations under the License. */ import { - DefaultGithubCredentialsProvider, GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; import { createTemplateAction } from '../../createTemplateAction'; -import { OctokitProvider } from './OctokitProvider'; import { emitterEventNames } from '@octokit/webhooks'; -import { assertError } from '@backstage/errors'; +import { assertError, InputError } from '@backstage/errors'; +import { Octokit } from 'octokit'; +import { getOctokitOptions } from './helpers'; +import { parseRepoUrl } from '../publish/util'; +/** + * Creates new action that creates a webhook for a repository on GitHub. + * @public + */ export function createGithubWebhookAction(options: { integrations: ScmIntegrationRegistry; defaultWebhookSecret?: string; @@ -30,11 +35,7 @@ export function createGithubWebhookAction(options: { }) { const { integrations, defaultWebhookSecret, githubCredentialsProvider } = options; - const octokitProvider = new OctokitProvider( - integrations, - githubCredentialsProvider ?? - DefaultGithubCredentialsProvider.fromIntegrations(integrations), - ); + const eventNames = emitterEventNames.filter(event => !event.includes('.')); return createTemplateAction<{ @@ -127,10 +128,19 @@ export function createGithubWebhookAction(options: { } = ctx.input; ctx.logger.info(`Creating webhook ${webhookUrl} for repo ${repoUrl}`); + const { owner, repo } = parseRepoUrl(repoUrl, integrations); - const { client, owner, repo } = await octokitProvider.getOctokit( - repoUrl, - { token: providedToken }, + if (!owner) { + throw new InputError('Invalid repository owner provided in repoUrl'); + } + + const client = new Octokit( + await getOctokitOptions({ + integrations, + credentialsProvider: githubCredentialsProvider, + repoUrl: repoUrl, + token: providedToken, + }), ); try { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts new file mode 100644 index 0000000000..29997e5843 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts @@ -0,0 +1,85 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { InputError } from '@backstage/errors'; +import { + DefaultGithubCredentialsProvider, + GithubCredentialsProvider, + ScmIntegrationRegistry, +} from '@backstage/integration'; +import { OctokitOptions } from '@octokit/core/dist-types/types'; +import { parseRepoUrl } from '../publish/util'; + +const DEFAULT_TIMEOUT_MS = 60_000; + +export async function getOctokitOptions(options: { + integrations: ScmIntegrationRegistry; + credentialsProvider?: GithubCredentialsProvider; + token?: string; + repoUrl: string; +}): Promise { + const { integrations, credentialsProvider, repoUrl, token } = options; + const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); + + const requestOptions = { + // set timeout to 60 seconds + timeout: DEFAULT_TIMEOUT_MS, + }; + + if (!owner) { + throw new InputError(`No owner provided for repo ${repoUrl}`); + } + + const integrationConfig = integrations.github.byHost(host)?.config; + + if (!integrationConfig) { + throw new InputError(`No integration for host ${host}`); + } + + // short circuit the `githubCredentialsProvider` if there is a token provided by the caller already + if (token) { + return { + auth: token, + baseUrl: integrationConfig.apiBaseUrl, + previews: ['nebula-preview'], + request: requestOptions, + }; + } + + const githubCredentialsProvider = + credentialsProvider ?? + DefaultGithubCredentialsProvider.fromIntegrations(integrations); + + // TODO(blam): Consider changing this API to take host and repo instead of repoUrl, as we end up parsing in this function + // and then parsing in the `getCredentials` function too the other side + const { token: credentialProviderToken } = + await githubCredentialsProvider.getCredentials({ + url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent( + repo, + )}`, + }); + + if (!credentialProviderToken) { + throw new InputError( + `No token available for host: ${host}, with owner ${owner}, and repo ${repo}`, + ); + } + + return { + auth: credentialProviderToken, + baseUrl: integrationConfig.apiBaseUrl, + previews: ['nebula-preview'], + }; +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts index 9303b5e487..79537ff8dc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts @@ -21,6 +21,7 @@ import { Git } from '@backstage/backend-common'; import { Octokit } from 'octokit'; import { assertError } from '@backstage/errors'; +/** @public */ export type RunCommandOptions = { /** command to run */ command: string; @@ -34,15 +35,18 @@ export type RunCommandOptions = { /** * Run a command in a sub-process, normally a shell command. + * + * @public */ -export const runCommand = async ({ - command, - args, - logStream = new PassThrough(), - options, -}: RunCommandOptions) => { +export const executeShellCommand = async (options: RunCommandOptions) => { + const { + command, + args, + options: spawnOptions, + logStream = new PassThrough(), + } = options; await new Promise((resolve, reject) => { - const process = spawn(command, args, options); + const process = spawn(command, args, spawnOptions); process.stdout.on('data', stream => { logStream.write(stream); @@ -67,6 +71,13 @@ export const runCommand = async ({ }); }; +/** + * Run a command in a sub-process, normally a shell command. + * @public + * @deprecated use {@link executeShellCommand} instead + */ +export const runCommand = executeShellCommand; + export async function initRepoAndPush({ dir, remoteUrl, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts index 2265f9b4f7..8be1b62eaa 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/index.ts @@ -15,12 +15,15 @@ */ export * from './catalog'; -export { createBuiltinActions } from './createBuiltinActions'; +export * from './createBuiltinActions'; export * from './debug'; export * from './fetch'; export * from './filesystem'; export * from './publish'; export * from './github'; +/** @deprecated please add this package to your own installation manually */ export { createFetchCookiecutterAction } from '@backstage/plugin-scaffolder-backend-module-cookiecutter'; -export { runCommand } from './helpers'; + +export { runCommand, executeShellCommand } from './helpers'; +export type { RunCommandOptions } from './helpers'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts index 4f67a0e45f..7a8e963b5c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.ts @@ -23,6 +23,11 @@ import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { createTemplateAction } from '../../createTemplateAction'; import { Config } from '@backstage/config'; +/** + * Creates a new action that initializes a git repository of the content in the workspace + * and publishes it to Azure. + * @public + */ export function createPublishAzureAction(options: { integrations: ScmIntegrationRegistry; config: Config; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts index 5493219d57..96fd8184ca 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts @@ -567,4 +567,47 @@ describe('publish:bitbucket', () => { 'https://bitbucket.org/workspace/repo/src/master', ); }); + + it('should call outputs with the correct urls with correct default branch', async () => { + server.use( + rest.post( + 'https://api.bitbucket.org/2.0/repositories/workspace/repo', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.json({ + links: { + html: { + href: 'https://bitbucket.org/workspace/repo', + }, + clone: [ + { + name: 'https', + href: 'https://bitbucket.org/workspace/cloneurl', + }, + ], + }, + }), + ), + ), + ); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + defaultBranch: 'main', + }, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://bitbucket.org/workspace/cloneurl', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://bitbucket.org/workspace/repo/src/main', + ); + }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts index 47ebce0550..445a494234 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.ts @@ -31,6 +31,7 @@ const createBitbucketCloudRepository = async (opts: { repo: string; description?: string; repoVisibility: 'private' | 'public'; + mainBranch: string; authorization: string; }) => { const { @@ -39,6 +40,7 @@ const createBitbucketCloudRepository = async (opts: { repo, description, repoVisibility, + mainBranch, authorization, } = opts; @@ -82,8 +84,9 @@ const createBitbucketCloudRepository = async (opts: { } } - // TODO use the urlReader to get the default branch - const repoContentsUrl = `${r.links.html.href}/src/master`; + // "mainbranch.name" cannot be set neither at create nor update of the repo + // the first pushed branch will be set as "main branch" then + const repoContentsUrl = `${r.links.html.href}/src/${mainBranch}`; return { remoteUrl, repoContentsUrl }; }; @@ -192,6 +195,11 @@ const performEnableLFS = async (opts: { ); }; +/** + * Creates a new action that initializes a git repository of the content in the workspace + * and publishes it to Bitbucket. + * @public + */ export function createPublishBitbucketAction(options: { integrations: ScmIntegrationRegistry; config: Config; @@ -324,6 +332,7 @@ export function createPublishBitbucketAction(options: { project, repo, repoVisibility, + mainBranch: defaultBranch, description, apiBaseUrl, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/file.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/file.ts index d59c87d4fa..2d2cb85930 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/file.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/file.ts @@ -23,8 +23,12 @@ import { createTemplateAction } from '../../createTemplateAction'; * This task is useful for local development and testing of both the scaffolder * and scaffolder templates. * + * @remarks + * * This action is not installed by default and should not be installed in * production, as it writes the files to the local filesystem of the scaffolder. + * + * @public */ export function createPublishFileAction() { return createTemplateAction<{ path: string }>({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index cabe40986f..926a63915a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -14,7 +14,6 @@ * limitations under the License. */ import { - DefaultGithubCredentialsProvider, GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; @@ -22,23 +21,25 @@ import { enableBranchProtectionOnDefaultRepoBranch, initRepoAndPush, } from '../helpers'; -import { getRepoSourceDirectory } from './util'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { createTemplateAction } from '../../createTemplateAction'; import { Config } from '@backstage/config'; -import { OctokitProvider } from '../github/OctokitProvider'; -import { assertError } from '@backstage/errors'; +import { assertError, InputError } from '@backstage/errors'; +import { getOctokitOptions } from '../github/helpers'; +import { Octokit } from 'octokit'; +/** + * Creates a new action that initializes a git repository of the content in the workspace + * and publishes it to GitHub. + * + * @public + */ export function createPublishGithubAction(options: { integrations: ScmIntegrationRegistry; config: Config; githubCredentialsProvider?: GithubCredentialsProvider; }) { const { integrations, config, githubCredentialsProvider } = options; - const octokitProvider = new OctokitProvider( - integrations, - githubCredentialsProvider || - DefaultGithubCredentialsProvider.fromIntegrations(integrations), - ); return createTemplateAction<{ repoUrl: string; @@ -160,10 +161,20 @@ export function createPublishGithubAction(options: { token: providedToken, } = ctx.input; - const { client, token, owner, repo } = await octokitProvider.getOctokit( + const { owner, repo } = parseRepoUrl(repoUrl, integrations); + + if (!owner) { + throw new InputError('Invalid repository owner provided in repoUrl'); + } + + const octokitOptions = await getOctokitOptions({ + integrations, + credentialsProvider: githubCredentialsProvider, + token: providedToken, repoUrl, - { token: providedToken }, - ); + }); + + const client = new Octokit(octokitOptions); const user = await client.rest.users.getByUsername({ username: owner, @@ -253,7 +264,7 @@ export function createPublishGithubAction(options: { defaultBranch, auth: { username: 'x-access-token', - password: token, + password: octokitOptions.auth, }, logger: ctx.logger, commitMessage: config.getOptionalString( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts index 64d3bc41de..9956f16003 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.test.ts @@ -26,9 +26,9 @@ import { resolve as resolvePath } from 'path'; import { Writable } from 'stream'; import { ActionContext, TemplateAction } from '../../types'; import { - ClientFactoryInput, + CreateGithubPullRequestClientFactoryInput, createPublishGithubPullRequestAction, - PullRequestCreator, + OctokitWithPullRequestPluginClient, } from './githubPullRequest'; const root = os.platform() === 'win32' ? 'C:\\root' : '/root'; @@ -42,9 +42,11 @@ type GithubPullRequestActionInput = ReturnType< describe('createPublishGithubPullRequestAction', () => { let instance: TemplateAction; - let fakeClient: PullRequestCreator; + let fakeClient: OctokitWithPullRequestPluginClient; - let clientFactory: (input: ClientFactoryInput) => Promise; + let clientFactory: ( + input: CreateGithubPullRequestClientFactoryInput, + ) => Promise; beforeEach(() => { const integrations = ScmIntegrations.fromConfig(new ConfigReader({})); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index c329b6b6f3..2ce8e1d81e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -20,7 +20,6 @@ import { parseRepoUrl, isExecutable } from './util'; import { GithubCredentialsProvider, ScmIntegrationRegistry, - SingleInstanceGithubCredentialsProvider, } from '@backstage/integration'; import { zipObject } from 'lodash'; import { createTemplateAction } from '../../createTemplateAction'; @@ -29,26 +28,21 @@ import { InputError, CustomErrorBase } from '@backstage/errors'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; import globby from 'globby'; import { resolveSafeChildPath } from '@backstage/backend-common'; +import { getOctokitOptions } from '../github/helpers'; export type Encoding = 'utf-8' | 'base64'; class GithubResponseError extends CustomErrorBase {} -type CreatePullRequestResponse = { - data: { html_url: string }; -}; - -export interface PullRequestCreator { - createPullRequest( - options: createPullRequest.Options, - ): Promise; +/** @public */ +export interface OctokitWithPullRequestPluginClient { + createPullRequest(options: createPullRequest.Options): Promise<{ + data: { html_url: string }; + } | null>; } -export type PullRequestCreatorConstructor = ( - octokit: Octokit, -) => PullRequestCreator; - -export type ClientFactoryInput = { +/** @public */ +export type CreateGithubPullRequestClientFactoryInput = { integrations: ScmIntegrationRegistry; githubCredentialsProvider?: GithubCredentialsProvider; host: string; @@ -64,49 +58,35 @@ export const defaultClientFactory = async ({ repo, host = 'github.com', token: providedToken, -}: ClientFactoryInput): Promise => { - const integrationConfig = integrations.github.byHost(host)?.config; +}: CreateGithubPullRequestClientFactoryInput): Promise => { + const [encodedHost, encodedOwner, encodedRepo] = [host, owner, repo].map( + encodeURIComponent, + ); + + const octokitOptions = await getOctokitOptions({ + integrations, + credentialsProvider: githubCredentialsProvider, + repoUrl: `${encodedHost}?owner=${encodedOwner}&repo=${encodedRepo}`, + token: providedToken, + }); + const OctokitPR = Octokit.plugin(createPullRequest); - - if (!integrationConfig) { - throw new InputError(`No integration for host ${host}`); - } - - if (providedToken) { - return new OctokitPR({ - auth: providedToken, - baseUrl: integrationConfig.apiBaseUrl, - }); - } - - const credentialsProvider = - githubCredentialsProvider || - SingleInstanceGithubCredentialsProvider.create(integrationConfig); - - const { token } = await credentialsProvider.getCredentials({ - url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent( - repo, - )}`, - }); - - if (!token) { - throw new InputError( - `No token available for host: ${host}, with owner ${owner}, and repo ${repo}`, - ); - } - - return new OctokitPR({ - auth: token, - baseUrl: integrationConfig.apiBaseUrl, - }); + return new OctokitPR(octokitOptions); }; -interface CreateGithubPullRequestActionOptions { +/** @public */ +export interface CreateGithubPullRequestActionOptions { integrations: ScmIntegrationRegistry; githubCredentialsProvider?: GithubCredentialsProvider; - clientFactory?: (input: ClientFactoryInput) => Promise; + clientFactory?: ( + input: CreateGithubPullRequestClientFactoryInput, + ) => Promise; } +/** + * Creates a Github Pull Request action. + * @public + */ export const createPublishGithubPullRequestAction = ({ integrations, githubCredentialsProvider, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts index a3c1919b63..9aa12803eb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlab.ts @@ -22,6 +22,12 @@ import { getRepoSourceDirectory, parseRepoUrl } from './util'; import { createTemplateAction } from '../../createTemplateAction'; import { Config } from '@backstage/config'; +/** + * Creates a new action that initializes a git repository of the content in the workspace + * and publishes it to GitLab. + * + * @public + */ export function createPublishGitlabAction(options: { integrations: ScmIntegrationRegistry; config: Config; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts index e04eb5cdb5..63cfeaa8b3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts @@ -24,6 +24,11 @@ import { InputError } from '@backstage/errors'; import { parseRepoUrl } from './util'; import { resolveSafeChildPath } from '@backstage/backend-common'; +/** + * Create a new action that creates a gitlab merge request. + * + * @public + */ export const createPublishGitlabMergeRequestAction = (options: { integrations: ScmIntegrationRegistry; }) => { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts index c42b04e55e..7d85b45edd 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts @@ -19,5 +19,10 @@ export { createPublishBitbucketAction } from './bitbucket'; export { createPublishFileAction } from './file'; export { createPublishGithubAction } from './github'; export { createPublishGithubPullRequestAction } from './githubPullRequest'; +export type { + CreateGithubPullRequestClientFactoryInput, + CreateGithubPullRequestActionOptions, + OctokitWithPullRequestPluginClient, +} from './githubPullRequest'; export { createPublishGitlabAction } from './gitlab'; export { createPublishGitlabMergeRequestAction } from './gitlabMergeRequest'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/createTemplateAction.ts b/plugins/scaffolder-backend/src/scaffolder/actions/createTemplateAction.ts index 243f39e070..8893fa0f20 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/createTemplateAction.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/createTemplateAction.ts @@ -17,6 +17,10 @@ import { JsonObject } from '@backstage/types'; import { TemplateAction } from './types'; +/** + * This function is used to create new template actions to get type safety. + * @public + */ export const createTemplateAction = ( templateAction: TemplateAction, ): TemplateAction => { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts index f1e5dd7b77..2a87f58fd8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts @@ -18,22 +18,24 @@ import { Logger } from 'winston'; import { Writable } from 'stream'; import { JsonValue, JsonObject } from '@backstage/types'; import { Schema } from 'jsonschema'; -import { TaskSecrets, TemplateMetadata } from '../tasks/types'; +import { TaskSecrets } from '../tasks/types'; +import { + TemplateInfo, + TemplateMetadata, +} from '@backstage/plugin-scaffolder-common'; +/** + * ActionContext is passed into scaffolder actions. + * @public */ export type ActionContext = { /** * Base URL for the location of the task spec, typically the url of the source entity file. + * @deprecated please use templateInfo.baseUrl instead */ baseUrl?: string; logger: Logger; logStream: Writable; - - /** - * User token forwarded from initial request, for use in subsequent api requests - * @deprecated use `secrets.backstageToken` instead - */ - token?: string | undefined; secrets?: TaskSecrets; workspacePath: string; input: Input; @@ -44,9 +46,14 @@ export type ActionContext = { */ createTemporaryDirectory(): Promise; + /** + * @deprecated please use templateInfo instead + */ metadata?: TemplateMetadata; + templateInfo?: TemplateInfo; }; +/** @public */ export type TemplateAction = { id: string; description?: string; diff --git a/plugins/scaffolder-backend/src/scaffolder/index.ts b/plugins/scaffolder-backend/src/scaffolder/index.ts index e055223d8c..0c6b5e01cc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/index.ts @@ -13,5 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export * from './actions'; export * from './tasks'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts index dcda3b921e..65b890c265 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts @@ -22,13 +22,13 @@ import { v4 as uuid } from 'uuid'; import { SerializedTaskEvent, SerializedTask, - Status, + TaskStatus, TaskEventType, - TaskSecrets, - TaskSpec, TaskStore, TaskStoreEmitOptions, TaskStoreListEventsOptions, + TaskStoreCreateTaskOptions, + TaskStoreCreateTaskResult, } from './types'; import { DateTime } from 'luxon'; @@ -40,7 +40,7 @@ const migrationsDir = resolvePackagePath( export type RawDbTaskRow = { id: string; spec: string; - status: Status; + status: TaskStatus; last_heartbeat_at?: string; created_at: string; secrets?: string | null; @@ -80,7 +80,7 @@ export class DatabaseTaskStore implements TaskStore { return new DatabaseTaskStore(options); } - constructor(options: DatabaseTaskStoreOptions) { + private constructor(options: DatabaseTaskStoreOptions) { this.db = options.database; } @@ -108,14 +108,13 @@ export class DatabaseTaskStore implements TaskStore { } async createTask( - spec: TaskSpec, - secrets?: TaskSecrets, - ): Promise<{ taskId: string }> { + options: TaskStoreCreateTaskOptions, + ): Promise { const taskId = uuid(); await this.db('tasks').insert({ id: taskId, - spec: JSON.stringify(spec), - secrets: secrets ? JSON.stringify(secrets) : undefined, + spec: JSON.stringify(options.spec), + secrets: options.secrets ? JSON.stringify(options.secrets) : undefined, status: 'open', }); return { taskId }; @@ -202,7 +201,7 @@ export class DatabaseTaskStore implements TaskStore { eventBody, }: { taskId: string; - status: Status; + status: TaskStatus; eventBody: JsonObject; }): Promise { let oldStatus: string; @@ -253,12 +252,15 @@ export class DatabaseTaskStore implements TaskStore { }); } - async emitLogEvent({ taskId, body }: TaskStoreEmitOptions): Promise { - const serliazedBody = JSON.stringify(body); + async emitLogEvent( + options: TaskStoreEmitOptions<{ message: string } & JsonObject>, + ): Promise { + const { taskId, body } = options; + const serializedBody = JSON.stringify(body); await this.db('task_events').insert({ task_id: taskId, event_type: 'log', - body: serliazedBody, + body: serializedBody, }); } diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts index 9b05c43831..dcad1fc5d0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.test.ts @@ -21,8 +21,9 @@ import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; import { getVoidLogger } from '@backstage/backend-common'; import { HandlebarsWorkflowRunner } from './HandlebarsWorkflowRunner'; -import { TaskContext, TaskSpec } from './types'; +import { TaskContext } from './types'; import { RepoSpec } from '../actions/builtin/publish/util'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; describe('LegacyWorkflowRunner', () => { let runner: HandlebarsWorkflowRunner; @@ -94,7 +95,7 @@ describe('LegacyWorkflowRunner', () => { }); it('should pass metadata through', async () => { - const templateName = 'template name'; + const entityRef = `template:default/templateName`; const task = createMockTaskWithSpec({ apiVersion: 'backstage.io/v1beta2', steps: [ @@ -107,13 +108,13 @@ describe('LegacyWorkflowRunner', () => { ], output: {}, values: {}, - metadata: { name: templateName }, + templateInfo: { entityRef }, }); await runner.execute(task); - expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({ - name: templateName, + expect(fakeActionHandler.mock.calls[0][0].templateInfo).toEqual({ + entityRef, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts index 91c82edebb..c82da51ead 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts @@ -13,13 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - TaskContext, - WorkflowRunner, - WorkflowResponse, - TaskSpecV1beta2, - TaskSpec, -} from './types'; +import { TaskContext, WorkflowRunner, WorkflowResponse } from './types'; import * as Handlebars from 'handlebars'; import { TemplateActionRegistry } from '..'; import { ScmIntegrations } from '@backstage/integration'; @@ -33,6 +27,7 @@ import fs from 'fs-extra'; import { validate as validateJsonSchema } from 'jsonschema'; import { JsonObject, JsonValue } from '@backstage/types'; import { InputError } from '@backstage/errors'; +import { TaskSpec, TaskSpecV1beta2 } from '@backstage/plugin-scaffolder-common'; type Options = { workingDirectory: string; @@ -225,19 +220,12 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner { input: JSON.stringify(input, null, 2), }); - if (!task.spec.metadata) { - console.warn( - 'DEPRECATION NOTICE: metadata is undefined. metadata will be required in the future.', - ); - } - await action.handler({ + // deprecated in favor of templateInfo.baseUrl baseUrl: task.spec.baseUrl, logger: taskLogger, logStream: stream, input, - // this token is deprecated, and will be removed in favour of secrets.backstageToken instead. - token: task.secrets?.token, secrets: task.secrets ?? {}, workspacePath, async createTemporaryDirectory() { @@ -250,7 +238,9 @@ export class HandlebarsWorkflowRunner implements WorkflowRunner { output(name: string, value: JsonValue) { stepOutputs[name] = value; }, + // deprecated in favor of templateInfo metadata: task.spec.metadata, + templateInfo: task.spec.templateInfo, }); // Remove all temporary directories that were created when executing the action diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts index 1597c575ac..1b635b695c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts @@ -22,7 +22,8 @@ import { NunjucksWorkflowRunner } from './NunjucksWorkflowRunner'; import { TemplateActionRegistry } from '../actions'; import { ScmIntegrations } from '@backstage/integration'; import { ConfigReader } from '@backstage/config'; -import { TaskContext, TaskSpec, TaskSecrets } from './types'; +import { TaskContext, TaskSecrets } from './types'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; const realFiles = Object.fromEntries( [ @@ -163,7 +164,7 @@ describe('DefaultWorkflowRunner', () => { }); it('should pass metadata through', async () => { - const templateName = 'template name'; + const entityRef = `template:default/templateName`; const task = createMockTaskWithSpec({ apiVersion: 'scaffolder.backstage.io/v1beta3', parameters: {}, @@ -176,13 +177,13 @@ describe('DefaultWorkflowRunner', () => { input: { foo: 1 }, }, ], - metadata: { name: templateName }, + templateInfo: { entityRef }, }); await runner.execute(task); - expect(fakeActionHandler.mock.calls[0][0].metadata).toEqual({ - name: templateName, + expect(fakeActionHandler.mock.calls[0][0].templateInfo).toEqual({ + entityRef, }); }); @@ -203,13 +204,15 @@ describe('DefaultWorkflowRunner', () => { ], }, { - token: fakeToken, + backstageToken: fakeToken, }, ); await runner.execute(task); - expect(fakeActionHandler.mock.calls[0][0].token).toEqual(fakeToken); + expect(fakeActionHandler.mock.calls[0][0].secrets).toEqual( + expect.objectContaining({ backstageToken: fakeToken }), + ); }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 889e94d000..fb9da56c22 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -15,14 +15,7 @@ */ import { ScmIntegrations } from '@backstage/integration'; -import { - TaskContext, - TaskSpec, - TaskSpecV1beta3, - TaskStep, - WorkflowResponse, - WorkflowRunner, -} from './types'; +import { TaskContext, WorkflowResponse, WorkflowRunner } from './types'; import * as winston from 'winston'; import fs from 'fs-extra'; import path from 'path'; @@ -39,6 +32,11 @@ import { SecureTemplater, SecureTemplateRenderer, } from '../../lib/templating/SecureTemplater'; +import { + TaskSpec, + TaskSpecV1beta3, + TaskStep, +} from '@backstage/plugin-scaffolder-common'; type NunjucksWorkflowRunnerOptions = { workingDirectory: string; @@ -258,17 +256,10 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const tmpDirs = new Array(); const stepOutput: { [outputName: string]: JsonValue } = {}; - if (!task.spec.metadata) { - console.warn( - 'DEPRECATION NOTICE: metadata is undefined. metadata will be required in the future.', - ); - } - await action.handler({ + // deprecated in favourof templateInfo.baseUrl baseUrl: task.spec.baseUrl, input, - // this token is deprecated, and will be removed in favour of secrets.backstageToken instead. - token: task.secrets?.token, secrets: task.secrets ?? {}, logger: taskLogger, logStream: streamLogger, @@ -283,7 +274,9 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { output(name: string, value: JsonValue) { stepOutput[name] = value; }, + // deprecated in favour of templateInfo metadata: task.spec.metadata, + templateInfo: task.spec.templateInfo, }); // Remove all temporary directories that were created when executing the action diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 130f6ced13..52fd175dc8 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -16,9 +16,10 @@ import { getVoidLogger, DatabaseManager } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { DatabaseTaskStore } from './DatabaseTaskStore'; import { StorageTaskBroker, TaskManager } from './StorageTaskBroker'; -import { TaskSecrets, TaskSpec, SerializedTaskEvent } from './types'; +import { TaskSecrets, SerializedTaskEvent } from './types'; async function createStore(): Promise { const manager = DatabaseManager.fromConfig( @@ -38,7 +39,7 @@ async function createStore(): Promise { describe('StorageTaskBroker', () => { let storage: DatabaseTaskStore; - const fakeSecrets = { token: 'secret' } as TaskSecrets; + const fakeSecrets = { backstageToken: 'secret' } as TaskSecrets; beforeAll(async () => { storage = await createStore(); @@ -47,7 +48,7 @@ describe('StorageTaskBroker', () => { const logger = getVoidLogger(); it('should claim a dispatched work item', async () => { const broker = new StorageTaskBroker(storage, logger); - await broker.dispatch({} as TaskSpec); + await broker.dispatch({ spec: {} as TaskSpec }); await expect(broker.claim()).resolves.toEqual(expect.any(TaskManager)); }); @@ -57,15 +58,15 @@ describe('StorageTaskBroker', () => { await expect(Promise.race([promise, 'waiting'])).resolves.toBe('waiting'); - await broker.dispatch({} as TaskSpec); + await broker.dispatch({ spec: {} as TaskSpec }); await expect(promise).resolves.toEqual(expect.any(TaskManager)); }); it('should dispatch multiple items and claim them in order', async () => { const broker = new StorageTaskBroker(storage, logger); - await broker.dispatch({ steps: [{ id: 'a' }] } as TaskSpec); - await broker.dispatch({ steps: [{ id: 'b' }] } as TaskSpec); - await broker.dispatch({ steps: [{ id: 'c' }] } as TaskSpec); + await broker.dispatch({ spec: { steps: [{ id: 'a' }] } as TaskSpec }); + await broker.dispatch({ spec: { steps: [{ id: 'b' }] } as TaskSpec }); + await broker.dispatch({ spec: { steps: [{ id: 'c' }] } as TaskSpec }); const taskA = await broker.claim(); const taskB = await broker.claim(); @@ -80,14 +81,14 @@ describe('StorageTaskBroker', () => { it('should store secrets', async () => { const broker = new StorageTaskBroker(storage, logger); - await broker.dispatch({} as TaskSpec, fakeSecrets); + await broker.dispatch({ spec: {} as TaskSpec, secrets: fakeSecrets }); const task = await broker.claim(); expect(task.secrets).toEqual(fakeSecrets); }, 10000); it('should complete a task', async () => { const broker = new StorageTaskBroker(storage, logger); - const dispatchResult = await broker.dispatch({} as TaskSpec); + const dispatchResult = await broker.dispatch({ spec: {} as TaskSpec }); const task = await broker.claim(); await task.complete('completed'); const taskRow = await storage.getTask(dispatchResult.taskId); @@ -96,7 +97,10 @@ describe('StorageTaskBroker', () => { it('should remove secrets after picking up a task', async () => { const broker = new StorageTaskBroker(storage, logger); - const dispatchResult = await broker.dispatch({} as TaskSpec, fakeSecrets); + const dispatchResult = await broker.dispatch({ + spec: {} as TaskSpec, + secrets: fakeSecrets, + }); await broker.claim(); const taskRow = await storage.getTask(dispatchResult.taskId); @@ -105,7 +109,7 @@ describe('StorageTaskBroker', () => { it('should fail a task', async () => { const broker = new StorageTaskBroker(storage, logger); - const dispatchResult = await broker.dispatch({} as TaskSpec); + const dispatchResult = await broker.dispatch({ spec: {} as TaskSpec }); const task = await broker.claim(); await task.complete('failed'); const taskRow = await storage.getTask(dispatchResult.taskId); @@ -116,12 +120,12 @@ describe('StorageTaskBroker', () => { const broker1 = new StorageTaskBroker(storage, logger); const broker2 = new StorageTaskBroker(storage, logger); - const { taskId } = await broker1.dispatch({} as TaskSpec); + const { taskId } = await broker1.dispatch({ spec: {} as TaskSpec }); const logPromise = new Promise(resolve => { const observedEvents = new Array(); - broker2.observe({ taskId, after: undefined }, (_err, { events }) => { + broker2.event$({ taskId, after: undefined }).subscribe(({ events }) => { observedEvents.push(...events); if (events.some(e => e.type === 'completion')) { resolve(observedEvents); @@ -143,9 +147,11 @@ describe('StorageTaskBroker', () => { ]); const afterLogs = await new Promise(resolve => { - broker2.observe({ taskId, after: logs[1].id }, (_err, { events }) => - resolve(events.map(e => e.body.message as string)), - ); + broker2 + .event$({ taskId, after: logs[1].id }) + .subscribe(({ events }) => + resolve(events.map(e => e.body.message as string)), + ); }); expect(afterLogs).toEqual([ 'log 3', @@ -155,7 +161,7 @@ describe('StorageTaskBroker', () => { it('should heartbeat', async () => { const broker = new StorageTaskBroker(storage, logger); - const { taskId } = await broker.dispatch({} as TaskSpec); + const { taskId } = await broker.dispatch({ spec: {} as TaskSpec }); const task = await broker.claim(); const initialTask = await storage.getTask(taskId); @@ -173,7 +179,7 @@ describe('StorageTaskBroker', () => { it('should be update the status to failed if heartbeat fails', async () => { const broker = new StorageTaskBroker(storage, logger); - const { taskId } = await broker.dispatch({} as TaskSpec); + const { taskId } = await broker.dispatch({ spec: {} as TaskSpec }); const task = await broker.claim(); jest diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts index 7e7ebe5436..0fd593f2f3 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts @@ -13,20 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JsonObject } from '@backstage/types'; -import { assertError } from '@backstage/errors'; +import { JsonObject, Observable } from '@backstage/types'; +import ObservableImpl from 'zen-observable'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; import { Logger } from 'winston'; import { - CompletedTaskState, + TaskCompletionState, TaskContext, TaskSecrets, - TaskSpec, TaskStore, TaskBroker, - DispatchResult, SerializedTaskEvent, SerializedTask, } from './types'; +import { TaskBrokerDispatchOptions } from '.'; /** * TaskManager @@ -38,48 +38,48 @@ export class TaskManager implements TaskContext { private heartbeatTimeoutId?: ReturnType; - static create(state: TaskState, storage: TaskStore, logger: Logger) { - const agent = new TaskManager(state, storage, logger); + static create(task: CurrentClaimedTask, storage: TaskStore, logger: Logger) { + const agent = new TaskManager(task, storage, logger); agent.startTimeout(); return agent; } // Runs heartbeat internally private constructor( - private readonly state: TaskState, + private readonly task: CurrentClaimedTask, private readonly storage: TaskStore, private readonly logger: Logger, ) {} get spec() { - return this.state.spec; + return this.task.spec; } get secrets() { - return this.state.secrets; + return this.task.secrets; } async getWorkspaceName() { - return this.state.taskId; + return this.task.taskId; } get done() { return this.isDone; } - async emitLog(message: string, metadata?: JsonObject): Promise { + async emitLog(message: string, logMetadata?: JsonObject): Promise { await this.storage.emitLogEvent({ - taskId: this.state.taskId, - body: { message, ...metadata }, + taskId: this.task.taskId, + body: { message, ...logMetadata }, }); } async complete( - result: CompletedTaskState, + result: TaskCompletionState, metadata?: JsonObject, ): Promise { await this.storage.completeTask({ - taskId: this.state.taskId, + taskId: this.task.taskId, status: result === 'failed' ? 'failed' : 'completed', eventBody: { message: `Run completed with status: ${result}`, @@ -95,13 +95,13 @@ export class TaskManager implements TaskContext { private startTimeout() { this.heartbeatTimeoutId = setTimeout(async () => { try { - await this.storage.heartbeatTask(this.state.taskId); + await this.storage.heartbeatTask(this.task.taskId); this.startTimeout(); } catch (error) { this.isDone = true; this.logger.error( - `Heartbeat for task ${this.state.taskId} failed`, + `Heartbeat for task ${this.task.taskId} failed`, error, ); } @@ -110,16 +110,24 @@ export class TaskManager implements TaskContext { } /** - * TaskState + * Stores the state of the current claimed task passed to the TaskContext * * @public */ -export interface TaskState { +export interface CurrentClaimedTask { spec: TaskSpec; taskId: string; secrets?: TaskSecrets; } +/** + * TaskState + * + * @public + * @deprecated use CurrentClaimedTask instead + */ +export type TaskState = CurrentClaimedTask; + function defer() { let resolve = () => {}; const promise = new Promise(_resolve => { @@ -155,10 +163,9 @@ export class StorageTaskBroker implements TaskBroker { } async dispatch( - spec: TaskSpec, - secrets?: TaskSecrets, - ): Promise { - const taskRow = await this.storage.createTask(spec, secrets); + options: TaskBrokerDispatchOptions, + ): Promise<{ taskId: string }> { + const taskRow = await this.storage.createTask(options); this.signalDispatch(); return { taskId: taskRow.taskId, @@ -169,47 +176,37 @@ export class StorageTaskBroker implements TaskBroker { return this.storage.getTask(taskId); } - observe( - options: { - taskId: string; - after: number | undefined; - }, - callback: ( - error: Error | undefined, - result: { events: SerializedTaskEvent[] }, - ) => void, - ): { unsubscribe: () => void } { - const { taskId } = options; + event$(options: { + taskId: string; + after?: number; + }): Observable<{ events: SerializedTaskEvent[] }> { + return new ObservableImpl(observer => { + const { taskId } = options; - let cancelled = false; - const unsubscribe = () => { - cancelled = true; - }; - - (async () => { let after = options.after; - while (!cancelled) { - const result = await this.storage.listEvents({ taskId, after: after }); - const { events } = result; - if (events.length) { - after = events[events.length - 1].id; - try { - callback(undefined, result); - } catch (error) { - assertError(error); - callback(error, { events: [] }); + let cancelled = false; + + (async () => { + while (!cancelled) { + const result = await this.storage.listEvents({ taskId, after }); + const { events } = result; + if (events.length) { + after = events[events.length - 1].id; + observer.next(result); } + + await new Promise(resolve => setTimeout(resolve, 1000)); } + })(); - await new Promise(resolve => setTimeout(resolve, 1000)); - } - })(); - - return { unsubscribe }; + return () => { + cancelled = true; + }; + }); } - async vacuumTasks(timeoutS: { timeoutS: number }): Promise { - const { tasks } = await this.storage.listStaleTasks(timeoutS); + async vacuumTasks(options: { timeoutS: number }): Promise { + const { tasks } = await this.storage.listStaleTasks(options); await Promise.all( tasks.map(async task => { try { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts index 9b6baf3b84..9ff6b9c8a1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.test.ts @@ -91,12 +91,14 @@ describe('TaskWorker', () => { }); await broker.dispatch({ - apiVersion: 'backstage.io/v1beta2', - steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], - output: { - result: '{{ steps.test.output.testOutput }}', + spec: { + apiVersion: 'backstage.io/v1beta2', + steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + values: {}, }, - values: {}, }); const task = await broker.claim(); @@ -122,12 +124,14 @@ describe('TaskWorker', () => { }); await broker.dispatch({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], - output: { - result: '{{ steps.test.output.testOutput }}', + spec: { + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + parameters: {}, }, - parameters: {}, }); const task = await broker.claim(); @@ -151,12 +155,14 @@ describe('TaskWorker', () => { }); const { taskId } = await broker.dispatch({ - apiVersion: 'scaffolder.backstage.io/v1beta3', - steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], - output: { - result: '{{ steps.test.output.testOutput }}', + spec: { + apiVersion: 'scaffolder.backstage.io/v1beta3', + steps: [{ id: 'test', name: 'test', action: 'not-found-action' }], + output: { + result: '{{ steps.test.output.testOutput }}', + }, + parameters: {}, }, - parameters: {}, }); const task = await broker.claim(); diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts index f563a08df6..b108a3308d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/index.ts @@ -14,25 +14,28 @@ * limitations under the License. */ export { DatabaseTaskStore } from './DatabaseTaskStore'; +export type { DatabaseTaskStoreOptions } from './DatabaseTaskStore'; export { TaskManager } from './StorageTaskBroker'; -export type { TaskState } from './StorageTaskBroker'; +export type { CurrentClaimedTask, TaskState } from './StorageTaskBroker'; export { TaskWorker } from './TaskWorker'; export type { CreateWorkerOptions } from './TaskWorker'; export type { TaskSecrets, - TaskSpec, + TaskCompletionState, CompletedTaskState, TaskStoreEmitOptions, TaskStoreListEventsOptions, SerializedTask, SerializedTaskEvent, - TaskSpecV1beta2, - TaskSpecV1beta3, Status, + TaskStatus, TaskEventType, TaskBroker, TaskContext, TaskStore, DispatchResult, - TemplateMetadata, + TaskBrokerDispatchResult, + TaskBrokerDispatchOptions, + TaskStoreCreateTaskOptions, + TaskStoreCreateTaskResult, } from './types'; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index 8caee87f57..57b92e0222 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -14,29 +14,15 @@ * limitations under the License. */ -import { JsonValue, JsonObject } from '@backstage/types'; -import { - TaskSpec, - TaskStep, - TemplateMetadata, - TaskSpecV1beta2, - TaskSpecV1beta3, -} from '@backstage/plugin-scaffolder-common'; - -export type { - TaskSpec, - TaskStep, - TemplateMetadata, - TaskSpecV1beta2, - TaskSpecV1beta3, -}; +import { JsonValue, JsonObject, Observable } from '@backstage/types'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; /** - * Status + * The status of each step of the Task * * @public */ -export type Status = +export type TaskStatus = | 'open' | 'processing' | 'failed' @@ -44,11 +30,27 @@ export type Status = | 'completed'; /** - * CompletedTaskState + * The status of each step of the Task + * + * @public + * @deprecated use TaskStatus instead + */ +export type Status = TaskStatus; + +/** + * The state of a completed task. * * @public */ -export type CompletedTaskState = 'failed' | 'completed'; +export type TaskCompletionState = 'failed' | 'completed'; + +/** + * The state of a completed task. + * + * @public + * @deprecated use TaskCompletionState instead + */ +export type CompletedTaskState = TaskCompletionState; /** * SerializedTask @@ -58,7 +60,7 @@ export type CompletedTaskState = 'failed' | 'completed'; export type SerializedTask = { id: string; spec: TaskSpec; - status: Status; + status: TaskStatus; createdAt: string; lastHeartbeatAt?: string; secrets?: TaskSecrets; @@ -90,19 +92,36 @@ export type SerializedTaskEvent = { * @public */ export type TaskSecrets = Record & { - /** @deprecated Use `backstageToken` instead */ - token?: string; backstageToken?: string; }; +/** + * The result of {@link TaskBroker.dispatch} + * + * @public + */ +export type TaskBrokerDispatchResult = { + taskId: string; +}; + +/** + * The options passed to {@link TaskBroker.dispatch} + * Currently a spec and optional secrets + * + * @public + */ +export type TaskBrokerDispatchOptions = { + spec: TaskSpec; + secrets?: TaskSecrets; +}; + /** * DispatchResult * * @public + * @deprecated use TaskBrokerDispatchResult instead */ -export type DispatchResult = { - taskId: string; -}; +export type DispatchResult = TaskBrokerDispatchResult; /** * Task @@ -113,8 +132,8 @@ export interface TaskContext { spec: TaskSpec; secrets?: TaskSecrets; done: boolean; - emitLog(message: string, metadata?: JsonValue): Promise; - complete(result: CompletedTaskState, metadata?: JsonValue): Promise; + emitLog(message: string, logMetadata?: JsonObject): Promise; + complete(result: TaskCompletionState, metadata?: JsonObject): Promise; getWorkspaceName(): Promise; } @@ -125,18 +144,14 @@ export interface TaskContext { */ export interface TaskBroker { claim(): Promise; - dispatch(spec: TaskSpec, secrets?: TaskSecrets): Promise; - vacuumTasks(timeoutS: { timeoutS: number }): Promise; - observe( - options: { - taskId: string; - after: number | undefined; - }, - callback: ( - error: Error | undefined, - result: { events: SerializedTaskEvent[] }, - ) => void, - ): { unsubscribe: () => void }; + dispatch( + options: TaskBrokerDispatchOptions, + ): Promise; + vacuumTasks(options: { timeoutS: number }): Promise; + event$(options: { + taskId: string; + after: number | undefined; + }): Observable<{ events: SerializedTaskEvent[] }>; get(taskId: string): Promise; } @@ -145,9 +160,9 @@ export interface TaskBroker { * * @public */ -export type TaskStoreEmitOptions = { +export type TaskStoreEmitOptions = { taskId: string; - body: JsonObject; + body: TBody; }; /** @@ -160,6 +175,23 @@ export type TaskStoreListEventsOptions = { after?: number | undefined; }; +/** + * The options passed to {@link TaskStore.createTask} + * @public + */ +export type TaskStoreCreateTaskOptions = { + spec: TaskSpec; + secrets?: TaskSecrets; +}; + +/** + * The response from {@link TaskStore.createTask} + * @public + */ +export type TaskStoreCreateTaskResult = { + taskId: string; +}; + /** * TaskStore * @@ -167,14 +199,13 @@ export type TaskStoreListEventsOptions = { */ export interface TaskStore { createTask( - task: TaskSpec, - secrets?: TaskSecrets, - ): Promise<{ taskId: string }>; + options: TaskStoreCreateTaskOptions, + ): Promise; getTask(taskId: string): Promise; claimTask(): Promise; completeTask(options: { taskId: string; - status: Status; + status: TaskStatus; eventBody: JsonObject; }): Promise; heartbeatTask(taskId: string): Promise; diff --git a/plugins/scaffolder-backend/src/service/helpers.ts b/plugins/scaffolder-backend/src/service/helpers.ts index f86b1663a7..e49c28cb74 100644 --- a/plugins/scaffolder-backend/src/service/helpers.ts +++ b/plugins/scaffolder-backend/src/service/helpers.ts @@ -14,14 +14,21 @@ * limitations under the License. */ +import { CatalogApi } from '@backstage/catalog-client'; import { Entity, ANNOTATION_LOCATION, parseLocationRef, ANNOTATION_SOURCE_LOCATION, + EntityName, + DEFAULT_NAMESPACE, } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; -import { assertError } from '@backstage/errors'; +import { assertError, InputError, NotFoundError } from '@backstage/errors'; +import { + TemplateEntityV1beta2, + TemplateEntityV1beta3, +} from '@backstage/plugin-scaffolder-common'; import fs from 'fs-extra'; import os from 'os'; import { Logger } from 'winston'; @@ -78,3 +85,31 @@ export function getEntityBaseUrl(entity: Entity): string | undefined { // what the url is pointing to makes sense to use as a baseUrl return undefined; } + +/** + * Will use the provided CatalogApi to go find the given template entity with an additional token. + * Returns the matching template, or throws a NotFoundError if no such template existed. + */ +export async function findTemplate(options: { + entityRef: EntityName; + token?: string; + catalogApi: CatalogApi; +}): Promise { + const { entityRef, token, catalogApi } = options; + + if (entityRef.namespace.toLocaleLowerCase('en-US') !== DEFAULT_NAMESPACE) { + throw new InputError( + `Invalid namespace, only '${DEFAULT_NAMESPACE}' namespace is supported`, + ); + } + if (entityRef.kind.toLocaleLowerCase('en-US') !== 'template') { + throw new InputError(`Invalid kind, only 'Template' kind is supported`); + } + + const template = await catalogApi.getEntityByName(entityRef, { token }); + if (!template) { + throw new NotFoundError(`Template ${entityRef} not found`); + } + + return template as TemplateEntityV1beta3 | TemplateEntityV1beta2; +} diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index d8ba15279f..fa1b649231 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -36,8 +36,9 @@ import { UrlReaders, } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; -import { TemplateEntityV1beta2 } from '@backstage/catalog-model'; +import { TemplateEntityV1beta2 } from '@backstage/plugin-scaffolder-common'; import { ConfigReader } from '@backstage/config'; +import ObservableImpl from 'zen-observable'; import express from 'express'; import request from 'supertest'; /** @@ -48,11 +49,12 @@ import request from 'supertest'; */ import { createRouter, DatabaseTaskStore, TaskBroker } from '../index'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; +import { stringifyEntityRef } from '@backstage/catalog-model'; -const createCatalogClient = (templates: any[] = []) => +const createCatalogClient = (template: any) => ({ - getEntities: async () => ({ items: templates }), - } as CatalogApi); + getEntityByName: async () => template, + } as unknown as CatalogApi); function createDatabase(): PluginDatabaseManager { return DatabaseManager.fromConfig( @@ -113,13 +115,13 @@ describe('createRouter', () => { jest.spyOn(taskBroker, 'dispatch'); jest.spyOn(taskBroker, 'get'); - jest.spyOn(taskBroker, 'observe'); + jest.spyOn(taskBroker, 'event$'); const router = await createRouter({ logger: getVoidLogger(), config: new ConfigReader({}), database: createDatabase(), - catalogClient: createCatalogClient([template]), + catalogClient: createCatalogClient(template), containerRunner: new DockerContainerRunner({} as any), reader: mockUrlReader, taskBroker, @@ -145,7 +147,10 @@ describe('createRouter', () => { const response = await request(app) .post('/v2/tasks') .send({ - templateName: 'create-react-app-template', + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), values: { storePath: 'https://github.com/backstage/backstage', }, @@ -164,7 +169,10 @@ describe('createRouter', () => { const response = await request(app) .post('/v2/tasks') .send({ - templateName: 'create-react-app-template', + templateRef: stringifyEntityRef({ + kind: 'template', + name: 'create-react-app-template', + }), values: { required: 'required-value', }, @@ -182,7 +190,7 @@ describe('createRouter', () => { spec: {} as any, status: 'completed', createdAt: '', - secrets: { token: 'secret' }, + secrets: { backstageToken: 'secret' }, }); const response = await request(app).get(`/v2/tasks/a-random-id`); @@ -194,37 +202,38 @@ describe('createRouter', () => { describe('GET /v2/tasks/:taskId/eventstream', () => { it('should return log messages', async () => { - const unsubscribe = jest.fn(); + let subscriber: ZenObservable.SubscriptionObserver; ( - taskBroker.observe as jest.Mocked['observe'] - ).mockImplementation(({ taskId }, callback) => { - // emit after this function returned - setImmediate(() => { - callback(undefined, { - events: [ - { - id: 0, - taskId, - type: 'log', - createdAt: '', - body: { message: 'My log message' }, - }, - ], - }); - callback(undefined, { - events: [ - { - id: 1, - taskId, - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ], + taskBroker.event$ as jest.Mocked['event$'] + ).mockImplementation(({ taskId }) => { + return new ObservableImpl(observer => { + subscriber = observer; + setImmediate(() => { + observer.next({ + events: [ + { + id: 0, + taskId, + type: 'log', + createdAt: '', + body: { message: 'My log message' }, + }, + ], + }); + observer.next({ + events: [ + { + id: 1, + taskId, + type: 'completion', + createdAt: '', + body: { message: 'Finished!' }, + }, + ], + }); }); }); - - return { unsubscribe }; + // emit after this function returned }); let statusCode: any = undefined; @@ -264,34 +273,32 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ `); - expect(taskBroker.observe).toBeCalledTimes(1); - expect(taskBroker.observe).toBeCalledWith( - { taskId: 'a-random-id' }, - expect.any(Function), - ); - - expect(unsubscribe).toBeCalledTimes(1); + expect(taskBroker.event$).toBeCalledTimes(1); + expect(taskBroker.event$).toBeCalledWith({ taskId: 'a-random-id' }); + expect(subscriber!.closed).toBe(true); }); it('should return log messages with after query', async () => { - const unsubscribe = jest.fn(); + let subscriber: ZenObservable.SubscriptionObserver; ( - taskBroker.observe as jest.Mocked['observe'] - ).mockImplementation(({ taskId }, callback) => { - setImmediate(() => { - callback(undefined, { - events: [ - { - id: 1, - taskId, - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ], + taskBroker.event$ as jest.Mocked['event$'] + ).mockImplementation(({ taskId }) => { + return new ObservableImpl(observer => { + subscriber = observer; + setImmediate(() => { + observer.next({ + events: [ + { + id: 1, + taskId, + type: 'completion', + createdAt: '', + body: { message: 'Finished!' }, + }, + ], + }); }); }); - return { unsubscribe }; }); let statusCode: any = undefined; @@ -318,41 +325,43 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ expect(statusCode).toBe(200); expect(headers['content-type']).toBe('text/event-stream'); - expect(taskBroker.observe).toBeCalledTimes(1); - expect(taskBroker.observe).toBeCalledWith( - { taskId: 'a-random-id', after: 10 }, - expect.any(Function), - ); + expect(taskBroker.event$).toBeCalledTimes(1); + expect(taskBroker.event$).toBeCalledWith({ + taskId: 'a-random-id', + after: 10, + }); - expect(unsubscribe).toBeCalledTimes(1); + expect(subscriber!.closed).toBe(true); }); }); describe('GET /v2/tasks/:taskId/events', () => { it('should return log messages', async () => { - const unsubscribe = jest.fn(); + let subscriber: ZenObservable.SubscriptionObserver; ( - taskBroker.observe as jest.Mocked['observe'] - ).mockImplementation(({ taskId }, callback) => { - callback(undefined, { - events: [ - { - id: 0, - taskId, - type: 'log', - createdAt: '', - body: { message: 'My log message' }, - }, - { - id: 1, - taskId, - type: 'completion', - createdAt: '', - body: { message: 'Finished!' }, - }, - ], + taskBroker.event$ as jest.Mocked['event$'] + ).mockImplementation(({ taskId }) => { + return new ObservableImpl(observer => { + subscriber = observer; + observer.next({ + events: [ + { + id: 0, + taskId, + type: 'log', + createdAt: '', + body: { message: 'My log message' }, + }, + { + id: 1, + taskId, + type: 'completion', + createdAt: '', + body: { message: 'Finished!' }, + }, + ], + }); }); - return { unsubscribe }; }); const response = await request(app).get('/v2/tasks/a-random-id/events'); @@ -375,21 +384,20 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ }, ]); - expect(taskBroker.observe).toBeCalledTimes(1); - expect(taskBroker.observe).toBeCalledWith( - { taskId: 'a-random-id' }, - expect.any(Function), - ); - expect(unsubscribe).toBeCalledTimes(1); + expect(taskBroker.event$).toBeCalledTimes(1); + expect(taskBroker.event$).toBeCalledWith({ taskId: 'a-random-id' }); + expect(subscriber!.closed).toBe(true); }); it('should return log messages with after query', async () => { - const unsubscribe = jest.fn(); + let subscriber: ZenObservable.SubscriptionObserver; ( - taskBroker.observe as jest.Mocked['observe'] - ).mockImplementation((_, callback) => { - callback(undefined, { events: [] }); - return { unsubscribe }; + taskBroker.event$ as jest.Mocked['event$'] + ).mockImplementation(() => { + return new ObservableImpl(observer => { + subscriber = observer; + observer.next({ events: [] }); + }); }); const response = await request(app) @@ -399,12 +407,12 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ expect(response.status).toEqual(200); expect(response.body).toEqual([]); - expect(taskBroker.observe).toBeCalledTimes(1); - expect(taskBroker.observe).toBeCalledWith( - { taskId: 'a-random-id', after: 10 }, - expect.any(Function), - ); - expect(unsubscribe).toBeCalledTimes(1); + expect(taskBroker.event$).toBeCalledTimes(1); + expect(taskBroker.event$).toBeCalledWith({ + taskId: 'a-random-id', + after: 10, + }); + expect(subscriber!.closed).toBe(true); }); }); }); diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 85ed8a60ab..8f793a34d8 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -20,27 +20,33 @@ import { UrlReader, } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; -import { Entity, TemplateEntityV1beta2 } from '@backstage/catalog-model'; +import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; import { Config } from '@backstage/config'; import { InputError, NotFoundError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; -import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { + TemplateEntityV1beta2, + TemplateEntityV1beta3, + TaskSpecV1beta3, + TaskSpec, + TaskSpecV1beta2, +} from '@backstage/plugin-scaffolder-common'; import express from 'express'; import Router from 'express-promise-router'; import { validate } from 'jsonschema'; import { Logger } from 'winston'; -import { CatalogEntityClient, TemplateFilter } from '../lib'; +import { TemplateFilter } from '../lib'; import { createBuiltinActions, DatabaseTaskStore, TaskBroker, - TaskSpec, TaskWorker, TemplateAction, TemplateActionRegistry, } from '../scaffolder'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; -import { getEntityBaseUrl, getWorkingDirectory } from './helpers'; +import { getEntityBaseUrl, getWorkingDirectory, findTemplate } from './helpers'; /** * RouterOptions @@ -69,6 +75,7 @@ function isSupportedTemplate( ); } +/** @public */ export async function createRouter( options: RouterOptions, ): Promise { @@ -89,7 +96,6 @@ export async function createRouter( const logger = parentLogger.child({ plugin: 'scaffolder' }); const workingDirectory = await getWorkingDirectory(config, logger); - const entityClient = new CatalogEntityClient(catalogClient); const integrations = ScmIntegrations.fromConfig(config); let taskBroker: TaskBroker; @@ -136,19 +142,9 @@ export async function createRouter( '/v2/templates/:namespace/:kind/:name/parameter-schema', async (req, res) => { const { namespace, kind, name } = req.params; - - if (namespace !== 'default') { - throw new InputError( - `Invalid namespace, only 'default' namespace is supported`, - ); - } - if (kind.toLowerCase() !== 'template') { - throw new InputError( - `Invalid kind, only 'Template' kind is supported`, - ); - } - - const template = await entityClient.findTemplate(name, { + const template = await findTemplate({ + catalogApi: catalogClient, + entityRef: { kind, namespace, name }, token: getBearerToken(req.headers.authorization), }); if (isSupportedTemplate(template)) { @@ -180,19 +176,33 @@ export async function createRouter( res.json(actionsList); }) .post('/v2/tasks', async (req, res) => { - const templateName: string = req.body.templateName; + const templateRef: string = req.body.templateRef; + const { kind, namespace, name } = parseEntityRef(templateRef, { + defaultKind: 'template', + }); const values = req.body.values; const token = getBearerToken(req.headers.authorization); - const template = await entityClient.findTemplate(templateName, { - token, + const template = await findTemplate({ + catalogApi: catalogClient, + entityRef: { kind, namespace, name }, + token: getBearerToken(req.headers.authorization), }); let taskSpec: TaskSpec; if (isSupportedTemplate(template)) { + if (template.apiVersion === 'backstage.io/v1beta2') { + logger.warn( + `Scaffolding ${stringifyEntityRef( + template, + )} with deprecated apiVersion ${ + template.apiVersion + }. Please migrate the template to backstage.io/v1beta3. https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3`, + ); + } + for (const parameters of [template.spec.parameters ?? []].flat()) { const result = validate(values, parameters); - if (!result.valid) { res.status(400).json({ errors: result.errors }); return; @@ -201,32 +211,40 @@ export async function createRouter( const baseUrl = getEntityBaseUrl(template); + const baseTaskSpec = { + baseUrl, + steps: template.spec.steps.map((step, index) => ({ + ...step, + id: step.id ?? `step-${index + 1}`, + name: step.name ?? step.action, + })), + output: template.spec.output ?? {}, + + // deprecated in favour of templateInfo + metadata: { name: template.metadata?.name }, + + templateInfo: { + entityRef: stringifyEntityRef({ + kind, + namespace, + name: template.metadata?.name, + }), + baseUrl, + }, + }; + taskSpec = template.apiVersion === 'backstage.io/v1beta2' - ? { + ? ({ + ...baseTaskSpec, apiVersion: template.apiVersion, - baseUrl, values, - steps: template.spec.steps.map((step, index) => ({ - ...step, - id: step.id ?? `step-${index + 1}`, - name: step.name ?? step.action, - })), - output: template.spec.output ?? {}, - metadata: { name: template.metadata?.name }, - } - : { + } as TaskSpecV1beta2) + : ({ + ...baseTaskSpec, apiVersion: template.apiVersion, - baseUrl, parameters: values, - steps: template.spec.steps.map((step, index) => ({ - ...step, - id: step.id ?? `step-${index + 1}`, - name: step.name ?? step.action, - })), - output: template.spec.output ?? {}, - metadata: { name: template.metadata?.name }, - }; + } as TaskSpecV1beta3); } else { throw new InputError( `Unsupported apiVersion field in schema entity, ${ @@ -235,11 +253,12 @@ export async function createRouter( ); } - const result = await taskBroker.dispatch(taskSpec, { - ...req.body.secrets, - backstageToken: token, - // This is deprecated, but we need to support it for now if people are running their own task broker. - token: token, + const result = await taskBroker.dispatch({ + spec: taskSpec, + secrets: { + ...req.body.secrets, + backstageToken: token, + }, }); res.status(201).json({ id: result.taskId }); @@ -269,15 +288,13 @@ export async function createRouter( }); // After client opens connection send all events as string - const { unsubscribe } = taskBroker.observe( - { taskId, after }, - (error, { events }) => { - if (error) { - logger.error( - `Received error from event stream when observing taskId '${taskId}', ${error}`, - ); - } - + const subscription = taskBroker.event$({ taskId, after }).subscribe({ + error: error => { + logger.error( + `Received error from event stream when observing taskId '${taskId}', ${error}`, + ); + }, + next: ({ events }) => { let shouldUnsubscribe = false; for (const event of events) { res.write( @@ -285,19 +302,18 @@ export async function createRouter( ); if (event.type === 'completion') { shouldUnsubscribe = true; - // Closing the event stream here would cause the frontend - // to automatically reconnect because it lost connection. } } // res.flush() is only available with the compression middleware res.flush?.(); - if (shouldUnsubscribe) unsubscribe(); + if (shouldUnsubscribe) subscription.unsubscribe(); }, - ); + }); + // When client closes connection we update the clients list // avoiding the disconnected one req.on('close', () => { - unsubscribe(); + subscription.unsubscribe(); logger.debug(`Event stream observing taskId '${taskId}' closed`); }); }) @@ -305,36 +321,29 @@ export async function createRouter( const { taskId } = req.params; const after = Number(req.query.after) || undefined; - let unsubscribe = () => {}; - // cancel the request after 30 seconds. this aligns with the recommendations of RFC 6202. const timeout = setTimeout(() => { - unsubscribe(); res.json([]); }, 30_000); // Get all known events after an id (always includes the completion event) and return the first callback - ({ unsubscribe } = taskBroker.observe( - { taskId, after }, - (error, { events }) => { - // stop the timeout + const subscription = taskBroker.event$({ taskId, after }).subscribe({ + error: error => { + logger.error( + `Received error from event stream when observing taskId '${taskId}', ${error}`, + ); + }, + next: ({ events }) => { clearTimeout(timeout); - unsubscribe(); - - if (error) { - logger.error( - `Received error from log when observing taskId '${taskId}', ${error}`, - ); - } - + subscription.unsubscribe(); res.json(events); }, - )); + }); // When client closes connection we update the clients list // avoiding the disconnected one req.on('close', () => { - unsubscribe(); + subscription.unsubscribe(); clearTimeout(timeout); }); }); diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index fb161f6a4a..db702973fe 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-scaffolder-common +## 0.2.2 + +### Patch Changes + +- a4d53fe18e: **DEPRECATED** - The `TaskSpec.metadata` and `TaskSpec.baseUrl` has been deprecated in favour of the new `TaskSpec.templateInfo`. + The `baseUrl` is now found on the `templateInfo` object, and the name can be inferred from the `templateInfo.entityRef` property. + + Usages of `TaskSpec.metadata.name` or `ctx.metadata.name` in Actions should migrate to using `parseEntityRef(taskSpec.templateInfo.entityRef)` to get the parsed entity triplet. + + Usages of `ctx.baseUrl` in Actions should migrate to using `ctx.templateInfo.baseUrl` instead. + +- Updated dependencies + - @backstage/catalog-model@0.11.0 + +## 0.2.1 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/catalog-model@0.10.1 + - @backstage/types@0.1.3 + +## 0.2.0 + +### Minor Changes + +- 5e585bbc7f: **BREAKING**: Removed the `templateEntityV1beta3Schema` export + +### Patch Changes + +- e72d371296: Added `TemplateEntityV1beta2` which was moved here from + `@backstage/plugin-scaffolder-common`. It has also been marked as deprecated in + the process - please consider [migrating to `v1beta3` + templates](https://backstage.io/docs/features/software-templates/migrating-from-v1beta2-to-v1beta3). +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/catalog-model@0.10.0 + - @backstage/types@0.1.2 + ## 0.1.3 ### Patch Changes diff --git a/plugins/scaffolder-common/api-report.md b/plugins/scaffolder-common/api-report.md index 3bca8e74ed..1872aa3ed8 100644 --- a/plugins/scaffolder-common/api-report.md +++ b/plugins/scaffolder-common/api-report.md @@ -5,19 +5,19 @@ ```ts import { Entity } from '@backstage/catalog-model'; import { JsonObject } from '@backstage/types'; -import { JSONSchema } from '@backstage/catalog-model'; import { JsonValue } from '@backstage/types'; +import { KindValidator } from '@backstage/catalog-model'; // @public export type TaskSpec = TaskSpecV1beta2 | TaskSpecV1beta3; -// @public +// @public @deprecated export interface TaskSpecV1beta2 { // (undocumented) apiVersion: 'backstage.io/v1beta2'; - // (undocumented) + // @deprecated (undocumented) baseUrl?: string; - // (undocumented) + // @deprecated (undocumented) metadata?: TemplateMetadata; // (undocumented) output: { @@ -26,6 +26,8 @@ export interface TaskSpecV1beta2 { // (undocumented) steps: TaskStep[]; // (undocumented) + templateInfo?: TemplateInfo; + // (undocumented) values: JsonObject; } @@ -33,9 +35,9 @@ export interface TaskSpecV1beta2 { export interface TaskSpecV1beta3 { // (undocumented) apiVersion: 'scaffolder.backstage.io/v1beta3'; - // (undocumented) + // @deprecated (undocumented) baseUrl?: string; - // (undocumented) + // @deprecated (undocumented) metadata?: TemplateMetadata; // (undocumented) output: { @@ -45,6 +47,8 @@ export interface TaskSpecV1beta3 { parameters: JsonObject; // (undocumented) steps: TaskStep[]; + // (undocumented) + templateInfo?: TemplateInfo; } // @public @@ -61,7 +65,34 @@ export interface TaskStep { name: string; } -// @public (undocumented) +// @public @deprecated +export interface TemplateEntityV1beta2 extends Entity { + // (undocumented) + apiVersion: 'backstage.io/v1beta2'; + // (undocumented) + kind: 'Template'; + // (undocumented) + spec: { + type: string; + parameters?: JsonObject | JsonObject[]; + steps: Array<{ + id?: string; + name?: string; + action: string; + input?: JsonObject; + if?: string | boolean; + }>; + output?: { + [name: string]: string; + }; + owner?: string; + }; +} + +// @public @deprecated +export const templateEntityV1beta2Validator: KindValidator; + +// @public export interface TemplateEntityV1beta3 extends Entity { // (undocumented) apiVersion: 'scaffolder.backstage.io/v1beta3'; @@ -85,10 +116,16 @@ export interface TemplateEntityV1beta3 extends Entity { }; } -// @public (undocumented) -export const templateEntityV1beta3Schema: JSONSchema; +// @public +export const templateEntityV1beta3Validator: KindValidator; // @public +export type TemplateInfo = { + entityRef: string; + baseUrl?: string; +}; + +// @public @deprecated export type TemplateMetadata = { name: string; }; diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index 6b03a7c884..be48edd743 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-common", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", - "version": "0.1.3", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -12,6 +12,9 @@ "module": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -25,21 +28,21 @@ "dist" ], "scripts": { - "build": "backstage-cli build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "bugs": { "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/types": "^0.1.1" + "@backstage/catalog-model": "^0.11.0", + "@backstage/types": "^0.1.3" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0" + "@backstage/cli": "^0.14.1" } } diff --git a/plugins/scaffolder-common/src/TaskSpec.ts b/plugins/scaffolder-common/src/TaskSpec.ts index 8d8ccedbad..2c7eb26d5b 100644 --- a/plugins/scaffolder-common/src/TaskSpec.ts +++ b/plugins/scaffolder-common/src/TaskSpec.ts @@ -17,16 +17,29 @@ import { JsonValue, JsonObject } from '@backstage/types'; /** - * TemplateMetadata + * Metadata about the Template that was the originator of a scaffolder task, as + * stored in the database. * * @public + * @deprecated use templateInfo on the spec instead */ export type TemplateMetadata = { name: string; }; /** - * TaskStep + * Information about a template that is stored on a task specification. + * Includes a stringified entityRef, and the baseUrl which is usually the relative path of the template definition + * + * @public + */ +export type TemplateInfo = { + entityRef: string; + baseUrl?: string; +}; + +/** + * An individual step of a scaffolder task, as stored in the database. * * @public */ @@ -39,35 +52,45 @@ export interface TaskStep { } /** - * TaskSpecV1beta2 + * A scaffolder task as stored in the database, generated from a v1beta2 + * apiVersion Template. * * @public + * @deprecated Please convert your templates to TaskSpecV1beta3 on apiVersion + * scaffolder.backstage.io/v1beta3 */ export interface TaskSpecV1beta2 { apiVersion: 'backstage.io/v1beta2'; + /** @deprecated use templateInfo.baseUrl instead */ baseUrl?: string; values: JsonObject; steps: TaskStep[]; output: { [name: string]: string }; + /** @deprecated use templateInfo instead */ metadata?: TemplateMetadata; + templateInfo?: TemplateInfo; } /** - * TaskSpecV1beta3 + * A scaffolder task as stored in the database, generated from a v1beta3 + * apiVersion Template. * * @public */ export interface TaskSpecV1beta3 { apiVersion: 'scaffolder.backstage.io/v1beta3'; + /** @deprecated use templateInfo.baseUrl instead */ baseUrl?: string; parameters: JsonObject; steps: TaskStep[]; output: { [name: string]: JsonValue }; + /** @deprecated use templateInfo instead */ metadata?: TemplateMetadata; + templateInfo?: TemplateInfo; } /** - * TaskSpec + * A scaffolder task as stored in the database, generated from a Template. * * @public */ diff --git a/packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json b/plugins/scaffolder-common/src/Template.v1beta2.schema.json similarity index 100% rename from packages/catalog-model/src/schema/kinds/Template.v1beta2.schema.json rename to plugins/scaffolder-common/src/Template.v1beta2.schema.json diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.test.ts b/plugins/scaffolder-common/src/TemplateEntityV1beta2.test.ts similarity index 100% rename from packages/catalog-model/src/kinds/TemplateEntityV1beta2.test.ts rename to plugins/scaffolder-common/src/TemplateEntityV1beta2.test.ts diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts b/plugins/scaffolder-common/src/TemplateEntityV1beta2.ts similarity index 58% rename from packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts rename to plugins/scaffolder-common/src/TemplateEntityV1beta2.ts index f4c2dfb5bc..280caec52f 100644 --- a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts +++ b/plugins/scaffolder-common/src/TemplateEntityV1beta2.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import { + Entity, + entityKindSchemaValidator, + KindValidator, +} from '@backstage/catalog-model'; import { JsonObject } from '@backstage/types'; -import type { Entity } from '../entity/Entity'; -import schema from '../schema/kinds/Template.v1beta2.schema.json'; -import { ajvCompiledJsonSchemaValidator } from './util'; +import schema from './Template.v1beta2.schema.json'; /** - * Backstage catalog Template kind Entity. Templates are used by the Scaffolder plugin to create new Components. + * Backstage catalog Template kind Entity. Templates are used by the Scaffolder + * plugin to create new entities, such as Components. * * @public + * @deprecated Please convert your templates to TemplateEntityV1beta3 on + * apiVersion scaffolder.backstage.io/v1beta3 */ export interface TemplateEntityV1beta2 extends Entity { apiVersion: 'backstage.io/v1beta2'; @@ -42,10 +48,18 @@ export interface TemplateEntityV1beta2 extends Entity { }; } +const validator = entityKindSchemaValidator(schema); + /** - * {@link KindValidator} for {@link TemplateEntityV1beta2}. + * Entity data validator for {@link TemplateEntityV1beta2}. * * @public + * @deprecated Please convert your templates to TemplateEntityV1beta3 on + * apiVersion scaffolder.backstage.io/v1beta3 */ -export const templateEntityV1beta2Validator = - ajvCompiledJsonSchemaValidator(schema); +export const templateEntityV1beta2Validator: KindValidator = { + // TODO(freben): Emulate the old KindValidator until we fix that type + async check(data: Entity) { + return validator(data) === data; + }, +}; diff --git a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts index 2aefa5fd6b..13b699de69 100644 --- a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts +++ b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts @@ -14,10 +14,20 @@ * limitations under the License. */ +import { + Entity, + entityKindSchemaValidator, + KindValidator, +} from '@backstage/catalog-model'; import { JsonObject } from '@backstage/types'; -import { Entity } from '@backstage/catalog-model'; +import schema from './Template.v1beta3.schema.json'; -/** @public */ +/** + * Backstage catalog Template kind Entity. Templates are used by the Scaffolder + * plugin to create new entities, such as Components. + * + * @public + */ export interface TemplateEntityV1beta3 extends Entity { apiVersion: 'scaffolder.backstage.io/v1beta3'; kind: 'Template'; @@ -35,3 +45,17 @@ export interface TemplateEntityV1beta3 extends Entity { owner?: string; }; } + +const validator = entityKindSchemaValidator(schema); + +/** + * Entity data validator for {@link TemplateEntityV1beta3}. + * + * @public + */ +export const templateEntityV1beta3Validator: KindValidator = { + // TODO(freben): Emulate the old KindValidator until we fix that type + async check(data: Entity) { + return validator(data) === data; + }, +}; diff --git a/plugins/scaffolder-common/src/index.ts b/plugins/scaffolder-common/src/index.ts index 1197f48fed..e74a5f988f 100644 --- a/plugins/scaffolder-common/src/index.ts +++ b/plugins/scaffolder-common/src/index.ts @@ -20,15 +20,8 @@ * @packageDocumentation */ -import { JSONSchema } from '@backstage/catalog-model'; -import v1beta3Schema from './Template.v1beta3.schema.json'; - -export type { TemplateEntityV1beta3 } from './TemplateEntityV1beta3'; - -/** @public */ -export const templateEntityV1beta3Schema: JSONSchema = v1beta3Schema as Omit< - JSONSchema, - 'examples' ->; - export * from './TaskSpec'; +export { templateEntityV1beta2Validator } from './TemplateEntityV1beta2'; +export type { TemplateEntityV1beta2 } from './TemplateEntityV1beta2'; +export { templateEntityV1beta3Validator } from './TemplateEntityV1beta3'; +export type { TemplateEntityV1beta3 } from './TemplateEntityV1beta3'; diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index dd7674a21b..1d1cddfa31 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,101 @@ # @backstage/plugin-scaffolder +## 0.13.0 + +### Minor Changes + +- 50e0242ac2: - **BREAKING** - `scaffolderApi.scaffold()` now takes one `options` argument instead of 3, the existing arguments should just be wrapped up in one object instead. + - **BREAKING** - `scaffolderApi.scaffold()` now returns an object instead of a single string for the job ID. It's now `{ taskId: string }` + - **BREAKING** - `scaffolderApi.scaffold()` now takes a `templateRef` instead of `templateName` as an argument in the options. This should be a valid stringified `entityRef`. + - **BREAKING** - `scaffolderApi.getIntegrationsList` now returns an object `{ integrations: { type: string, title: string, host: string }[] }` instead of just an array. +- a2589000ee: - **BREAKING** - Removed the `plugin` export, use `scaffolderPlugin` instead. + - **BREAKING** - Removed the `TextValuePicker` component export, you can inline this component instead as it's a simple wrapper around a `TextField` from `@material-ui/core`. + +### Patch Changes + +- 67a7c02d26: Remove usages of `EntityRef` and `parseEntityName` from `@backstage/catalog-model` +- 6e1cbc12a6: Updated according to the new `getEntityFacets` catalog API method +- b776ce5aab: Replaced use of deprecated `useEntityListProvider` hook with `useEntityList`. +- 0f37cdef19: Migrated over from the deprecated `spec.metadata` to `spec.templateInfo` for the `name` and the `baseUrl` of the template. +- 50e0242ac2: - Moved the `JSONSchema` type from `@backstage/catalog-model` to `JSONSchema7`. + - Renamed and prefixed some types ready for exporting. +- a2589000ee: - Reworking the `FieldExtensionComponentType` so we can export the `ui:schema:options` props in the `api-report.md`. + - Exporting all of the `UiOptions` types for the `FieldExtensions` so we can see them in the `api-report.md`. + - Removing the redundant type in the `CustomFieldValidator` union. +- 2f2543592c: You can now hide sections or fields in your templates based on a feature flag. For example, take this template: + + ```json + { + title: 'my-schema', + steps: [ + { + title: 'Fill in some steps', + schema: { + title: 'Fill in some steps', + 'backstage:featureFlag': 'experimental-feature', + properties: { + name: { + title: 'Name', + type: 'string', + 'backstage:featureFlag': 'should-show-some-stuff-first-option', + }, + description: { + title: 'Description', + type: 'string', + description: 'A description for the component', + }, + owner: { + title: 'Owner', + type: 'string', + description: 'Owner of the component', + }, + }, + type: 'object', + }, + }, + } + + ``` + + If you have a feature flag that is called `experimental-feature` then your first step would be shown if you that feature flag was not active then it wouldn't be shown. The same goes for the properties in the schema. Make sure to use the key `backstage:featureFlag` in your templates if you want to use this functionality. + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-scaffolder-common@0.2.2 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration@0.7.5 + - @backstage/integration-react@0.1.23 + - @backstage/plugin-permission-react@0.3.2 + +## 0.12.3 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 538ca90790: Use updated type names from `@backstage/catalog-client` +- deaf6065db: Adapt to the new `CatalogApi.getLocationByRef` +- e72d371296: Use `TemplateEntityV1beta2` from `@backstage/plugin-scaffolder-common` instead + of `@backstage/catalog-model`. +- Updated dependencies + - @backstage/plugin-scaffolder-common@0.2.0 + - @backstage/catalog-client@0.7.0 + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/integration-react@0.1.22 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/plugin-permission-react@0.3.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + - @backstage/types@0.1.2 + - @backstage/plugin-catalog-common@0.1.3 + ## 0.12.2 ### Patch Changes diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index f8ae64cd82..82dfecfaa5 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -12,7 +12,6 @@ import { ComponentProps } from 'react'; import { ComponentType } from 'react'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; -import { EntityName } from '@backstage/catalog-model'; import { Extension } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; @@ -20,40 +19,33 @@ import { FieldProps } from '@rjsf/core'; import { FieldValidation } from '@rjsf/core'; import { IconButton } from '@material-ui/core'; import { JsonObject } from '@backstage/types'; -import { JSONSchema } from '@backstage/catalog-model'; +import { JSONSchema7 } from 'json-schema'; +import { JsonValue } from '@backstage/types'; import { Observable } from '@backstage/types'; import { default as React_2 } from 'react'; -import { ReactNode } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; -import { TemplateEntityV1beta2 } from '@backstage/catalog-model'; +import { TemplateEntityV1beta2 } from '@backstage/plugin-scaffolder-common'; // Warning: (ae-missing-release-tag) "createScaffolderFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export function createScaffolderFieldExtension( - options: FieldExtensionOptions, +export function createScaffolderFieldExtension< + TReturnValue = unknown, + TInputProps = unknown, +>( + options: FieldExtensionOptions, ): Extension<() => null>; // @public -export type CustomFieldValidator = - | ((data: T, field: FieldValidation) => void) - | (( - data: T, - field: FieldValidation, - context: { - apiHolder: ApiHolder; - }, - ) => void); - -// Warning: (ae-missing-release-tag) "EntityNamePicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const EntityNamePicker: ({ - schema: { title, description }, - ...props -}: FieldProps) => JSX.Element; +export type CustomFieldValidator = ( + data: TFieldReturnValue, + field: FieldValidation, + context: { + apiHolder: ApiHolder; + }, +) => void; // Warning: (ae-missing-release-tag) "EntityNamePickerFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -62,32 +54,46 @@ export const EntityNamePickerFieldExtension: () => null; // Warning: (ae-missing-release-tag) "EntityPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) -export const EntityPicker: ({ - onChange, - schema: { title, description }, - required, - uiSchema, - rawErrors, - formData, - idSchema, -}: FieldProps) => JSX.Element; +// @public +export const EntityPicker: ( + props: FieldExtensionComponentProps, +) => JSX.Element; // Warning: (ae-missing-release-tag) "EntityPickerFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const EntityPickerFieldExtension: () => null; +// Warning: (ae-missing-release-tag) "EntityPickerUiOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface EntityPickerUiOptions { + // (undocumented) + allowArbitraryValues?: boolean; + // (undocumented) + allowedKinds?: string[]; + // (undocumented) + defaultKind?: string; +} + +// Warning: (ae-missing-release-tag) "EntityTagsPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// // @public -export const EntityTagsPicker: ({ - formData, - onChange, - uiSchema, -}: FieldProps) => JSX.Element; +export const EntityTagsPicker: ( + props: FieldExtensionComponentProps, +) => JSX.Element; // @public export const EntityTagsPickerFieldExtension: () => null; +// Warning: (ae-missing-release-tag) "EntityTagsPickerUiOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface EntityTagsPickerUiOptions { + // (undocumented) + kinds?: string[]; +} + // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "FavouriteTemplate" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -96,57 +102,113 @@ export const FavouriteTemplate: (props: Props) => JSX.Element; // @public export interface FieldExtensionComponentProps< - ReturnValue, - UiOptions extends {} = {}, -> extends FieldProps { + TFieldReturnValue, + TUiOptions extends {} = {}, +> extends FieldProps { // (undocumented) - uiSchema: { - 'ui:options'?: UiOptions; + uiSchema: FieldProps['uiSchema'] & { + 'ui:options'?: TUiOptions; }; } // @public -export type FieldExtensionOptions = { +export type FieldExtensionOptions< + TFieldReturnValue = unknown, + TProps = FieldProps, +> = { name: string; - component: (props: FieldProps) => JSX.Element | null; - validation?: CustomFieldValidator; + component: (props: TProps) => JSX.Element | null; + validation?: CustomFieldValidator; +}; + +// Warning: (ae-missing-release-tag) "JobStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; + +// Warning: (ae-missing-release-tag) "ListActionsResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ListActionsResponse = Array<{ + id: string; + description?: string; + schema?: { + input?: JSONSchema7; + output?: JSONSchema7; + }; +}>; + +// Warning: (ae-missing-release-tag) "LogEvent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type LogEvent = { + type: 'log' | 'completion'; + body: { + message: string; + stepId?: string; + status?: ScaffolderTaskStatus; + }; + createdAt: string; + id: string; + taskId: string; }; // Warning: (ae-missing-release-tag) "OwnedEntityPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) -export const OwnedEntityPicker: ({ - onChange, - schema: { title, description }, - required, - uiSchema, - rawErrors, - formData, - idSchema, -}: FieldProps) => JSX.Element; +// @public +export const OwnedEntityPicker: ( + props: FieldExtensionComponentProps, +) => JSX.Element; // Warning: (ae-missing-release-tag) "OwnedEntityPickerFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const OwnedEntityPickerFieldExtension: () => null; -// Warning: (ae-missing-release-tag) "OwnerPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "OwnedEntityPickerUiOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const OwnerPicker: ({ - schema: { title, description }, - uiSchema, - ...props -}: FieldProps) => JSX.Element; +export interface OwnedEntityPickerUiOptions { + // (undocumented) + allowedKinds?: string[]; + // (undocumented) + defaultKind?: string; +} + +// Warning: (ae-missing-release-tag) "OwnerPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const OwnerPicker: ( + props: FieldExtensionComponentProps, +) => JSX.Element; // Warning: (ae-missing-release-tag) "OwnerPickerFieldExtension" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const OwnerPickerFieldExtension: () => null; -// Warning: (ae-missing-release-tag) "RepoUrlPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "OwnerPickerUiOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) +export interface OwnerPickerUiOptions { + // (undocumented) + allowedKinds?: string[]; +} + +// Warning: (ae-missing-release-tag) "repoPickerValidation" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export const repoPickerValidation: ( + value: string, + validation: FieldValidation, + context: { + apiHolder: ApiHolder; + }, +) => void; + +// Warning: (ae-missing-release-tag) "RepoUrlPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public export const RepoUrlPicker: ( props: FieldExtensionComponentProps, ) => JSX.Element; @@ -176,37 +238,49 @@ export interface RepoUrlPickerUiOptions { }; } +// Warning: (ae-missing-release-tag) "RouterProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type RouterProps = { + TemplateCardComponent?: + | ComponentType<{ + template: TemplateEntityV1beta2; + }> + | undefined; + TaskPageComponent?: ComponentType<{}>; + components?: { + TemplateCardComponent?: + | ComponentType<{ + template: TemplateEntityV1beta2; + }> + | undefined; + TaskPageComponent?: ComponentType<{}>; + }; + groups?: Array<{ + title?: string; + titleComponent?: React_2.ReactNode; + filter: (entity: Entity) => boolean; + }>; +}; + // @public export interface ScaffolderApi { // (undocumented) - getIntegrationsList(options: { allowedHosts: string[] }): Promise< - { - type: string; - title: string; - host: string; - }[] - >; - // Warning: (ae-forgotten-export) The symbol "ScaffolderTask" needs to be exported by the entry point index.d.ts - // + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise; // (undocumented) getTask(taskId: string): Promise; - // Warning: (ae-forgotten-export) The symbol "TemplateParameterSchema" needs to be exported by the entry point index.d.ts - // // (undocumented) getTemplateParameterSchema( - templateName: EntityName, + templateRef: string, ): Promise; - // Warning: (ae-forgotten-export) The symbol "ListActionsResponse" needs to be exported by the entry point index.d.ts listActions(): Promise; scaffold( - templateName: string, - values: Record, - secrets?: Record, - ): Promise; - // Warning: (ae-forgotten-export) The symbol "LogEvent" needs to be exported by the entry point index.d.ts - // + options: ScaffolderScaffoldOptions, + ): Promise; // (undocumented) - streamLogs(options: { taskId: string; after?: number }): Observable; + streamLogs(options: ScaffolderStreamLogsOptions): Observable; } // @public @@ -221,28 +295,22 @@ export class ScaffolderClient implements ScaffolderApi { useLongPollingLogs?: boolean; }); // (undocumented) - getIntegrationsList(options: { allowedHosts: string[] }): Promise< - { - type: string; - title: string; - host: string; - }[] - >; + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise; // (undocumented) - getTask(taskId: string): Promise; + getTask(taskId: string): Promise; // (undocumented) getTemplateParameterSchema( - templateName: EntityName, + templateRef: string, ): Promise; // (undocumented) listActions(): Promise; scaffold( - templateName: string, - values: Record, - secrets?: Record, - ): Promise; + options: ScaffolderScaffoldOptions, + ): Promise; // (undocumented) - streamLogs(options: { taskId: string; after?: number }): Observable; + streamLogs(options: ScaffolderStreamLogsOptions): Observable; } // Warning: (ae-missing-release-tag) "ScaffolderFieldExtensions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) @@ -250,33 +318,35 @@ export class ScaffolderClient implements ScaffolderApi { // @public (undocumented) export const ScaffolderFieldExtensions: React_2.ComponentType; +// Warning: (ae-missing-release-tag) "ScaffolderGetIntegrationsListOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderGetIntegrationsListOptions { + // (undocumented) + allowedHosts: string[]; +} + +// Warning: (ae-missing-release-tag) "ScaffolderGetIntegrationsListResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderGetIntegrationsListResponse { + // (undocumented) + integrations: { + type: string; + title: string; + host: string; + }[]; +} + // Warning: (ae-missing-release-tag) "ScaffolderPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const ScaffolderPage: ({ - TemplateCardComponent, - TaskPageComponent, - groups, -}: { - TemplateCardComponent?: - | ComponentType<{ - template: TemplateEntityV1beta2; - }> - | undefined; - TaskPageComponent?: ComponentType<{}> | undefined; - groups?: - | { - title?: string | undefined; - titleComponent?: ReactNode; - filter: (entity: Entity) => boolean; - }[] - | undefined; -}) => JSX.Element; +export const ScaffolderPage: (props: RouterProps) => JSX.Element; // Warning: (ae-missing-release-tag) "scaffolderPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -const scaffolderPlugin: BackstagePlugin< +export const scaffolderPlugin: BackstagePlugin< { root: RouteRef; }, @@ -284,8 +354,76 @@ const scaffolderPlugin: BackstagePlugin< registerComponent: ExternalRouteRef; } >; -export { scaffolderPlugin as plugin }; -export { scaffolderPlugin }; + +// Warning: (ae-missing-release-tag) "ScaffolderScaffoldOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderScaffoldOptions { + // (undocumented) + secrets?: Record; + // (undocumented) + templateRef: string; + // (undocumented) + values: Record; +} + +// Warning: (ae-missing-release-tag) "ScaffolderScaffoldResponse" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderScaffoldResponse { + // (undocumented) + taskId: string; +} + +// Warning: (ae-missing-release-tag) "ScaffolderStreamLogsOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ScaffolderStreamLogsOptions { + // (undocumented) + after?: number; + // (undocumented) + taskId: string; +} + +// Warning: (ae-missing-release-tag) "ScaffolderTask" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ScaffolderTask = { + id: string; + spec: TaskSpec; + status: 'failed' | 'completed' | 'processing' | 'open' | 'cancelled'; + lastHeartbeatAt: string; + createdAt: string; +}; + +// Warning: (ae-missing-release-tag) "ScaffolderTaskOutput" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ScaffolderTaskOutput = { + entityRef?: string; + remoteUrl?: string; + links?: ScaffolderOutputLink[]; +} & { + [key: string]: unknown; +}; + +// Warning: (ae-missing-release-tag) "ScaffolderTaskStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type ScaffolderTaskStatus = + | 'open' + | 'processing' + | 'failed' + | 'completed' + | 'skipped'; + +// @public +export interface ScaffolderUseTemplateSecrets { + // @deprecated (undocumented) + setSecret: (input: Record) => void; + // (undocumented) + setSecrets: (input: Record) => void; +} // @public export const TaskPage: ({ loadingText }: TaskPageProps) => JSX.Element; @@ -297,7 +435,7 @@ export type TaskPageProps = { // Warning: (ae-missing-release-tag) "TemplateList" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public @deprecated (undocumented) export const TemplateList: ({ TemplateCardComponent, group, @@ -305,7 +443,7 @@ export const TemplateList: ({ // Warning: (ae-missing-release-tag) "TemplateListProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // -// @public (undocumented) +// @public @deprecated (undocumented) export type TemplateListProps = { TemplateCardComponent?: | ComponentType<{ @@ -313,33 +451,32 @@ export type TemplateListProps = { }> | undefined; group?: { - title?: string; + title?: React_2.ReactNode; titleComponent?: React_2.ReactNode; filter: (entity: Entity) => boolean; }; }; +// Warning: (ae-missing-release-tag) "TemplateParameterSchema" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type TemplateParameterSchema = { + title: string; + steps: Array<{ + title: string; + schema: JsonObject; + }>; +}; + // Warning: (ae-missing-release-tag) "TemplateTypePicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) export const TemplateTypePicker: () => JSX.Element | null; -// Warning: (ae-missing-release-tag) "TextValuePicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const TextValuePicker: ({ - onChange, - required, - schema: { title, description }, - rawErrors, - formData, - uiSchema: { 'ui:autofocus': autoFocus }, - idSchema, - placeholder, -}: FieldProps) => JSX.Element; - // @public -export const useTemplateSecrets: () => { - setSecret: (input: Record) => void; -}; +export const useTemplateSecrets: () => ScaffolderUseTemplateSecrets; + +// Warnings were encountered during analysis: +// +// src/types.d.ts:32:5 - (ae-forgotten-export) The symbol "ScaffolderOutputLink" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 0e31d6c4a5..e4eada7a51 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "0.12.2", + "version": "0.13.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,30 +24,31 @@ "backstage" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/integration-react": "^0.1.21", - "@backstage/plugin-catalog-common": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/plugin-permission-react": "^0.3.0", - "@backstage/plugin-scaffolder-common": "^0.1.3", - "@backstage/theme": "^0.2.14", - "@backstage/types": "^0.1.1", + "@types/json-schema": "^7.0.9", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/integration-react": "^0.1.23", + "@backstage/plugin-catalog-common": "^0.1.4", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/plugin-permission-react": "^0.3.2", + "@backstage/plugin-scaffolder-common": "^0.2.2", + "@backstage/theme": "^0.2.15", + "@backstage/types": "^0.1.3", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -69,11 +73,11 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/plugin-catalog": "^0.7.12", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/plugin-catalog": "^0.9.0", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/scaffolder/src/api.test.ts b/plugins/scaffolder/src/api.test.ts index 369722ec8f..4e47c7f239 100644 --- a/plugins/scaffolder/src/api.test.ts +++ b/plugins/scaffolder/src/api.test.ts @@ -62,7 +62,9 @@ describe('api', () => { 'dev.azure.com', 'bitbucket.org', ]; - const integrations = await apiClient.getIntegrationsList({ allowedHosts }); + const { integrations } = await apiClient.getIntegrationsList({ + allowedHosts, + }); integrations.forEach(integration => expect(allowedHosts).toContain(integration.host), ); diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 62fc06a6db..818ee7971b 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { EntityName } from '@backstage/catalog-model'; +import { parseEntityRef } from '@backstage/catalog-model'; import { createApiRef, DiscoveryApi, @@ -22,11 +22,21 @@ import { } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { JsonObject, JsonValue, Observable } from '@backstage/types'; -import { Field, FieldValidation } from '@rjsf/core'; +import { Observable } from '@backstage/types'; import qs from 'qs'; import ObservableImpl from 'zen-observable'; -import { ListActionsResponse, ScaffolderTask, Status } from './types'; +import { + ListActionsResponse, + LogEvent, + ScaffolderApi, + TemplateParameterSchema, + ScaffolderScaffoldOptions, + ScaffolderScaffoldResponse, + ScaffolderStreamLogsOptions, + ScaffolderGetIntegrationsListOptions, + ScaffolderGetIntegrationsListResponse, + ScaffolderTask, +} from './types'; /** * Utility API reference for the {@link ScaffolderApi}. @@ -37,70 +47,6 @@ export const scaffolderApiRef = createApiRef({ id: 'plugin.scaffolder.service', }); -type TemplateParameterSchema = { - title: string; - steps: Array<{ - title: string; - schema: JsonObject; - }>; -}; - -export type LogEvent = { - type: 'log' | 'completion'; - body: { - message: string; - stepId?: string; - status?: Status; - }; - createdAt: string; - id: string; - taskId: string; -}; - -export type CustomField = { - name: string; - component: Field; - validation: (data: JsonValue, field: FieldValidation) => void; -}; - -/** - * An API to interact with the scaffolder backend. - * - * @public - */ -export interface ScaffolderApi { - getTemplateParameterSchema( - templateName: EntityName, - ): Promise; - - /** - * Executes the scaffolding of a component, given a template and its - * parameter values. - * - * @param templateName - Name of the Template entity for the scaffolder to use. New project is going to be created out of this template. - * @param values - Parameters for the template, e.g. name, description - * @param secrets - Optional secrets to pass to as the secrets parameter to the template. - */ - scaffold( - templateName: string, - values: Record, - secrets?: Record, - ): Promise; - - getTask(taskId: string): Promise; - - getIntegrationsList(options: { - allowedHosts: string[]; - }): Promise<{ type: string; title: string; host: string }[]>; - - /** - * Returns a list of all installed actions. - */ - listActions(): Promise; - - streamLogs(options: { taskId: string; after?: number }): Observable; -} - /** * An API to interact with the scaffolder backend. * @@ -124,8 +70,10 @@ export class ScaffolderClient implements ScaffolderApi { this.useLongPollingLogs = options.useLongPollingLogs ?? false; } - async getIntegrationsList(options: { allowedHosts: string[] }) { - return [ + async getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise { + const integrations = [ ...this.scmIntegrationsApi.azure.list(), ...this.scmIntegrationsApi.bitbucket.list(), ...this.scmIntegrationsApi.github.list(), @@ -133,17 +81,24 @@ export class ScaffolderClient implements ScaffolderApi { ] .map(c => ({ type: c.type, title: c.title, host: c.config.host })) .filter(c => options.allowedHosts.includes(c.host)); + + return { + integrations, + }; } async getTemplateParameterSchema( - templateName: EntityName, + templateRef: string, ): Promise { - const { namespace, kind, name } = templateName; + const { namespace, kind, name } = parseEntityRef(templateRef, { + defaultKind: 'template', + }); const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); const templatePath = [namespace, kind, name] .map(s => encodeURIComponent(s)) .join('/'); + const url = `${baseUrl}/v2/templates/${templatePath}/parameter-schema`; const response = await this.fetchApi.fetch(url); @@ -159,15 +114,12 @@ export class ScaffolderClient implements ScaffolderApi { * Executes the scaffolding of a component, given a template and its * parameter values. * - * @param templateName - Template name for the scaffolder to use. New project is going to be created out of this template. - * @param values - Parameters for the template, e.g. name, description - * @param secrets - Optional secrets to pass to as the secrets parameter to the template. + * @param options - The {@link ScaffolderScaffoldOptions} the scaffolding. */ async scaffold( - templateName: string, - values: Record, - secrets: Record = {}, - ): Promise { + options: ScaffolderScaffoldOptions, + ): Promise { + const { templateRef, values, secrets = {} } = options; const url = `${await this.discoveryApi.getBaseUrl('scaffolder')}/v2/tasks`; const response = await this.fetchApi.fetch(url, { method: 'POST', @@ -175,7 +127,7 @@ export class ScaffolderClient implements ScaffolderApi { 'Content-Type': 'application/json', }, body: JSON.stringify({ - templateName, + templateRef, values: { ...values }, secrets, }), @@ -188,10 +140,10 @@ export class ScaffolderClient implements ScaffolderApi { } const { id } = (await response.json()) as { id: string }; - return id; + return { taskId: id }; } - async getTask(taskId: string) { + async getTask(taskId: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); const url = `${baseUrl}/v2/tasks/${encodeURIComponent(taskId)}`; @@ -203,10 +155,7 @@ export class ScaffolderClient implements ScaffolderApi { return await response.json(); } - streamLogs(options: { - taskId: string; - after?: number; - }): Observable { + streamLogs(options: ScaffolderStreamLogsOptions): Observable { if (this.useLongPollingLogs) { return this.streamLogsPolling(options); } diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx index ce0354b817..aff6681037 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx @@ -14,11 +14,12 @@ * limitations under the License. */ import React from 'react'; -import { ScaffolderApi, scaffolderApiRef } from '../../api'; +import { scaffolderApiRef } from '../../api'; import { ActionsPage } from './ActionsPage'; import { rootRouteRef } from '../../routes'; import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils'; import { ApiProvider } from '@backstage/core-app-api'; +import { ScaffolderApi } from '../../types'; const scaffolderApiMock: jest.Mocked = { scaffold: jest.fn(), diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx index 4542979e73..78efaa43ff 100644 --- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx +++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.tsx @@ -28,8 +28,7 @@ import { TableRow, makeStyles, } from '@material-ui/core'; -import { JSONSchema } from '@backstage/catalog-model'; -import { JSONSchema7Definition } from 'json-schema'; +import { JSONSchema7, JSONSchema7Definition } from 'json-schema'; import classNames from 'classnames'; import { useApi } from '@backstage/core-plugin-api'; @@ -87,7 +86,7 @@ export const ActionsPage = () => { ); } - const formatRows = (input: JSONSchema) => { + const formatRows = (input: JSONSchema7) => { const properties = input.properties; if (!properties) { return undefined; @@ -95,7 +94,7 @@ export const ActionsPage = () => { return Object.entries(properties).map(entry => { const [key] = entry; - const props = entry[1] as unknown as JSONSchema; + const props = entry[1] as unknown as JSONSchema7; const codeClassname = classNames(classes.code, { [classes.codeRequired]: input.required?.includes(key), }); @@ -115,7 +114,7 @@ export const ActionsPage = () => { }); }; - const renderTable = (input: JSONSchema) => { + const renderTable = (input: JSONSchema7) => { if (!input.properties) { return undefined; } @@ -145,7 +144,7 @@ export const ActionsPage = () => { <> {name} {input.map((i, index) => ( -
{renderTable(i as unknown as JSONSchema)}
+
{renderTable(i as unknown as JSONSchema7)}
))} ); diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index a9c8eaec1d..4d37eafee4 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -24,12 +24,17 @@ import { Stepper, Typography, } from '@material-ui/core'; -import { errorApiRef, useApi } from '@backstage/core-plugin-api'; +import { + errorApiRef, + useApi, + featureFlagsApiRef, +} from '@backstage/core-plugin-api'; import { FormProps, IChangeEvent, UiSchema, withTheme } from '@rjsf/core'; import { Theme as MuiTheme } from '@rjsf/material-ui'; import React, { useState } from 'react'; import { transformSchemaToProps } from './schema'; import { Content, StructuredMetadataTable } from '@backstage/core-components'; +import cloneDeep from 'lodash/cloneDeep'; import * as fieldOverrides from './FieldOverrides'; const Form = withTheme(MuiTheme); @@ -104,18 +109,50 @@ export function getReviewData(formData: Record, steps: Step[]) { return reviewData; } -export const MultistepJsonForm = ({ - steps, - formData, - onChange, - onReset, - onFinish, - fields, - widgets, -}: Props) => { +export const MultistepJsonForm = (props: Props) => { + const { formData, onChange, onReset, onFinish, fields, widgets } = props; const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); const errorApi = useApi(errorApiRef); + const featureFlagApi = useApi(featureFlagsApiRef); + const featureFlagKey = 'backstage:featureFlag'; + const filterOutProperties = (step: Step): Step => { + const filteredStep = cloneDeep(step); + const removedPropertyKeys: Array = []; + if (filteredStep.schema.properties) { + filteredStep.schema.properties = Object.fromEntries( + Object.entries(filteredStep.schema.properties).filter( + ([key, value]) => { + if (value[featureFlagKey]) { + if (featureFlagApi.isActive(value[featureFlagKey])) { + return true; + } + removedPropertyKeys.push(key); + return false; + } + return true; + }, + ), + ); + + // remove the feature flag property key from required if they are not active + filteredStep.schema.required = Array.isArray(filteredStep.schema.required) + ? filteredStep.schema.required?.filter( + r => !removedPropertyKeys.includes(r as string), + ) + : filteredStep.schema.required; + } + return filteredStep; + }; + + const steps = props.steps + .filter(step => { + const featureFlag = step.schema[featureFlagKey]; + return ( + typeof featureFlag !== 'string' || featureFlagApi.isActive(featureFlag) + ); + }) + .map(filterOutProperties); const handleReset = () => { setActiveStep(0); diff --git a/plugins/scaffolder/src/components/Router.tsx b/plugins/scaffolder/src/components/Router.tsx index 601844780d..a456f2bdd3 100644 --- a/plugins/scaffolder/src/components/Router.tsx +++ b/plugins/scaffolder/src/components/Router.tsx @@ -16,7 +16,8 @@ import React, { ComponentType } from 'react'; import { Routes, Route, useOutlet } from 'react-router'; -import { TemplateEntityV1beta2, Entity } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; +import { TemplateEntityV1beta2 } from '@backstage/plugin-scaffolder-common'; import { ScaffolderPage } from './ScaffolderPage'; import { TemplatePage } from './TemplatePage'; import { TaskPage } from './TaskPage'; @@ -31,11 +32,20 @@ import { } from '../extensions'; import { useElementFilter } from '@backstage/core-plugin-api'; -type RouterProps = { +export type RouterProps = { + /** @deprecated use components.TemplateCardComponent instead */ TemplateCardComponent?: | ComponentType<{ template: TemplateEntityV1beta2 }> | undefined; + /** @deprecated use component.TaskPageComponent instead */ TaskPageComponent?: ComponentType<{}>; + + components?: { + TemplateCardComponent?: + | ComponentType<{ template: TemplateEntityV1beta2 }> + | undefined; + TaskPageComponent?: ComponentType<{}>; + }; groups?: Array<{ title?: string; titleComponent?: React.ReactNode; @@ -43,13 +53,26 @@ type RouterProps = { }>; }; -export const Router = ({ - TemplateCardComponent, - TaskPageComponent, - groups, -}: RouterProps) => { +export const Router = (props: RouterProps) => { + const { + TemplateCardComponent: legacyTemplateCardComponent, + TaskPageComponent: legacyTaskPageComponent, + groups, + components = {}, + } = props; + + if (legacyTemplateCardComponent || legacyTaskPageComponent) { + // eslint-disable-next-line no-console + console.warn( + "DEPRECATION: 'TemplateCardComponent' and 'TaskPageComponent' are deprecated when calling the 'ScaffolderPage'. Use 'components' prop to pass these component overrides instead.", + ); + } + + const { TemplateCardComponent, TaskPageComponent } = components; + const outlet = useOutlet(); - const TaskPageElement = TaskPageComponent || TaskPage; + const TaskPageElement = + TaskPageComponent ?? legacyTaskPageComponent ?? TaskPage; const customFieldExtensions = useElementFilter(outlet, elements => elements @@ -77,8 +100,10 @@ export const Router = ({ path="/" element={ } /> diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx index 116b7ed82a..da9f1f74b8 100644 --- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx +++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx @@ -23,7 +23,8 @@ import { Page, SupportButton, } from '@backstage/core-components'; -import { TemplateEntityV1beta2, Entity } from '@backstage/catalog-model'; +import { Entity } from '@backstage/catalog-model'; +import { TemplateEntityV1beta2 } from '@backstage/plugin-scaffolder-common'; import { useRouteRef } from '@backstage/core-plugin-api'; import { EntityKindPicker, @@ -54,7 +55,8 @@ export type ScaffolderPageProps = { | ComponentType<{ template: TemplateEntityV1beta2 }> | undefined; groups?: Array<{ - title?: string; + title?: React.ReactNode; + /** @deprcated use title instead as it accepts a string and react component */ titleComponent?: React.ReactNode; filter: (entity: Entity) => boolean; }>; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 2f49f40643..e0445ed641 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -14,6 +14,7 @@ * limitations under the License. */ +import { parseEntityRef } from '@backstage/catalog-model'; import { Content, ErrorPage, @@ -45,10 +46,10 @@ import classNames from 'classnames'; import { DateTime, Interval } from 'luxon'; import qs from 'qs'; import React, { memo, useEffect, useMemo, useState } from 'react'; -import { generatePath, useNavigate, useParams } from 'react-router'; +import { useNavigate, useParams } from 'react-router'; import useInterval from 'react-use/lib/useInterval'; -import { rootRouteRef } from '../../routes'; -import { Status, TaskOutput } from '../../types'; +import { rootRouteRef, selectedTemplateRouteRef } from '../../routes'; +import { ScaffolderTaskStatus, ScaffolderTaskOutput } from '../../types'; import { useTaskEventStream } from '../hooks/useEventStream'; import { TaskPageLinks } from './TaskPageLinks'; @@ -85,7 +86,7 @@ const useStyles = makeStyles((theme: Theme) => type TaskStep = { id: string; name: string; - status: Status; + status: ScaffolderTaskStatus; startedAt?: string; endedAt?: string; }; @@ -216,7 +217,11 @@ export const TaskStatusStepper = memo( }, ); -const hasLinks = ({ entityRef, remoteUrl, links = [] }: TaskOutput): boolean => +const hasLinks = ({ + entityRef, + remoteUrl, + links = [], +}: ScaffolderTaskOutput): boolean => !!(entityRef || remoteUrl || links.length > 0); /** @@ -238,7 +243,8 @@ export type TaskPageProps = { export const TaskPage = ({ loadingText }: TaskPageProps) => { const classes = useStyles(); const navigate = useNavigate(); - const rootLink = useRouteRef(rootRouteRef); + const rootPath = useRouteRef(rootRouteRef); + const templateRoute = useRouteRef(selectedTemplateRouteRef); const [userSelectedStepId, setUserSelectedStepId] = useState< string | undefined >(undefined); @@ -291,8 +297,9 @@ export const TaskPage = ({ loadingText }: TaskPageProps) => { const { output } = taskStream; const handleStartOver = () => { - if (!taskStream.task || !taskStream.task?.spec.metadata?.name) { - navigate(generatePath(rootLink())); + if (!taskStream.task || !taskStream.task?.spec.templateInfo?.entityRef) { + navigate(rootPath()); + return; } const formData = @@ -300,15 +307,14 @@ export const TaskPage = ({ loadingText }: TaskPageProps) => { ? taskStream.task!.spec.values : taskStream.task!.spec.parameters; + const { name } = parseEntityRef( + taskStream.task!.spec.templateInfo?.entityRef, + ); + navigate( - generatePath( - `${rootLink()}/templates/:templateName?${qs.stringify({ - formData: JSON.stringify(formData), - })}`, - { - templateName: taskStream.task!.spec.metadata!.name, - }, - ), + `${templateRoute({ templateName: name })}?${qs.stringify({ + formData: JSON.stringify(formData), + })}`, ); }; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx index 2adec564a1..5451b4e725 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx @@ -14,17 +14,17 @@ * limitations under the License. */ -import { parseEntityName } from '@backstage/catalog-model'; +import { parseEntityRef } from '@backstage/catalog-model'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { Box } from '@material-ui/core'; import LanguageIcon from '@material-ui/icons/Language'; import React from 'react'; -import { TaskOutput } from '../../types'; +import { ScaffolderTaskOutput } from '../../types'; import { IconLink } from './IconLink'; import { IconComponent, useApp, useRouteRef } from '@backstage/core-plugin-api'; type TaskPageLinksProps = { - output: TaskOutput; + output: ScaffolderTaskOutput; }; export const TaskPageLinks = ({ output }: TaskPageLinksProps) => { @@ -57,7 +57,7 @@ export const TaskPageLinks = ({ output }: TaskPageLinksProps) => { .filter(({ url, entityRef }) => url || entityRef) .map(({ url, entityRef, title, icon }) => { if (entityRef) { - const entityName = parseEntityName(entityRef); + const entityName = parseEntityRef(entityRef); const target = entityRoute(entityName); return { title, icon, url: target }; } diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index a02e6d3b14..f4710427c2 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -13,11 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - Entity, - RELATION_OWNED_BY, - TemplateEntityV1beta2, -} from '@backstage/catalog-model'; +import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { TemplateEntityV1beta2 } from '@backstage/plugin-scaffolder-common'; import { ScmIntegrationIcon, scmIntegrationsApiRef, @@ -44,8 +41,7 @@ import { } from '@material-ui/core'; import WarningIcon from '@material-ui/icons/Warning'; import React from 'react'; -import { generatePath } from 'react-router'; -import { rootRouteRef } from '../../routes'; +import { selectedTemplateRouteRef } from '../../routes'; import { FavouriteTemplate } from '../FavouriteTemplate/FavouriteTemplate'; import { Button, ItemCardHeader } from '@backstage/core-components'; @@ -123,7 +119,8 @@ const DeprecationWarning = () => { const Title = ( - This template syntax is deprecated. Click for more info. + This template uses a syntax that has been deprecated, and should be + migrated to a newer syntax. Click for more info. ); @@ -131,7 +128,7 @@ const DeprecationWarning = () => {
@@ -143,7 +140,7 @@ const DeprecationWarning = () => { export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { const backstageTheme = useTheme(); - const rootLink = useRouteRef(rootRouteRef); + const templateRoute = useRouteRef(selectedTemplateRouteRef); const templateProps = getTemplateCardProps(template); const ownedByRelations = getEntityRelations( template as Entity, @@ -154,9 +151,7 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { : 'other'; const theme = backstageTheme.getPageTheme({ themeId }); const classes = useStyles({ backgroundImage: theme.backgroundImage }); - const href = generatePath(`${rootLink()}/templates/:templateName`, { - templateName: templateProps.name, - }); + const href = templateRoute({ templateName: templateProps.name }); const scmIntegrationsApi = useApi(scmIntegrationsApiRef); const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi); diff --git a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx index 2ac0def547..3d9da542de 100644 --- a/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx +++ b/plugins/scaffolder/src/components/TemplateList/TemplateList.tsx @@ -15,11 +15,8 @@ */ import React, { ComponentType } from 'react'; -import { - Entity, - stringifyEntityRef, - TemplateEntityV1beta2, -} from '@backstage/catalog-model'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { TemplateEntityV1beta2 } from '@backstage/plugin-scaffolder-common'; import { Content, ContentHeader, @@ -28,35 +25,55 @@ import { Progress, WarningPanel, } from '@backstage/core-components'; -import { useEntityListProvider } from '@backstage/plugin-catalog-react'; +import { useEntityList } from '@backstage/plugin-catalog-react'; import { Typography } from '@material-ui/core'; import { TemplateCard } from '../TemplateCard'; +/** + * @deprecated this type is deprecated and will be removed in a future releases, please use the TemplateCard to render your own list. + */ export type TemplateListProps = { TemplateCardComponent?: | ComponentType<{ template: TemplateEntityV1beta2 }> | undefined; group?: { - title?: string; + title?: React.ReactNode; + /** @deprecated use title instead, can be a string or a react component */ titleComponent?: React.ReactNode; filter: (entity: Entity) => boolean; }; }; +/** + * @deprecated this component is deprecated and will be removed in a future releases, please use the TemplateCard to render your own list. + */ export const TemplateList = ({ TemplateCardComponent, group, }: TemplateListProps) => { - const { loading, error, entities } = useEntityListProvider(); + const { loading, error, entities } = useEntityList(); const Card = TemplateCardComponent || TemplateCard; const maybeFilteredEntities = group ? entities.filter(e => group.filter(e)) : entities; - const title = group ? ( - group.titleComponent || - ) : ( - - ); + + const titleComponent: React.ReactNode = (() => { + if (group?.titleComponent) { + // eslint-disable-next-line no-console + console.warn( + 'DEPRECATED: group.titleComponent is now deprecated. Use group.title instead, it can be a string or a react component', + ); + return group?.titleComponent; + } + if (group && group.title) { + if (typeof group.title === 'string') { + return ; + } + return group.title; + } + + return ; + })(); if (group && maybeFilteredEntities.length === 0) { return null; @@ -82,7 +99,7 @@ export const TemplateList = ({ )} - {title} + {titleComponent} {maybeFilteredEntities && maybeFilteredEntities?.length > 0 && @@ -90,6 +107,7 @@ export const TemplateList = ({ ))} diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx index 7ca65fff25..d0eca58b9d 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx @@ -23,9 +23,14 @@ import { ThemeProvider } from '@material-ui/core'; import { act, fireEvent, within } from '@testing-library/react'; import React from 'react'; import { MemoryRouter, Route } from 'react-router'; -import { ScaffolderApi, scaffolderApiRef } from '../../api'; +import { scaffolderApiRef } from '../../api'; +import { ScaffolderApi } from '../../types'; import { rootRouteRef } from '../../routes'; import { TemplatePage } from './TemplatePage'; +import { + featureFlagsApiRef, + FeatureFlagsApi, +} from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; import { errorApiRef } from '@backstage/core-plugin-api'; @@ -48,11 +53,63 @@ const scaffolderApiMock: jest.Mocked = { listActions: jest.fn(), }; +const featureFlagsApiMock: jest.Mocked = { + isActive: jest.fn(), + registerFlag: jest.fn(), + getRegisteredFlags: jest.fn(), + save: jest.fn(), +}; + const errorApiMock = { post: jest.fn(), error$: jest.fn() }; +const schemaMockValue = { + title: 'my-schema', + steps: [ + { + title: 'Fill in some steps', + schema: { + title: 'Fill in some steps', + 'backstage:featureFlag': 'experimental-feature', + properties: { + name: { + title: 'Name', + type: 'string', + 'backstage:featureFlag': 'should-show-some-stuff-first-option', + }, + description: { + title: 'Description', + type: 'string', + description: 'A description for the component', + }, + owner: { + title: 'Owner', + type: 'string', + description: 'Owner of the component', + }, + }, + type: 'object', + }, + }, + { + title: 'Send data', + schema: { + title: 'Send data', + properties: { + user: { + title: 'User', + type: 'string', + }, + }, + type: 'object', + }, + }, + ], +}; + const apis = TestApiRegistry.from( [scaffolderApiRef, scaffolderApiMock], [errorApiRef, errorApiMock], + [featureFlagsApiRef, featureFlagsApiMock], ); describe('TemplatePage', () => { @@ -197,4 +254,32 @@ describe('TemplatePage', () => { fireEvent.click(await findByText('Next step')); expect(await findByText('Reset')).toBeInTheDocument(); }); + + it('should display a section or property based on a feature flag', async () => { + featureFlagsApiMock.isActive.mockImplementation(flag => { + if (flag === 'experimental-feature') { + return true; + } + return false; + }); + scaffolderApiMock.getTemplateParameterSchema.mockResolvedValue( + schemaMockValue, + ); + + const { queryByText } = await renderInTestApp( + + + , + { + mountedRoutes: { + '/create/actions': rootRouteRef, + }, + }, + ); + + expect(await queryByText('Name')).not.toBeInTheDocument(); + expect(await queryByText('Description')).toBeInTheDocument(); + expect(await queryByText('Owner')).toBeInTheDocument(); + expect(await queryByText('Send data')).toBeInTheDocument(); + }); }); diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index d30c292053..e69c4bab5c 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -18,13 +18,13 @@ import { LinearProgress } from '@material-ui/core'; import { FormValidation, IChangeEvent } from '@rjsf/core'; import qs from 'qs'; import React, { useCallback, useContext, useState } from 'react'; -import { generatePath, Navigate, useNavigate } from 'react-router'; +import { Navigate, useNavigate } from 'react-router'; import { useParams } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; import { scaffolderApiRef } from '../../api'; import { CustomFieldValidator, FieldExtensionOptions } from '../../extensions'; import { SecretsContext } from '../secrets/SecretsContext'; -import { rootRouteRef } from '../../routes'; +import { rootRouteRef, scaffolderTaskRouteRef } from '../../routes'; import { MultistepJsonForm } from '../MultistepJsonForm'; import { @@ -41,17 +41,13 @@ import { useApiHolder, useRouteRef, } from '@backstage/core-plugin-api'; +import { stringifyEntityRef } from '@backstage/catalog-model'; -const useTemplateParameterSchema = (templateName: string) => { +const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); const { value, loading, error } = useAsync( - () => - scaffolderApi.getTemplateParameterSchema({ - name: templateName, - kind: 'template', - namespace: 'default', - }), - [scaffolderApi, templateName], + () => scaffolderApi.getTemplateParameterSchema(templateRef), + [scaffolderApi, templateRef], ); return { schema: value, loading, error }; }; @@ -121,7 +117,8 @@ export const TemplatePage = ({ const scaffolderApi = useApi(scaffolderApiRef); const { templateName } = useParams(); const navigate = useNavigate(); - const rootLink = useRouteRef(rootRouteRef); + const scaffolderTaskRoute = useRouteRef(scaffolderTaskRouteRef); + const rootRoute = useRouteRef(rootRouteRef); const { schema, loading, error } = useTemplateParameterSchema(templateName); const [formState, setFormState] = useState>(() => { const query = qs.parse(window.location.search, { @@ -141,11 +138,15 @@ export const TemplatePage = ({ ); const handleCreate = async () => { - const id = await scaffolderApi.scaffold( - templateName, - formState, - secretsContext?.secrets, - ); + const { taskId } = await scaffolderApi.scaffold({ + templateRef: stringifyEntityRef({ + name: templateName, + kind: 'template', + namespace: 'default', + }), + values: formState, + secrets: secretsContext?.secrets, + }); const formParams = qs.stringify( { formData: formState }, @@ -158,16 +159,16 @@ export const TemplatePage = ({ // extra back/forward slots. window.history?.replaceState(null, document.title, newUrl); - navigate(generatePath(`${rootLink()}/tasks/:taskId`, { taskId: id })); + navigate(scaffolderTaskRoute({ taskId })); }; if (error) { errorApi.post(new Error(`Failed to load template, ${error}`)); - return ; + return ; } if (!loading && !schema) { errorApi.post(new Error('Template was not found.')); - return ; + return ; } const customFieldComponents = Object.fromEntries( diff --git a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx index c70f251eac..aa6630d4aa 100644 --- a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx +++ b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx @@ -17,7 +17,6 @@ import React from 'react'; import { fireEvent } from '@testing-library/react'; import { capitalize } from 'lodash'; -import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { TemplateTypePicker } from './TemplateTypePicker'; import { @@ -28,6 +27,7 @@ import { import { AlertApi, alertApiRef } from '@backstage/core-plugin-api'; import { ApiProvider } from '@backstage/core-app-api'; import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils'; +import { GetEntityFacetsResponse } from '@backstage/catalog-client'; const entities: Entity[] = [ { @@ -66,10 +66,15 @@ const apis = TestApiRegistry.from( [ catalogApiRef, { - getEntities: jest - .fn() - .mockImplementation(() => Promise.resolve({ items: entities })), - } as unknown as CatalogApi, + getEntityFacets: jest.fn().mockResolvedValue({ + facets: { + 'spec.type': entities.map(e => ({ + value: (e.spec as any).type, + count: 1, + })), + }, + } as GetEntityFacetsResponse), + }, ], [ alertApiRef, diff --git a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx index 8e7c0b0bea..802135a863 100644 --- a/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityNamePicker/EntityNamePicker.tsx @@ -14,12 +14,38 @@ * limitations under the License. */ import React from 'react'; -import { FieldProps } from '@rjsf/core'; -import { TextValuePicker } from '../TextValuePicker'; +import { FieldExtensionComponentProps } from '../../../extensions'; +import { TextField } from '@material-ui/core'; -export const EntityNamePicker = ({ - schema: { title = 'Name', description = 'Unique name of the component' }, - ...props -}: FieldProps) => ( - -); +/** + * EntityName Picker + */ +export const EntityNamePicker = ( + props: FieldExtensionComponentProps, +) => { + const { + onChange, + required, + schema: { title = 'Name', description = 'Unique name of the component' }, + rawErrors, + formData, + uiSchema: { 'ui:autofocus': autoFocus }, + idSchema, + placeholder, + } = props; + + return ( + onChange(value)} + margin="normal" + error={rawErrors?.length > 0 && !formData} + inputProps={{ autoFocus }} + /> + ); +}; diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx index 3dc609a63b..942eef9d82 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx @@ -18,9 +18,9 @@ import { Entity } from '@backstage/catalog-model'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; import { FieldProps } from '@rjsf/core'; -import userEvent from '@testing-library/user-event'; +import { fireEvent } from '@testing-library/react'; import React from 'react'; -import { EntityPicker, allowArbitraryValues } from './EntityPicker'; +import { EntityPicker } from './EntityPicker'; const makeEntity = (kind: string, namespace: string, name: string): Entity => ({ apiVersion: 'backstage.io/v1beta1', @@ -46,7 +46,7 @@ describe('', () => { getEntityByName: jest.fn(), getEntities: jest.fn(async () => ({ items: entities })), addLocation: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), } as any; let Wrapper: React.ComponentType; @@ -92,15 +92,16 @@ describe('', () => { }); it('updates even if there is not an exact match', async () => { - const { getByLabelText } = await renderInTestApp( + const { getByRole } = await renderInTestApp( , ); - const input = getByLabelText('Entity'); - userEvent.type(input, 'squ'); - input.blur(); + const input = getByRole('textbox'); + + fireEvent.change(input, { target: { value: 'squ' } }); + fireEvent.blur(input); expect(onChange).toHaveBeenCalledWith('squ'); }); @@ -136,37 +137,3 @@ describe('', () => { }); }); }); - -describe('allowArbitraryValues', () => { - describe('without ui:options', () => { - it('defaults to true', () => { - const uiSchema = {}; - const result = allowArbitraryValues(uiSchema); - expect(result).toBe(true); - }); - }); - - describe('without ui:options.allowArbitraryValues', () => { - it('defaults to true', () => { - const uiSchema = { 'ui:options': {} }; - const result = allowArbitraryValues(uiSchema); - expect(result).toBe(true); - }); - }); - - describe('with ui:options.allowArbitraryValues set to true', () => { - it('is true', () => { - const uiSchema = { 'ui:options': { allowArbitraryValues: true } }; - const result = allowArbitraryValues(uiSchema); - expect(result).toBe(true); - }); - }); - - describe('with ui:options.allowArbitraryValues set to false', () => { - it('is false', () => { - const uiSchema = { 'ui:options': { allowArbitraryValues: false } }; - const result = allowArbitraryValues(uiSchema); - expect(result).toBe(false); - }); - }); -}); diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx index 959e7883c0..31c44bfe97 100644 --- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.tsx @@ -21,24 +21,34 @@ import { import { TextField } from '@material-ui/core'; import FormControl from '@material-ui/core/FormControl'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import { FieldProps, UiSchema } from '@rjsf/core'; import React, { useCallback, useEffect } from 'react'; import useAsync from 'react-use/lib/useAsync'; +import { FieldExtensionComponentProps } from '../../../extensions'; -export const allowArbitraryValues = (uiSchema: UiSchema): boolean => - (uiSchema['ui:options']?.allowArbitraryValues as boolean) ?? true; +export interface EntityPickerUiOptions { + allowedKinds?: string[]; + defaultKind?: string; + allowArbitraryValues?: boolean; +} + +/** + * Entity Picker + */ +export const EntityPicker = ( + props: FieldExtensionComponentProps, +) => { + const { + onChange, + schema: { title = 'Entity', description = 'An entity from the catalog' }, + required, + uiSchema, + rawErrors, + formData, + idSchema, + } = props; + const allowedKinds = uiSchema['ui:options']?.allowedKinds; + const defaultKind = uiSchema['ui:options']?.defaultKind; -export const EntityPicker = ({ - onChange, - schema: { title = 'Entity', description = 'An entity from the catalog' }, - required, - uiSchema, - rawErrors, - formData, - idSchema, -}: FieldProps) => { - const allowedKinds = uiSchema['ui:options']?.allowedKinds as string[]; - const defaultKind = uiSchema['ui:options']?.defaultKind as string | undefined; const catalogApi = useApi(catalogApiRef); const { value: entities, loading } = useAsync(() => @@ -78,7 +88,7 @@ export const EntityPicker = ({ onChange={onSelect} options={entityRefs || []} autoSelect - freeSolo={allowArbitraryValues(uiSchema)} + freeSolo={uiSchema['ui:options']?.allowArbitraryValues ?? true} renderInput={params => ( ) => { +export const EntityTagsPicker = ( + props: FieldExtensionComponentProps, +) => { + const { formData, onChange, uiSchema } = props; const catalogApi = useApi(catalogApiRef); const [inputValue, setInputValue] = useState(''); const [inputError, setInputError] = useState(false); const tagValidator = makeValidator().isValidTag; - const kinds = uiSchema['ui:options']?.kinds as string[]; + const kinds = uiSchema['ui:options']?.kinds; const { loading, value: existingTags } = useAsync(async () => { const tagsRequest: GetEntitiesRequest = { fields: ['metadata.tags'] }; diff --git a/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts b/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts index 09f4d5c519..3ba36a2409 100644 --- a/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/EntityTagsPicker/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { EntityTagsPicker } from './EntityTagsPicker'; +export type { EntityTagsPickerUiOptions } from './EntityTagsPicker'; diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx index 3c2fc2b495..80f63efaf9 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/OwnedEntityPicker.tsx @@ -20,20 +20,33 @@ import { import { TextField } from '@material-ui/core'; import FormControl from '@material-ui/core/FormControl'; import Autocomplete from '@material-ui/lab/Autocomplete'; -import { FieldProps } from '@rjsf/core'; import React from 'react'; -export const OwnedEntityPicker = ({ - onChange, - schema: { title = 'Entity', description = 'An entity from the catalog' }, - required, - uiSchema, - rawErrors, - formData, - idSchema, -}: FieldProps) => { - const allowedKinds = uiSchema['ui:options']?.allowedKinds as string[]; - const defaultKind = uiSchema['ui:options']?.defaultKind as string | undefined; +import { FieldExtensionComponentProps } from '../../../extensions'; + +export interface OwnedEntityPickerUiOptions { + allowedKinds?: string[]; + defaultKind?: string; +} + +/** + * Owned Entity Picker + */ +export const OwnedEntityPicker = ( + props: FieldExtensionComponentProps, +) => { + const { + onChange, + schema: { title = 'Entity', description = 'An entity from the catalog' }, + required, + uiSchema, + rawErrors, + formData, + idSchema, + } = props; + + const allowedKinds = uiSchema['ui:options']?.allowedKinds; + const defaultKind = uiSchema['ui:options']?.defaultKind; const { ownedEntities, loading } = useOwnedEntities(allowedKinds); const entityRefs = ownedEntities?.items diff --git a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts index 7cf5add6eb..125f6fdef0 100644 --- a/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/OwnedEntityPicker/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { OwnedEntityPicker } from './OwnedEntityPicker'; +export type { OwnedEntityPickerUiOptions } from './OwnedEntityPicker'; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx index 31992fbc8a..b78d3add9e 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx @@ -43,7 +43,7 @@ describe('', () => { getEntityByName: jest.fn(), getEntities: jest.fn(async () => ({ items: entities })), addLocation: jest.fn(), - getLocationByEntity: jest.fn(), + getLocationByRef: jest.fn(), removeEntityByUid: jest.fn(), } as any; let Wrapper: React.ComponentType; diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx index dd02622ae9..9012ed149e 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.tsx @@ -13,15 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { FieldProps } from '@rjsf/core'; import React from 'react'; import { EntityPicker } from '../EntityPicker'; +import { FieldExtensionComponentProps } from '../../../extensions'; + +export interface OwnerPickerUiOptions { + allowedKinds?: string[]; +} + +/** + * Owner Picker + */ +export const OwnerPicker = ( + props: FieldExtensionComponentProps, +) => { + const { + schema: { title = 'Owner', description = 'The owner of the component' }, + uiSchema, + ...restProps + } = props; -export const OwnerPicker = ({ - schema: { title = 'Owner', description = 'The owner of the component' }, - uiSchema, - ...props -}: FieldProps) => { const ownerUiSchema = { ...uiSchema, 'ui:options': { @@ -35,7 +46,7 @@ export const OwnerPicker = ({ return ( diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts b/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts index 801ed59c93..b8cb97eb97 100644 --- a/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts +++ b/plugins/scaffolder/src/components/fields/OwnerPicker/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { OwnerPicker } from './OwnerPicker'; +export type { OwnerPickerUiOptions } from './OwnerPicker'; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx index 829495cdda..22043d5761 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.test.tsx @@ -23,7 +23,9 @@ import { scmAuthApiRef, ScmAuthApi, } from '@backstage/integration-react'; -import { scaffolderApiRef, ScaffolderApi } from '../../../api'; +import { scaffolderApiRef } from '../../../api'; +import { ScaffolderApi } from '../../../types'; + import { SecretsContextProvider, SecretsContext, @@ -32,10 +34,12 @@ import { act, fireEvent } from '@testing-library/react'; describe('RepoUrlPicker', () => { const mockScaffolderApi: Partial = { - getIntegrationsList: async () => [ - { host: 'github.com', type: 'github', title: 'github.com' }, - { host: 'dev.azure.com', type: 'azure', title: 'dev.azure.com' }, - ], + getIntegrationsList: async () => ({ + integrations: [ + { host: 'github.com', type: 'github', title: 'github.com' }, + { host: 'dev.azure.com', type: 'azure', title: 'dev.azure.com' }, + ], + }), }; const mockIntegrationsApi: Partial = { diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index 1ccaed4e86..17fd5ef665 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -44,6 +44,9 @@ export interface RepoUrlPickerUiOptions { }; } +/** + * Repo Url Picker + */ export const RepoUrlPicker = ( props: FieldExtensionComponentProps, ) => { @@ -53,7 +56,7 @@ export const RepoUrlPicker = ( ); const integrationApi = useApi(scmIntegrationsApiRef); const scmAuthApi = useApi(scmAuthApiRef); - const { setSecret } = useTemplateSecrets(); + const { setSecrets } = useTemplateSecrets(); const allowedHosts = useMemo( () => uiSchema?.['ui:options']?.allowedHosts ?? [], [uiSchema], @@ -111,7 +114,7 @@ export const RepoUrlPicker = ( // set the secret using the key provided in the the ui:options for use // in the templating the manifest with ${{ secrets[secretsKey] }} - setSecret({ [requestUserCredentials.secretsKey]: token }); + setSecrets({ [requestUserCredentials.secretsKey]: token }); }, 500, [state, uiSchema], diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx index e151ddbbc7..bd71fb160f 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.test.tsx @@ -23,11 +23,11 @@ describe('RepoUrlPickerHostField', () => { it('renders the default host properly', async () => { const mockOnChange = jest.fn(); const mockScaffolderApi = { - getIntegrationsList: jest - .fn() - .mockResolvedValue([ + getIntegrationsList: jest.fn().mockResolvedValue({ + integrations: [ { host: 'github.com', title: 'github.com', type: 'github' }, - ]), + ], + }), }; const { getByText } = await renderInTestApp( @@ -46,10 +46,12 @@ describe('RepoUrlPickerHostField', () => { it('should provide a dropdown when multiple hosts are returned that can be selected', async () => { const mockOnChange = jest.fn(); const mockScaffolderApi = { - getIntegrationsList: jest.fn().mockResolvedValue([ - { host: 'github.com', title: 'github.com', type: 'github' }, - { host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' }, - ]), + getIntegrationsList: jest.fn().mockResolvedValue({ + integrations: [ + { host: 'github.com', title: 'github.com', type: 'github' }, + { host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' }, + ], + }), }; const { getByRole, getByText, getByTestId } = await renderInTestApp( @@ -73,10 +75,12 @@ describe('RepoUrlPickerHostField', () => { it('should not display hosts that dont have integration config set correctly', async () => { const mockOnChange = jest.fn(); const mockScaffolderApi = { - getIntegrationsList: jest.fn().mockResolvedValue([ - { host: 'github.com', title: 'github.com', type: 'github' }, - { host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' }, - ]), + getIntegrationsList: jest.fn().mockResolvedValue({ + integrations: [ + { host: 'github.com', title: 'github.com', type: 'github' }, + { host: 'gitlab.com', title: 'gitlab.com', type: 'gitlab' }, + ], + }), }; const { getByRole, getByText, getByTestId } = await renderInTestApp( diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx index ec09494a1b..b9762cd69d 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPickerHost.tsx @@ -30,11 +30,13 @@ export const RepoUrlPickerHost = (props: { const { host, hosts, onChange, rawErrors } = props; const scaffolderApi = useApi(scaffolderApiRef); - const { value: integrations, loading } = useAsync(async () => { - return await scaffolderApi.getIntegrationsList({ - allowedHosts: hosts ?? [], - }); - }); + const { value: { integrations } = { integrations: [] }, loading } = useAsync( + async () => { + return await scaffolderApi.getIntegrationsList({ + allowedHosts: hosts ?? [], + }); + }, + ); useEffect(() => { // If there is no host chosen currently diff --git a/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx b/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx deleted file mode 100644 index 7bed7eedb9..0000000000 --- a/plugins/scaffolder/src/components/fields/TextValuePicker/TextValuePicker.tsx +++ /dev/null @@ -1,42 +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 { TextField } from '@material-ui/core'; -import { FieldProps } from '@rjsf/core'; -import React from 'react'; - -export const TextValuePicker = ({ - onChange, - required, - schema: { title, description }, - rawErrors, - formData, - uiSchema: { 'ui:autofocus': autoFocus }, - idSchema, - placeholder, -}: FieldProps) => ( - onChange(value)} - margin="normal" - error={rawErrors?.length > 0 && !formData} - inputProps={{ autoFocus }} - /> -); diff --git a/plugins/scaffolder/src/components/fields/index.ts b/plugins/scaffolder/src/components/fields/index.ts index ecde218029..6358934025 100644 --- a/plugins/scaffolder/src/components/fields/index.ts +++ b/plugins/scaffolder/src/components/fields/index.ts @@ -13,10 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export * from './EntityNamePicker'; export * from './EntityPicker'; export * from './OwnerPicker'; export * from './RepoUrlPicker'; -export * from './TextValuePicker'; export * from './OwnedEntityPicker'; export * from './EntityTagsPicker'; diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts index 7414a35189..8ff9a30d0b 100644 --- a/plugins/scaffolder/src/components/hooks/useEventStream.ts +++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts @@ -15,14 +15,19 @@ */ import { useImmerReducer } from 'use-immer'; import { useEffect } from 'react'; -import { scaffolderApiRef, LogEvent } from '../../api'; -import { ScaffolderTask, Status, TaskOutput } from '../../types'; +import { scaffolderApiRef } from '../../api'; +import { + ScaffolderTask, + ScaffolderTaskStatus, + ScaffolderTaskOutput, + LogEvent, +} from '../../types'; import { useApi } from '@backstage/core-plugin-api'; import { Subscription } from '@backstage/types'; type Step = { id: string; - status: Status; + status: ScaffolderTaskStatus; endedAt?: string; startedAt?: string; }; @@ -34,16 +39,16 @@ export type TaskStream = { completed: boolean; task?: ScaffolderTask; steps: { [stepId in string]: Step }; - output?: TaskOutput; + output?: ScaffolderTaskOutput; }; type ReducerLogEntry = { createdAt: string; body: { stepId?: string; - status?: Status; + status?: ScaffolderTaskStatus; message: string; - output?: TaskOutput; + output?: ScaffolderTaskOutput; }; }; diff --git a/plugins/scaffolder/src/components/index.ts b/plugins/scaffolder/src/components/index.ts new file mode 100644 index 0000000000..030c485fd7 --- /dev/null +++ b/plugins/scaffolder/src/components/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright 2022 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 './fields'; +export type { RepoUrlPickerUiOptions } from './fields'; +export { FavouriteTemplate } from './FavouriteTemplate'; +export { TemplateList } from './TemplateList'; +export type { TemplateListProps } from './TemplateList'; +export { TemplateTypePicker } from './TemplateTypePicker'; +export * from './secrets'; +export { TaskPage } from './TaskPage'; +export type { RouterProps } from './Router'; diff --git a/plugins/scaffolder/src/components/secrets/SecretsContext.test.tsx b/plugins/scaffolder/src/components/secrets/SecretsContext.test.tsx index 37d35c9015..3b84c074d7 100644 --- a/plugins/scaffolder/src/components/secrets/SecretsContext.test.tsx +++ b/plugins/scaffolder/src/components/secrets/SecretsContext.test.tsx @@ -36,7 +36,7 @@ describe('SecretsContext', () => { ); expect(result.current.context?.secrets.foo).toEqual(undefined); - act(() => result.current.hook.setSecret({ foo: 'bar' })); + act(() => result.current.hook.setSecrets({ foo: 'bar' })); expect(result.current.context?.secrets.foo).toEqual('bar'); }); diff --git a/plugins/scaffolder/src/components/secrets/SecretsContext.tsx b/plugins/scaffolder/src/components/secrets/SecretsContext.tsx index 6570f9d901..f0b62f20a1 100644 --- a/plugins/scaffolder/src/components/secrets/SecretsContext.tsx +++ b/plugins/scaffolder/src/components/secrets/SecretsContext.tsx @@ -48,11 +48,21 @@ export const SecretsContextProvider = ({ children }: PropsWithChildren<{}>) => { ); }; +/** + * The return type from the useTemplateSecrets hook. + * @public + */ +export interface ScaffolderUseTemplateSecrets { + /** @deprecated use setSecrets instead */ + setSecret: (input: Record) => void; + setSecrets: (input: Record) => void; +} + /** * Hook to access the secrets context. * @public */ -export const useTemplateSecrets = () => { +export const useTemplateSecrets = (): ScaffolderUseTemplateSecrets => { const value = useContext(SecretsContext); if (!value) { throw new Error( @@ -60,14 +70,14 @@ export const useTemplateSecrets = () => { ); } - const { setSecrets } = value; + const { setSecrets: updateSecrets } = value; - const setSecret = useCallback( + const setSecrets = useCallback( (input: Record) => { - setSecrets(currentSecrets => ({ ...currentSecrets, ...input })); + updateSecrets(currentSecrets => ({ ...currentSecrets, ...input })); }, - [setSecrets], + [updateSecrets], ); - return { setSecret }; + return { setSecret: setSecrets, setSecrets }; }; diff --git a/plugins/scaffolder/src/components/secrets/index.ts b/plugins/scaffolder/src/components/secrets/index.ts index 44adb2a00a..c608834803 100644 --- a/plugins/scaffolder/src/components/secrets/index.ts +++ b/plugins/scaffolder/src/components/secrets/index.ts @@ -14,3 +14,4 @@ * limitations under the License. */ export { useTemplateSecrets } from './SecretsContext'; +export type { ScaffolderUseTemplateSecrets } from './SecretsContext'; diff --git a/plugins/scaffolder/src/extensions/default.ts b/plugins/scaffolder/src/extensions/default.ts index 820cc0e6d3..10c0746e41 100644 --- a/plugins/scaffolder/src/extensions/default.ts +++ b/plugins/scaffolder/src/extensions/default.ts @@ -27,31 +27,32 @@ import { import { FieldExtensionOptions } from './types'; import { OwnedEntityPicker } from '../components/fields/OwnedEntityPicker'; -export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS: FieldExtensionOptions[] = [ - { - component: EntityPicker, - name: 'EntityPicker', - }, - { - component: EntityNamePicker, - name: 'EntityNamePicker', - validation: entityNamePickerValidation, - }, - { - component: EntityTagsPicker, - name: 'EntityTagsPicker', - }, - { - component: RepoUrlPicker, - name: 'RepoUrlPicker', - validation: repoPickerValidation, - }, - { - component: OwnerPicker, - name: 'OwnerPicker', - }, - { - component: OwnedEntityPicker, - name: 'OwnedEntityPicker', - }, -]; +export const DEFAULT_SCAFFOLDER_FIELD_EXTENSIONS: FieldExtensionOptions[] = + [ + { + component: EntityPicker, + name: 'EntityPicker', + }, + { + component: EntityNamePicker, + name: 'EntityNamePicker', + validation: entityNamePickerValidation, + }, + { + component: EntityTagsPicker, + name: 'EntityTagsPicker', + }, + { + component: RepoUrlPicker, + name: 'RepoUrlPicker', + validation: repoPickerValidation, + }, + { + component: OwnerPicker, + name: 'OwnerPicker', + }, + { + component: OwnedEntityPicker, + name: 'OwnedEntityPicker', + }, + ]; diff --git a/plugins/scaffolder/src/extensions/index.tsx b/plugins/scaffolder/src/extensions/index.tsx index f9c167a6b0..53db4cd927 100644 --- a/plugins/scaffolder/src/extensions/index.tsx +++ b/plugins/scaffolder/src/extensions/index.tsx @@ -25,8 +25,13 @@ import { Extension, attachComponentData } from '@backstage/core-plugin-api'; export const FIELD_EXTENSION_WRAPPER_KEY = 'scaffolder.extensions.wrapper.v1'; export const FIELD_EXTENSION_KEY = 'scaffolder.extensions.field.v1'; -export function createScaffolderFieldExtension( - options: FieldExtensionOptions, +export function createScaffolderFieldExtension< + TReturnValue = unknown, + TInputProps = unknown, +>( + options: FieldExtensionOptions, + // TODO: need know how to embed these types nicely so the api report looks nice. + // then we can remove the export of the components ): Extension<() => null> { return { expose() { @@ -43,7 +48,9 @@ export function createScaffolderFieldExtension( }; } -export const ScaffolderFieldExtensions: React.ComponentType = () => null; +export const ScaffolderFieldExtensions: React.ComponentType = + (): JSX.Element | null => null; + attachComponentData( ScaffolderFieldExtensions, FIELD_EXTENSION_WRAPPER_KEY, diff --git a/plugins/scaffolder/src/extensions/types.ts b/plugins/scaffolder/src/extensions/types.ts index e1da9889e7..574ce7ea64 100644 --- a/plugins/scaffolder/src/extensions/types.ts +++ b/plugins/scaffolder/src/extensions/types.ts @@ -21,13 +21,11 @@ import { FieldValidation, FieldProps } from '@rjsf/core'; * * @public */ -export type CustomFieldValidator = - | ((data: T, field: FieldValidation) => void) - | (( - data: T, - field: FieldValidation, - context: { apiHolder: ApiHolder }, - ) => void); +export type CustomFieldValidator = ( + data: TFieldReturnValue, + field: FieldValidation, + context: { apiHolder: ApiHolder }, +) => void; /** * Type for the Custom Field Extension with the @@ -35,10 +33,13 @@ export type CustomFieldValidator = * * @public */ -export type FieldExtensionOptions = { +export type FieldExtensionOptions< + TFieldReturnValue = unknown, + TProps = FieldProps, +> = { name: string; - component: (props: FieldProps) => JSX.Element | null; - validation?: CustomFieldValidator; + component: (props: TProps) => JSX.Element | null; + validation?: CustomFieldValidator; }; /** @@ -48,10 +49,10 @@ export type FieldExtensionOptions = { * @public */ export interface FieldExtensionComponentProps< - ReturnValue, - UiOptions extends {} = {}, -> extends FieldProps { - uiSchema: { - 'ui:options'?: UiOptions; + TFieldReturnValue, + TUiOptions extends {} = {}, +> extends FieldProps { + uiSchema: FieldProps['uiSchema'] & { + 'ui:options'?: TUiOptions; }; } diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index adb2aab035..71cd1d8f84 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -21,7 +21,21 @@ */ export { scaffolderApiRef, ScaffolderClient } from './api'; -export type { ScaffolderApi } from './api'; +export type { + JobStatus, + ListActionsResponse, + LogEvent, + ScaffolderApi, + ScaffolderGetIntegrationsListOptions, + ScaffolderGetIntegrationsListResponse, + ScaffolderScaffoldOptions, + ScaffolderScaffoldResponse, + ScaffolderStreamLogsOptions, + ScaffolderTask, + ScaffolderTaskOutput, + ScaffolderTaskStatus, + TemplateParameterSchema, +} from './types'; export { createScaffolderFieldExtension, ScaffolderFieldExtensions, @@ -39,23 +53,7 @@ export { OwnedEntityPickerFieldExtension, RepoUrlPickerFieldExtension, ScaffolderPage, - scaffolderPlugin as plugin, scaffolderPlugin, } from './plugin'; -export { - EntityNamePicker, - EntityPicker, - EntityTagsPicker, - OwnerPicker, - RepoUrlPicker, - TextValuePicker, - OwnedEntityPicker, -} from './components/fields'; -export type { RepoUrlPickerUiOptions } from './components/fields'; -export { FavouriteTemplate } from './components/FavouriteTemplate'; -export { TemplateList } from './components/TemplateList'; -export type { TemplateListProps } from './components/TemplateList'; -export { TemplateTypePicker } from './components/TemplateTypePicker'; -export * from './components/secrets'; -export { TaskPage } from './components/TaskPage'; +export * from './components'; export type { TaskPageProps } from './components/TaskPage'; diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index df5868c9ea..97f942d8b9 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -16,6 +16,7 @@ import { createExternalRouteRef, createRouteRef, + createSubRouteRef, } from '@backstage/core-plugin-api'; export const registerComponentRouteRef = createExternalRouteRef({ @@ -26,3 +27,21 @@ export const registerComponentRouteRef = createExternalRouteRef({ export const rootRouteRef = createRouteRef({ id: 'scaffolder', }); + +export const selectedTemplateRouteRef = createSubRouteRef({ + id: 'scaffolder/selected-template', + parent: rootRouteRef, + path: '/templates/:templateName', +}); + +export const scaffolderTaskRouteRef = createSubRouteRef({ + id: 'scaffolder/task', + parent: rootRouteRef, + path: '/tasks/:taskId', +}); + +export const actionsRouteRef = createSubRouteRef({ + id: 'scaffolder/actions', + parent: rootRouteRef, + path: '/actions', +}); diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 291f680710..0ffbd601a8 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -13,31 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { JSONSchema } from '@backstage/catalog-model'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { JsonObject, JsonValue, Observable } from '@backstage/types'; +import { JSONSchema7 } from 'json-schema'; + +export type ScaffolderTaskStatus = + | 'open' + | 'processing' + | 'failed' + | 'completed' + | 'skipped'; -export type Status = 'open' | 'processing' | 'failed' | 'completed' | 'skipped'; export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED'; -export type Job = { - id: string; - metadata: { - entity: any; - values: any; - remoteUrl?: string; - catalogInfoUrl?: string; - }; - status: JobStatus; - stages: Stage[]; - error?: Error; -}; - -export type Stage = { - name: string; - log: string[]; - status: JobStatus; - startedAt: string; - endedAt?: string; -}; export type ScaffolderTask = { id: string; @@ -51,24 +38,100 @@ export type ListActionsResponse = Array<{ id: string; description?: string; schema?: { - input?: JSONSchema; - output?: JSONSchema; + input?: JSONSchema7; + output?: JSONSchema7; }; }>; -type OutputLink = { +type ScaffolderOutputLink = { title?: string; icon?: string; url?: string; entityRef?: string; }; -export type TaskOutput = { +export type ScaffolderTaskOutput = { /** @deprecated use the `links` property to link out to relevant resources */ entityRef?: string; /** @deprecated use the `links` property to link out to relevant resources */ remoteUrl?: string; - links?: OutputLink[]; + links?: ScaffolderOutputLink[]; } & { [key: string]: unknown; }; + +export type TemplateParameterSchema = { + title: string; + steps: Array<{ + title: string; + schema: JsonObject; + }>; +}; + +export type LogEvent = { + type: 'log' | 'completion'; + body: { + message: string; + stepId?: string; + status?: ScaffolderTaskStatus; + }; + createdAt: string; + id: string; + taskId: string; +}; + +export interface ScaffolderScaffoldOptions { + templateRef: string; + values: Record; + secrets?: Record; +} + +export interface ScaffolderScaffoldResponse { + taskId: string; +} + +export interface ScaffolderGetIntegrationsListOptions { + allowedHosts: string[]; +} + +export interface ScaffolderGetIntegrationsListResponse { + integrations: { type: string; title: string; host: string }[]; +} + +export interface ScaffolderStreamLogsOptions { + taskId: string; + after?: number; +} +/** + * An API to interact with the scaffolder backend. + * + * @public + */ +export interface ScaffolderApi { + getTemplateParameterSchema( + templateRef: string, + ): Promise; + + /** + * Executes the scaffolding of a component, given a template and its + * parameter values. + * + * @param options - The {@link ScaffolderScaffoldOptions} the scaffolding. + */ + scaffold( + options: ScaffolderScaffoldOptions, + ): Promise; + + getTask(taskId: string): Promise; + + getIntegrationsList( + options: ScaffolderGetIntegrationsListOptions, + ): Promise; + + /** + * Returns a list of all installed actions. + */ + listActions(): Promise; + + streamLogs(options: ScaffolderStreamLogsOptions): Observable; +} diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 5dc394be6c..b68824932e 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 0.0.10 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/config@0.1.15 + - @backstage/search-common@0.2.4 + +## 0.0.9 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 4c0332e55c: chore(deps-dev): bump `@elastic/elasticsearch-mock` from 0.3.0 to 1.0.0 +- Updated dependencies + - @backstage/config@0.1.14 + - @backstage/search-common@0.2.3 + ## 0.0.8 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index 8a0e0d9d2c..2a2dbd6f85 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", "description": "A module for the search backend that implements search using ElasticSearch", - "version": "0.0.8", + "version": "0.0.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,18 +10,21 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin-module" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/config": "^0.1.13", - "@backstage/search-common": "^0.2.2", + "@backstage/config": "^0.1.15", + "@backstage/search-common": "^0.2.4", "@elastic/elasticsearch": "7.13.0", "@acuris/aws-es-connection": "^2.2.0", "aws-sdk": "^2.948.0", @@ -30,8 +33,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-common": "^0.10.7-next.0", - "@backstage/cli": "^0.13.2-next.0", + "@backstage/backend-common": "^0.11.0", + "@backstage/cli": "^0.14.1", "@elastic/elasticsearch-mock": "^1.0.0" }, "files": [ diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index c0b1f6778d..08986f6d34 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-search-backend-module-pg +## 0.2.9 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + +## 0.2.8 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/search-common@0.2.4 + - @backstage/plugin-search-backend-node@0.4.7 + +## 0.2.7 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/search-common@0.2.3 + - @backstage/plugin-search-backend-node@0.4.6 + ## 0.2.6 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 392b9af73a..d006c4e675 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-pg", "description": "A module for the search backend that implements search using PostgreSQL", - "version": "0.2.6", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,25 +10,28 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin-module" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/search-common": "^0.2.2", - "@backstage/plugin-search-backend-node": "^0.4.5", + "@backstage/backend-common": "^0.11.0", + "@backstage/search-common": "^0.2.4", + "@backstage/plugin-search-backend-node": "^0.4.7", "lodash": "^4.17.21", "knex": "^1.0.2" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.17", - "@backstage/cli": "^0.13.2" + "@backstage/backend-test-utils": "^0.1.19", + "@backstage/cli": "^0.14.1" }, "files": [ "dist", diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 6f664c086c..864376a7e7 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-search-backend-node +## 0.4.7 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/search-common@0.2.4 + +## 0.4.6 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/search-common@0.2.3 + ## 0.4.5 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index e2d83d0f4f..dd8ef5599a 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-node", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", - "version": "0.4.5", + "version": "0.4.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,24 +10,27 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "node-library" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/search-common": "^0.2.2", + "@backstage/search-common": "^0.2.4", "winston": "^3.2.1", "lunr": "^2.3.9", "@types/lunr": "^2.3.3" }, "devDependencies": { - "@backstage/backend-common": "^0.10.7-next.0", - "@backstage/cli": "^0.13.2-next.0" + "@backstage/backend-common": "^0.11.0", + "@backstage/cli": "^0.14.1" }, "files": [ "dist" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 218615c66b..a802848287 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-search-backend +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-permission-node@0.5.2 + - @backstage/plugin-auth-node@0.1.3 + +## 0.4.4 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/search-common@0.2.4 + - @backstage/types@0.1.3 + - @backstage/plugin-auth-node@0.1.2 + - @backstage/plugin-permission-common@0.5.1 + - @backstage/plugin-permission-node@0.5.1 + - @backstage/plugin-search-backend-node@0.4.7 + +## 0.4.3 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/plugin-auth-node@0.1.1 + - @backstage/plugin-permission-common@0.5.0 + - @backstage/config@0.1.14 + - @backstage/search-common@0.2.3 + - @backstage/types@0.1.2 + - @backstage/plugin-permission-node@0.5.0 + - @backstage/plugin-search-backend-node@0.4.6 + ## 0.4.2 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 117e484c62..cdfb1afead 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend", "description": "The Backstage backend plugin that provides your backstage app with search", - "version": "0.4.2", + "version": "0.4.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,25 +10,28 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/search-common": "^0.2.2", - "@backstage/plugin-auth-node": "^0.1.0", - "@backstage/plugin-permission-common": "^0.4.0-next.0", - "@backstage/plugin-permission-node": "^0.4.3", - "@backstage/plugin-search-backend-node": "^0.4.5", - "@backstage/types": "^0.1.1", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/search-common": "^0.2.4", + "@backstage/plugin-auth-node": "^0.1.3", + "@backstage/plugin-permission-common": "^0.5.1", + "@backstage/plugin-permission-node": "^0.5.2", + "@backstage/plugin-search-backend-node": "^0.4.7", + "@backstage/types": "^0.1.3", "@types/express": "^4.17.6", "dataloader": "^2.0.0", "express": "^4.17.1", @@ -40,7 +43,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index bb4877b6da..a0e80eaeee 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,50 @@ # @backstage/plugin-search +## 0.7.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.7.0 + +### Minor Changes + +- f986369b2a: **BREAKING**: `useSearch` doesn't return anymore `open` and `toggleModal`. + The two properties have been moved to the `useSearchModal` hook. + + ``` + import { SearchModal, useSearchModal } from '@backstage/plugin-search'; + + const Foo = () => { + const { state, setOpen, toggleModal } = useSearchModal(); + + return ( + + ); + }; + ``` + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/search-common@0.2.3 + - @backstage/theme@0.2.15 + - @backstage/types@0.1.2 + ## 0.6.2 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 34baf41306..1f7f9af8c1 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search", "description": "The Backstage plugin that provides your backstage app with search", - "version": "0.6.2", + "version": "0.7.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -20,25 +23,25 @@ "backstage" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/search-common": "^0.2.2", - "@backstage/theme": "^0.2.14", - "@backstage/types": "^0.1.1", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/search-common": "^0.2.4", + "@backstage/theme": "^0.2.15", + "@backstage/types": "^0.1.3", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -53,10 +56,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 5f87df9a3a..44f20e05d1 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-sentry +## 0.3.38 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.3.37 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.3.36 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 48137dc5e1..1d6db3a846 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry", "description": "A Backstage plugin that integrates towards Sentry", - "version": "0.3.36", + "version": "0.3.38", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,21 +25,21 @@ "sentry" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -49,10 +52,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index 11bc03104a..f60d458dd0 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-shortcuts +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.0 + +### Minor Changes + +- dc9fd0acf9: Use Avatar instead of custom icon for ShortcutIcon + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/theme@0.2.15 + - @backstage/types@0.1.2 + ## 0.1.22 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index d3bcb14f15..2407f4b84b 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-shortcuts", "description": "A Backstage plugin that provides a shortcuts feature to the sidebar", - "version": "0.1.22", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,21 +10,24 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/theme": "^0.2.14", - "@backstage/types": "^0.1.1", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/theme": "^0.2.15", + "@backstage/types": "^0.1.3", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -39,10 +42,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/shortcuts/src/ShortcutIcon.tsx b/plugins/shortcuts/src/ShortcutIcon.tsx index 94a894fcb0..be78928452 100644 --- a/plugins/shortcuts/src/ShortcutIcon.tsx +++ b/plugins/shortcuts/src/ShortcutIcon.tsx @@ -15,33 +15,28 @@ */ import React from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import Avatar from '@material-ui/core/Avatar'; type Props = { text: string; color: string; }; -export const ShortcutIcon = ({ text, color }: Props) => { - const size = 28; - return ( - - - - {text} - - - ); +const useStyles = makeStyles(theme => ({ + avatar: (props: Props) => ({ + color: theme.palette.getContrastText(props.color), + backgroundColor: props.color, + width: 28, + height: 28, + fontWeight: 'bold', + fontSize: 13, + filter: 'contrast(150%) brightness(1.4)', + }), +})); + +export const ShortcutIcon = (props: Props) => { + const classes = useStyles(props); + + return {props.text}; }; diff --git a/plugins/shortcuts/src/ShortcutItem.test.tsx b/plugins/shortcuts/src/ShortcutItem.test.tsx index e080026f00..528eb13efa 100644 --- a/plugins/shortcuts/src/ShortcutItem.test.tsx +++ b/plugins/shortcuts/src/ShortcutItem.test.tsx @@ -24,7 +24,6 @@ import { renderInTestApp, wrapInTestApp, } from '@backstage/test-utils'; -import { pageTheme } from '@backstage/theme'; import { SidebarContext } from '@backstage/core-components'; describe('ShortcutItem', () => { @@ -77,27 +76,4 @@ describe('ShortcutItem', () => { expect(screen.getByText('MT')).toBeInTheDocument(); }); }); - - it('gets the color based on the theme', async () => { - const { rerender } = await renderInTestApp( - , - ); - - expect(document.querySelector('circle')?.getAttribute('fill')).toEqual( - pageTheme.tool.colors[0], - ); - - const newShortcut: Shortcut = { - id: 'id', - url: '/catalog', - title: 'some title', - }; - rerender(wrapInTestApp()); - - await waitFor(() => { - expect(document.querySelector('circle')?.getAttribute('fill')).toEqual( - pageTheme.home.colors[0], - ); - }); - }); }); diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index c0de393aab..53209faa93 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-sonarqube +## 0.3.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.2.17 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.2.16 ### Patch Changes diff --git a/plugins/sonarqube/api-report.md b/plugins/sonarqube/api-report.md index b23ed65ddd..a7549a689c 100644 --- a/plugins/sonarqube/api-report.md +++ b/plugins/sonarqube/api-report.md @@ -16,7 +16,6 @@ export const EntitySonarQubeCard: ({ variant, duplicationRatings, }: { - entity?: Entity | undefined; variant?: InfoCardVariants | undefined; duplicationRatings?: | { @@ -38,7 +37,6 @@ export const SonarQubeCard: ({ variant, duplicationRatings, }: { - entity?: Entity | undefined; variant?: InfoCardVariants | undefined; duplicationRatings?: DuplicationRating[] | undefined; }) => JSX.Element; @@ -52,5 +50,5 @@ export { sonarQubePlugin }; // Warnings were encountered during analysis: // -// src/components/SonarQubeCard/SonarQubeCard.d.ts:11:5 - (ae-forgotten-export) The symbol "DuplicationRating" needs to be exported by the entry point index.d.ts +// src/components/SonarQubeCard/SonarQubeCard.d.ts:9:5 - (ae-forgotten-export) The symbol "DuplicationRating" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index fb2de64ffe..38aada552f 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sonarqube", "description": "", - "version": "0.2.16", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -23,21 +26,21 @@ "sonarcloud" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -50,10 +53,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx index ca3ddbda76..4527c2a3e3 100644 --- a/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx +++ b/plugins/sonarqube/src/components/SonarQubeCard/SonarQubeCard.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; import { Chip, Grid } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; @@ -90,7 +89,6 @@ export const SonarQubeCard = ({ variant = 'gridItem', duplicationRatings = defaultDuplicationRatings, }: { - entity?: Entity; variant?: InfoCardVariants; duplicationRatings?: DuplicationRating[]; }) => { diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index badc0f06fc..8667f46749 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,29 @@ # @backstage/plugin-splunk-on-call +## 0.3.24 + +### Patch Changes + +- 02faa6781b: The Splunk On-Call plugin now supports an optional `readOnly` property (``) for suppressing the rendering of incident trigger-acknowledge-resolve controls from the Backstage UI. +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.3.23 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + ## 0.3.22 ### Patch Changes diff --git a/plugins/splunk-on-call/README.md b/plugins/splunk-on-call/README.md index 0200cacc50..5078dfa38b 100644 --- a/plugins/splunk-on-call/README.md +++ b/plugins/splunk-on-call/README.md @@ -45,6 +45,14 @@ const overviewContent = ( ``` +### `readOnly` mode + +To suppress the rendering of the actionable create-acknowledge-resolve incident buttons and UI controls, the `EntitySplunkOnCallCard` can also be instantiated in `readOnly` mode: + +```ts + +``` + ## Client configuration In order to be able to perform certain actions (create-acknowledge-resolve an action), you need to provide a REST Endpoint. @@ -74,6 +82,19 @@ proxy: In addition, to make certain API calls (trigger-resolve-acknowledge an incident) you need to add the `PATCH` method to the backend `cors` methods list: `[GET, POST, PUT, DELETE, PATCH]`. +**WARNING**: In current implementation, the Splunk OnCall plugin requires the `/splunk-on-call` proxy endpoint be exposed by the Backstage backend as an unprotected endpoint, in effect enabling Splunk OnCall API access using the configured `SPLUNK_ON_CALL_API_KEY` for any user or process with access to the `/splunk-on-call` Backstage backend endpoint. See below for further configuration options enabling protection of this endpoint. If you regard this as problematic, consider using the plugin in `readOnly` mode (``) using the following proxy configuration: + +```yaml +proxy: + '/splunk-on-call': + target: https://api.victorops.com/api-public + headers: + X-VO-Api-Id: ${SPLUNK_ON_CALL_API_ID} + X-VO-Api-Key: ${SPLUNK_ON_CALL_API_KEY} + # prohibit the `/splunk-on-call` proxy endpoint from servicing non-GET requests + allowedMethods: ['GET'] +``` + ### Adding your team name to the entity annotation The information displayed for each entity is based on either an associated team name or an associated routing key. diff --git a/plugins/splunk-on-call/api-report.md b/plugins/splunk-on-call/api-report.md index 8e8076d9e8..60ec38ca6d 100644 --- a/plugins/splunk-on-call/api-report.md +++ b/plugins/splunk-on-call/api-report.md @@ -12,10 +12,13 @@ import { DiscoveryApi } from '@backstage/core-plugin-api'; import { Entity } from '@backstage/catalog-model'; import { RouteRef } from '@backstage/core-plugin-api'; +// Warning: (ae-forgotten-export) The symbol "EntitySplunkOnCallCardProps" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "EntitySplunkOnCallCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntitySplunkOnCallCard: () => JSX.Element; +export const EntitySplunkOnCallCard: ( + props: EntitySplunkOnCallCardProps, +) => JSX.Element; // Warning: (ae-missing-release-tag) "isSplunkOnCallAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 1bc8948d95..7d8f6b5bdf 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-splunk-on-call", "description": "A Backstage plugin that integrates towards Splunk On-Call", - "version": "0.3.22", + "version": "0.3.24", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,21 +24,21 @@ "splunk-on-call" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -48,10 +51,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx b/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx index a83db136e4..0688fb8237 100644 --- a/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx +++ b/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx @@ -131,6 +131,32 @@ describe('SplunkOnCallCard', () => { expect(getByText('Empty escalation policy')).toBeInTheDocument(); }); + it('does not render a "Create incident" link in read only mode', async () => { + mockSplunkOnCallApi.getUsers = jest + .fn() + .mockImplementationOnce(async () => [MOCKED_USER]); + mockSplunkOnCallApi.getTeams = jest + .fn() + .mockImplementation(async () => [MOCK_TEAM_NO_INCIDENTS]); + + const { getByText, queryByTestId } = render( + wrapInTestApp( + + + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + expect(() => getByText('Create Incident')).toThrow(); + await waitFor( + () => expect(getByText('Nice! No incidents found!')).toBeInTheDocument(), + { timeout: 2000 }, + ); + expect(getByText('Empty escalation policy')).toBeInTheDocument(); + }); + it('handles a "splunk.com/on-call-routing-key" annotation', async () => { mockSplunkOnCallApi.getUsers = jest .fn() diff --git a/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.tsx b/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.tsx index 4a6f983636..4f6bcd9a0e 100644 --- a/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.tsx +++ b/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.tsx @@ -109,7 +109,14 @@ const useStyles = makeStyles({ }, }); -export const EntitySplunkOnCallCard = () => { +/** @public */ +export type EntitySplunkOnCallCardProps = { + readOnly?: boolean; +}; + +/** @public */ +export const EntitySplunkOnCallCard = (props: EntitySplunkOnCallCardProps) => { + const { readOnly } = props; const classes = useStyles(); const config = useApi(configApiRef); const api = useApi(splunkOnCallApiRef); @@ -218,7 +225,11 @@ export const EntitySplunkOnCallCard = () => { return ( <> - + {usersHashMap && team && ( )} @@ -259,7 +270,7 @@ export const EntitySplunkOnCallCard = () => { , , ]} /> diff --git a/plugins/splunk-on-call/src/components/Incident/IncidentListItem.tsx b/plugins/splunk-on-call/src/components/Incident/IncidentListItem.tsx index 4c9975bd06..d8b6a56efc 100644 --- a/plugins/splunk-on-call/src/components/Incident/IncidentListItem.tsx +++ b/plugins/splunk-on-call/src/components/Incident/IncidentListItem.tsx @@ -64,6 +64,7 @@ type Props = { team: string; incident: Incident; onIncidentAction: () => void; + readOnly: boolean; }; const IncidentPhaseStatus = ({ @@ -135,6 +136,7 @@ const IncidentAction = ({ export const IncidentListItem = ({ incident, + readOnly, onIncidentAction, team, }: Props) => { @@ -241,12 +243,14 @@ export const IncidentListItem = ({ {incident.incidentLink && incident.incidentNumber && ( - + {!readOnly && ( + + )} { const { getByText, queryByTestId } = render( wrapInTestApp( - + , ), ); @@ -68,7 +68,7 @@ describe('Incidents', () => { } = render( wrapInTestApp( - + , ), ); @@ -90,6 +90,40 @@ describe('Incidents', () => { expect(getAllByTitle('View in Splunk On-Call').length).toEqual(1); }); + it('does not render incident action buttons in read only mode', async () => { + mockSplunkOnCallApi.getIncidents.mockResolvedValue([MOCK_INCIDENT]); + mockSplunkOnCallApi.getTeams.mockResolvedValue([MOCK_TEAM]); + + const { getByText, getAllByTitle, getByLabelText, queryByTestId } = render( + wrapInTestApp( + + + , + ), + ); + await waitFor(() => !queryByTestId('progress')); + await waitFor( + () => + expect( + getByText('user', { + exact: false, + }), + ).toBeInTheDocument(), + { timeout: 2000 }, + ); + expect(getByText('test-incident')).toBeInTheDocument(); + expect(getByLabelText('Status warning')).toBeInTheDocument(); + expect(() => getAllByTitle('Acknowledge')).toThrow( + 'Unable to find an element with the title: Acknowledge.', + ); + expect(() => getAllByTitle('Resolve')).toThrow( + 'Unable to find an element with the title: Resolve.', + ); + + // assert links, mailto and hrefs, date calculation + expect(getAllByTitle('View in Splunk On-Call').length).toEqual(1); + }); + it('Handle errors', async () => { mockSplunkOnCallApi.getIncidents.mockRejectedValueOnce( new Error('Error occurred'), @@ -99,7 +133,7 @@ describe('Incidents', () => { const { getByText, queryByTestId } = render( wrapInTestApp( - + , ), ); diff --git a/plugins/splunk-on-call/src/components/Incident/Incidents.tsx b/plugins/splunk-on-call/src/components/Incident/Incidents.tsx index 3c557773fd..e2ec9609bd 100644 --- a/plugins/splunk-on-call/src/components/Incident/Incidents.tsx +++ b/plugins/splunk-on-call/src/components/Incident/Incidents.tsx @@ -49,9 +49,10 @@ const useStyles = makeStyles((theme: Theme) => type Props = { refreshIncidents: boolean; team: string; + readOnly: boolean; }; -export const Incidents = ({ refreshIncidents, team }: Props) => { +export const Incidents = ({ readOnly, refreshIncidents, team }: Props) => { const classes = useStyles(); const api = useApi(splunkOnCallApiRef); @@ -108,6 +109,7 @@ export const Incidents = ({ refreshIncidents, team }: Props) => { key={index} team={team} incident={incident} + readOnly={readOnly} /> )) )} diff --git a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md index 3128854594..b34cb336cd 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-tech-insights-backend-module-jsonfc +## 0.1.11 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/plugin-tech-insights-node@0.2.5 + +## 0.1.10 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/plugin-tech-insights-common@0.2.3 + - @backstage/plugin-tech-insights-node@0.2.4 + +## 0.1.9 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/errors@0.2.1 + - @backstage/config@0.1.14 + - @backstage/plugin-tech-insights-common@0.2.2 + - @backstage/plugin-tech-insights-node@0.2.3 + ## 0.1.8 ### Patch Changes diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index fc2c6b7add..5c618d4172 100644 --- a/plugins/tech-insights-backend-module-jsonfc/package.json +++ b/plugins/tech-insights-backend-module-jsonfc/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend-module-jsonfc", - "version": "0.1.8", + "version": "0.1.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin-module" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,20 +25,20 @@ "scorecard" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-tech-insights-common": "^0.2.1", - "@backstage/plugin-tech-insights-node": "^0.2.2", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-tech-insights-common": "^0.2.3", + "@backstage/plugin-tech-insights-node": "^0.2.5", "ajv": "^7.0.3", "json-rules-engine": "^6.1.2", "lodash": "^4.17.21", @@ -43,7 +46,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2", + "@backstage/cli": "^0.14.1", "@types/node-cron": "^3.0.1" }, "files": [ diff --git a/plugins/tech-insights-backend/CHANGELOG.md b/plugins/tech-insights-backend/CHANGELOG.md index 36e7aa97a1..34dfee6ccb 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,46 @@ # @backstage/plugin-tech-insights-backend +## 0.2.7 + +### Patch Changes + +- 67a7c02d26: Remove usages of `EntityRef` and `parseEntityName` from `@backstage/catalog-model` +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/plugin-tech-insights-node@0.2.5 + +## 0.2.6 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/plugin-tech-insights-common@0.2.3 + - @backstage/plugin-tech-insights-node@0.2.4 + +## 0.2.5 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 538ca90790: Use updated type names from `@backstage/catalog-client` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/errors@0.2.1 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/plugin-tech-insights-common@0.2.2 + - @backstage/plugin-tech-insights-node@0.2.3 + ## 0.2.4 ### Patch Changes diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index 5a60e478d8..c44aaa138c 100644 --- a/plugins/tech-insights-backend/package.json +++ b/plugins/tech-insights-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend", - "version": "0.2.4", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,22 +25,22 @@ "reporting" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-tech-insights-common": "^0.2.1", - "@backstage/plugin-tech-insights-node": "^0.2.2", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-tech-insights-common": "^0.2.3", + "@backstage/plugin-tech-insights-node": "^0.2.5", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -51,8 +54,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.17", - "@backstage/cli": "^0.13.2", + "@backstage/backend-test-utils": "^0.1.19", + "@backstage/cli": "^0.14.1", "@types/supertest": "^2.0.8", "@types/node-cron": "^3.0.0", "@types/semver": "^7.3.8", diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts index a51ce0ab83..14d0728cf6 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityMetadataFactRetriever.test.ts @@ -57,6 +57,7 @@ const defaultEntityListResponse: GetEntitiesResponse = { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/my-team', target: { name: 'team-a', kind: 'group', namespace: 'default' }, }, ], diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts index 2108379f06..374bda2569 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/entityOwnershipFactRetriever.test.ts @@ -57,6 +57,7 @@ const defaultEntityListResponse: GetEntitiesResponse = { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/team-a', target: { name: 'team-a', kind: 'group', namespace: 'default' }, }, ], diff --git a/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts index a3604129b7..b4fa8ba67c 100644 --- a/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts +++ b/plugins/tech-insights-backend/src/service/fact/factRetrievers/techdocsFactRetriever.test.ts @@ -57,6 +57,7 @@ const defaultEntityListResponse: GetEntitiesResponse = { relations: [ { type: RELATION_OWNED_BY, + targetRef: 'group:default/team-a', target: { name: 'team-a', kind: 'group', namespace: 'default' }, }, ], diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts index 0b7013295f..ddb90b75cd 100644 --- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts +++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts @@ -26,7 +26,7 @@ import { rsort } from 'semver'; import { groupBy, omit } from 'lodash'; import { DateTime } from 'luxon'; import { Logger } from 'winston'; -import { parseEntityName, stringifyEntityRef } from '@backstage/catalog-model'; +import { parseEntityRef, stringifyEntityRef } from '@backstage/catalog-model'; import { isMaxItems, isTtl } from '../fact/factRetrievers/utils'; type Transaction = Knex.Transaction; @@ -161,7 +161,7 @@ export class TechInsightsDatabase implements TechInsightsStore { return groupBy( results.map(it => { - const { namespace, kind, name } = parseEntityName(it.entity); + const { namespace, kind, name } = parseEntityRef(it.entity); const timestamp = typeof it.timestamp === 'string' ? DateTime.fromISO(it.timestamp) @@ -253,7 +253,7 @@ export class TechInsightsDatabase implements TechInsightsStore { private dbFactRowsToTechInsightFacts(rows: RawDbFactRow[]) { return rows.reduce((acc, it) => { - const { namespace, kind, name } = parseEntityName(it.entity); + const { namespace, kind, name } = parseEntityRef(it.entity); const timestamp = typeof it.timestamp === 'string' ? DateTime.fromISO(it.timestamp) diff --git a/plugins/tech-insights-backend/src/service/router.ts b/plugins/tech-insights-backend/src/service/router.ts index 22a80c2d79..b13802358f 100644 --- a/plugins/tech-insights-backend/src/service/router.ts +++ b/plugins/tech-insights-backend/src/service/router.ts @@ -28,8 +28,7 @@ import { DateTime } from 'luxon'; import { PersistenceContext } from './persistence/persistenceContext'; import { EntityName, - EntityRef, - parseEntityName, + parseEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; import { errorHandler } from '@backstage/backend-common'; @@ -130,7 +129,7 @@ export async function createRouter< */ router.get('/facts/latest', async (req, res) => { const { entity } = req.query; - const { namespace, kind, name } = parseEntityName(entity as EntityRef); + const { namespace, kind, name } = parseEntityRef(entity as string); const ids = req.query.ids as string[]; return res.send( await techInsightsStore.getLatestFactsByIds( @@ -145,7 +144,7 @@ export async function createRouter< */ router.get('/facts/range', async (req, res) => { const { entity } = req.query; - const { namespace, kind, name } = parseEntityName(entity as EntityRef); + const { namespace, kind, name } = parseEntityRef(entity as string); const ids = req.query.ids as string[]; const startDatetime = DateTime.fromISO(req.query.startDatetime as string); diff --git a/plugins/tech-insights-common/CHANGELOG.md b/plugins/tech-insights-common/CHANGELOG.md index 947c22756f..e895a82e6c 100644 --- a/plugins/tech-insights-common/CHANGELOG.md +++ b/plugins/tech-insights-common/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-tech-insights-common +## 0.2.3 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/types@0.1.3 + +## 0.2.2 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/types@0.1.2 + ## 0.2.1 ### Patch Changes diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json index a0b840da74..f20310cc0c 100644 --- a/plugins/tech-insights-common/package.json +++ b/plugins/tech-insights-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-common", - "version": "0.2.1", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "common-library" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,20 +25,20 @@ ], "scripts": { "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { "@types/luxon": "^2.0.5", "luxon": "^2.0.2", - "@backstage/types": "^0.1.1" + "@backstage/types": "^0.1.3" }, "devDependencies": { - "@backstage/cli": "^0.13.2-next.0" + "@backstage/cli": "^0.14.0" }, "files": [ "dist" diff --git a/plugins/tech-insights-node/CHANGELOG.md b/plugins/tech-insights-node/CHANGELOG.md index c38c36ecd9..cb019ccb99 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,32 @@ # @backstage/plugin-tech-insights-node +## 0.2.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.11.0 + +## 0.2.4 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/config@0.1.15 + - @backstage/plugin-tech-insights-common@0.2.3 + +## 0.2.3 + +### Patch Changes + +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/config@0.1.14 + - @backstage/plugin-tech-insights-common@0.2.2 + ## 0.2.2 ### Patch Changes diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json index 05c6f10071..b3b0c7d6b8 100644 --- a/plugins/tech-insights-node/package.json +++ b/plugins/tech-insights-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-node", - "version": "0.2.2", + "version": "0.2.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -10,6 +10,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,24 +24,24 @@ "tech-insights" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/config": "^0.1.13", - "@backstage/plugin-tech-insights-common": "^0.2.1", + "@backstage/backend-common": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/plugin-tech-insights-common": "^0.2.3", "@types/luxon": "^2.0.5", "luxon": "^2.0.2", "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2" + "@backstage/cli": "^0.14.1" }, "files": [ "dist" diff --git a/plugins/tech-insights/CHANGELOG.md b/plugins/tech-insights/CHANGELOG.md index af16d5f347..30f21e45bd 100644 --- a/plugins/tech-insights/CHANGELOG.md +++ b/plugins/tech-insights/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-tech-insights +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + +## 0.1.9 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/catalog-model@0.10.0 + - @backstage/theme@0.2.15 + - @backstage/types@0.1.2 + - @backstage/plugin-tech-insights-common@0.2.2 + ## 0.1.8 ### Patch Changes diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index b7b1b6877c..99d4eeefad 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights", - "version": "0.1.8", + "version": "0.1.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -9,25 +9,28 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/plugin-tech-insights-common": "^0.2.1", - "@backstage/theme": "^0.2.14", - "@backstage/types": "^0.1.1", + "@backstage/catalog-model": "^0.11.0", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/plugin-tech-insights-common": "^0.2.3", + "@backstage/theme": "^0.2.15", + "@backstage/types": "^0.1.3", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -39,10 +42,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index bafe2212cf..88913e12e4 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-tech-radar +## 0.5.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.8.10 + - @backstage/core-plugin-api@0.7.0 + +## 0.5.6 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/theme@0.2.15 + ## 0.5.5 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 29601d1755..0ea04212a5 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-tech-radar", "description": "A Backstage plugin that lets you display a Tech Radar for your organization", - "version": "0.5.5", + "version": "0.5.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -21,19 +24,19 @@ "backstage" ], "scripts": { - "build": "backstage-cli plugin:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean", - "start": "backstage-cli plugin:serve" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean", + "start": "backstage-cli package start" }, "dependencies": { - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/theme": "^0.2.14", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -46,10 +49,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index b05e8a6dad..2e253f5d0c 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,72 @@ # @backstage/plugin-techdocs-backend +## 0.14.0 + +### Minor Changes + +- a925ba8385: BREAKING: constructor based initialization of DefaultTechDocsCollator now deprecated. Use static fromConfig method instead. + + ```diff + indexBuilder.addCollator({ + defaultRefreshIntervalSeconds: 600, + - collator: new DefaultTechDocsCollator({ + + collator: DefaultTechDocsCollator.fromConfig(config, { + discovery, + logger, + tokenManager, + }), + }); + ``` + + Note: in an upcoming release, TechDocs backend's /sync/:namespace/:kind/:name endpoint will only respond to text/event-stream-based requests. Update any custom code at your organization accordingly. + +### Patch Changes + +- 91eb01b5cf: Optimize DefaultTechDocsCollator get entities. +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/backend-common@0.11.0 + - @backstage/catalog-model@0.11.0 + - @backstage/catalog-client@0.7.2 + - @backstage/techdocs-common@0.11.10 + - @backstage/integration@0.7.5 + +## 0.13.5 + +### Patch Changes + +- Fix for the previous release with missing type declarations. +- Updated dependencies + - @backstage/backend-common@0.10.9 + - @backstage/catalog-client@0.7.1 + - @backstage/catalog-model@0.10.1 + - @backstage/config@0.1.15 + - @backstage/errors@0.2.2 + - @backstage/integration@0.7.4 + - @backstage/search-common@0.2.4 + - @backstage/techdocs-common@0.11.9 + - @backstage/plugin-catalog-common@0.1.4 + +## 0.13.4 + +### Patch Changes + +- 453145abba: Do not use cross-fetch in the backend +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 811c710a21: Fix bug where tech docs collator stores search indices with wrong entity ref casing. Make the collator to conform legacyPathCasing configuration option. +- 7aeb491394: Replace use of deprecated `ENTITY_DEFAULT_NAMESPACE` constant with `DEFAULT_NAMESPACE`. +- Updated dependencies + - @backstage/backend-common@0.10.8 + - @backstage/catalog-client@0.7.0 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/search-common@0.2.3 + - @backstage/techdocs-common@0.11.8 + - @backstage/plugin-catalog-common@0.1.3 + ## 0.13.3 ### Patch Changes diff --git a/plugins/techdocs-backend/api-report.md b/plugins/techdocs-backend/api-report.md index cefb7d937b..5d106cfafb 100644 --- a/plugins/techdocs-backend/api-report.md +++ b/plugins/techdocs-backend/api-report.md @@ -18,25 +18,17 @@ import { PublisherBase } from '@backstage/techdocs-common'; import { TechDocsDocument } from '@backstage/techdocs-common'; import { TokenManager } from '@backstage/backend-common'; -// Warning: (ae-missing-release-tag) "createRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export function createRouter(options: RouterOptions): Promise; -// Warning: (ae-missing-release-tag) "DefaultTechDocsCollator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export class DefaultTechDocsCollator implements DocumentCollator { - // @deprecated - constructor(options: TechDocsCollatorOptions); // (undocumented) protected applyArgsToFormat( format: string, args: Record, ): string; // (undocumented) - protected discovery: PluginEndpointDiscovery; - // (undocumented) execute(): Promise; // (undocumented) static fromConfig( @@ -44,15 +36,11 @@ export class DefaultTechDocsCollator implements DocumentCollator { options: TechDocsCollatorOptions, ): DefaultTechDocsCollator; // (undocumented) - protected locationTemplate: string; - // (undocumented) readonly type: string; // (undocumented) readonly visibilityPermission: Permission; } -// Warning: (ae-missing-release-tag) "OutOfTheBoxDeploymentOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type OutOfTheBoxDeploymentOptions = { preparers: PreparerBuilder; @@ -65,8 +53,6 @@ export type OutOfTheBoxDeploymentOptions = { cache: PluginCacheManager; }; -// Warning: (ae-missing-release-tag) "RecommendedDeploymentOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type RecommendedDeploymentOptions = { publisher: PublisherBase; @@ -76,16 +62,12 @@ export type RecommendedDeploymentOptions = { cache: PluginCacheManager; }; -// Warning: (ae-missing-release-tag) "RouterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type RouterOptions = | RecommendedDeploymentOptions | OutOfTheBoxDeploymentOptions; -// Warning: (ae-missing-release-tag) "TechDocsCollatorOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type TechDocsCollatorOptions = { discovery: PluginEndpointDiscovery; logger: Logger_2; diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 37b15ffbe0..7e8241bfea 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-backend", "description": "The Backstage backend plugin that renders technical documentation for your components", - "version": "0.13.3", + "version": "0.14.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "backend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,24 +25,24 @@ "techdocs" ], "scripts": { - "start": "backstage-cli backend:dev", - "build": "backstage-cli backend:build", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.10.7", - "@backstage/catalog-client": "^0.6.0", - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/plugin-catalog-common": "^0.1.2", - "@backstage/search-common": "^0.2.2", - "@backstage/techdocs-common": "^0.11.7", + "@backstage/backend-common": "^0.11.0", + "@backstage/catalog-client": "^0.7.2", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/plugin-catalog-common": "^0.1.4", + "@backstage/search-common": "^0.2.4", + "@backstage/techdocs-common": "^0.11.10", "@types/express": "^4.17.6", "dockerode": "^3.3.1", "express": "^4.17.1", @@ -52,8 +55,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/test-utils": "^0.2.6", "@types/dockerode": "^3.3.0", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts index e81fc1e7a9..d9f75767a7 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts @@ -208,8 +208,13 @@ describe('DefaultTechDocsCollator', () => { }); it('maps a returned entity with a custom locationTemplate', async () => { + const mockConfig = new ConfigReader({ + techdocs: { + legacyUseCaseSensitiveTripletPaths: true, + }, + }); // Provide an alternate location template. - collator = new DefaultTechDocsCollator({ + collator = DefaultTechDocsCollator.fromConfig(mockConfig, { discovery: mockDiscoveryApi, tokenManager: mockTokenManager, locationTemplate: '/software/:name', diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts index 987e8c6b15..73c030cc51 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts @@ -20,6 +20,7 @@ import { } from '@backstage/backend-common'; import { Entity, + parseEntityRef, RELATION_OWNED_BY, stringifyEntityRef, } from '@backstage/catalog-model'; @@ -30,7 +31,11 @@ import { Logger } from 'winston'; import pLimit from 'p-limit'; import { Config } from '@backstage/config'; import { catalogEntityReadPermission } from '@backstage/plugin-catalog-common'; -import { CatalogApi, CatalogClient } from '@backstage/catalog-client'; +import { + CatalogApi, + CatalogClient, + CATALOG_FILTER_EXISTS, +} from '@backstage/catalog-client'; import { TechDocsDocument } from '@backstage/techdocs-common'; interface MkSearchIndexDoc { @@ -39,6 +44,11 @@ interface MkSearchIndexDoc { location: string; } +/** + * Options to configure the TechDocs collator + * + * @public + */ export type TechDocsCollatorOptions = { discovery: PluginEndpointDiscovery; logger: Logger; @@ -55,47 +65,48 @@ type EntityInfo = { kind: string; }; +/** + * A search collator responsible for gathering and transforming TechDocs documents. + * + * @public + */ export class DefaultTechDocsCollator implements DocumentCollator { - protected discovery: PluginEndpointDiscovery; - protected locationTemplate: string; - private readonly logger: Logger; - private readonly catalogClient: CatalogApi; - private readonly tokenManager: TokenManager; - private readonly parallelismLimit: number; - private readonly legacyPathCasing: boolean; public readonly type: string = 'techdocs'; public readonly visibilityPermission = catalogEntityReadPermission; - /** - * @deprecated use static fromConfig method instead. - */ - constructor(options: TechDocsCollatorOptions) { - this.discovery = options.discovery; - this.locationTemplate = - options.locationTemplate || '/docs/:namespace/:kind/:name/:path'; - this.logger = options.logger; - this.catalogClient = - options.catalogClient || - new CatalogClient({ discoveryApi: options.discovery }); - this.parallelismLimit = options.parallelismLimit ?? 10; - this.legacyPathCasing = options.legacyPathCasing ?? false; - this.tokenManager = options.tokenManager; - } + private constructor( + private readonly legacyPathCasing: boolean, + private readonly options: TechDocsCollatorOptions, + ) {} static fromConfig(config: Config, options: TechDocsCollatorOptions) { const legacyPathCasing = config.getOptionalBoolean( 'techdocs.legacyUseCaseSensitiveTripletPaths', ) || false; - return new DefaultTechDocsCollator({ ...options, legacyPathCasing }); + return new DefaultTechDocsCollator(legacyPathCasing, options); } async execute() { - const limit = pLimit(this.parallelismLimit); - const techDocsBaseUrl = await this.discovery.getBaseUrl('techdocs'); - const { token } = await this.tokenManager.getToken(); - const entities = await this.catalogClient.getEntities( + const { + parallelismLimit, + discovery, + tokenManager, + catalogClient, + locationTemplate, + logger, + } = this.options; + const limit = pLimit(parallelismLimit ?? 10); + const techDocsBaseUrl = await discovery.getBaseUrl('techdocs'); + const { token } = await tokenManager.getToken(); + const entities = await ( + catalogClient ?? new CatalogClient({ discoveryApi: discovery }) + ).getEntities( { + filter: { + 'metadata.annotations.backstage.io/techdocs-ref': + CATALOG_FILTER_EXISTS, + }, fields: [ 'kind', 'namespace', @@ -110,61 +121,60 @@ export class DefaultTechDocsCollator implements DocumentCollator { }, { token }, ); - const docPromises = entities.items - .filter(it => it.metadata?.annotations?.['backstage.io/techdocs-ref']) - .map((entity: Entity) => - limit(async (): Promise => { - const entityInfo = DefaultTechDocsCollator.handleEntityInfoCasing( - this.legacyPathCasing, + const docPromises = entities.items.map((entity: Entity) => + limit(async (): Promise => { + const entityInfo = DefaultTechDocsCollator.handleEntityInfoCasing( + this.legacyPathCasing ?? false, + { + kind: entity.kind, + namespace: entity.metadata.namespace || 'default', + name: entity.metadata.name, + }, + ); + + try { + const searchIndexResponse = await fetch( + DefaultTechDocsCollator.constructDocsIndexUrl( + techDocsBaseUrl, + entityInfo, + ), { - kind: entity.kind, - namespace: entity.metadata.namespace || 'default', - name: entity.metadata.name, + headers: { + Authorization: `Bearer ${token}`, + }, }, ); + const searchIndex = await searchIndexResponse.json(); - try { - const searchIndexResponse = await fetch( - DefaultTechDocsCollator.constructDocsIndexUrl( - techDocsBaseUrl, - entityInfo, - ), + return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({ + title: unescape(doc.title), + text: unescape(doc.text || ''), + location: this.applyArgsToFormat( + locationTemplate || '/docs/:namespace/:kind/:name/:path', { - headers: { - Authorization: `Bearer ${token}`, - }, - }, - ); - const searchIndex = await searchIndexResponse.json(); - - return searchIndex.docs.map((doc: MkSearchIndexDoc) => ({ - title: unescape(doc.title), - text: unescape(doc.text || ''), - location: this.applyArgsToFormat(this.locationTemplate, { ...entityInfo, path: doc.location, - }), - path: doc.location, - ...entityInfo, - entityTitle: entity.metadata.title, - componentType: entity.spec?.type?.toString() || 'other', - lifecycle: (entity.spec?.lifecycle as string) || '', - owner: - entity.relations?.find(r => r.type === RELATION_OWNED_BY) - ?.target?.name || '', - authorization: { - resourceRef: stringifyEntityRef(entity), }, - })); - } catch (e) { - this.logger.debug( - `Failed to retrieve tech docs search index for entity ${entityInfo.namespace}/${entityInfo.kind}/${entityInfo.name}`, - e, - ); - return []; - } - }), - ); + ), + path: doc.location, + ...entityInfo, + entityTitle: entity.metadata.title, + componentType: entity.spec?.type?.toString() || 'other', + lifecycle: (entity.spec?.lifecycle as string) || '', + owner: getSimpleEntityOwnerString(entity), + authorization: { + resourceRef: stringifyEntityRef(entity), + }, + })); + } catch (e) { + logger.debug( + `Failed to retrieve tech docs search index for entity ${entityInfo.namespace}/${entityInfo.kind}/${entityInfo.name}`, + e, + ); + return []; + } + }), + ); return (await Promise.all(docPromises)).flat(); } @@ -197,3 +207,14 @@ export class DefaultTechDocsCollator implements DocumentCollator { }, {} as EntityInfo); } } + +function getSimpleEntityOwnerString(entity: Entity): string { + if (entity.relations) { + const owner = entity.relations.find(r => r.type === RELATION_OWNED_BY); + if (owner) { + const { name } = parseEntityRef(owner.targetRef); + return name; + } + } + return ''; +} diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 96916bdc9b..d620020245 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -41,8 +41,10 @@ import { } from './DocsBuildStrategy'; /** - * All of the required dependencies for running TechDocs in the "out-of-the-box" + * Required dependencies for running TechDocs in the "out-of-the-box" * deployment configuration (prepare/generate/publish all in the Backend). + * + * @public */ export type OutOfTheBoxDeploymentOptions = { preparers: PreparerBuilder; @@ -59,6 +61,8 @@ export type OutOfTheBoxDeploymentOptions = { /** * Required dependencies for running TechDocs in the "recommended" deployment * configuration (prepare/generate handled externally in CI/CD). + * + * @public */ export type RecommendedDeploymentOptions = { publisher: PublisherBase; @@ -71,6 +75,8 @@ export type RecommendedDeploymentOptions = { /** * One of the two deployment configurations must be provided. + * + * @public */ export type RouterOptions = | RecommendedDeploymentOptions @@ -79,6 +85,8 @@ export type RouterOptions = /** * Typeguard to help createRouter() understand when we are in a "recommended" * deployment vs. when we are in an out-of-the-box deployment configuration. + * + * * @public */ function isOutOfTheBoxOption( opt: RouterOptions, @@ -86,6 +94,11 @@ function isOutOfTheBoxOption( return (opt as OutOfTheBoxDeploymentOptions).preparers !== undefined; } +/** + * Creates a techdocs router. + * + * @public + */ export async function createRouter( options: RouterOptions, ): Promise { @@ -332,12 +345,8 @@ export function createEventStream( } /** - * Create a HTTP response. This is used for the legacy non-event-stream implementation of the sync endpoint. - * - * @param res - the response to write the event-stream to - * @returns A tuple of callbacks to emit messages. A call to 'error' or 'finish' - * will close the event-stream. - */ + * @deprecated use event-stream implementation of the sync endpoint + * */ export function createHttpResponse( res: Response, ): DocsSynchronizerSyncOpts { diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 3ff8c0e175..97104ea5a4 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,66 @@ # @backstage/plugin-techdocs +## 0.14.0 + +### Minor Changes + +- 2262fe19c9: **BREAKING**: Removed support for passing in an explicit `entity` prop to entity page extensions, which has been deprecated for a long time. This is only a breaking change at the TypeScript level, as this property was already ignored. +- 4faae902eb: Adjust the Tech Docs page theme as a side effect of the `mkdocs-material` theme update. + + If you use the `spofify/techdocs` image to build your documentation, make sure you use version `spotify/techdocs:v0.3.7`. + + **Breaking**: The `PyMdown` extensions have also been updated and some syntax may have changed, so it is recommended that you check the extension's documentation if something stops working. + For example, the syntax of tags below was deprecated in `PyMdown` extensions `v.7.0` and in `v.8.0.0` it has been removed. This means that the old syntax specified below no longer works. + + ````markdown + ```markdown tab="tab" + This is some markdown + ``` + + ```markdown tab="tab 2" + This is some markdown in tab 2 + ``` + ```` + +### Patch Changes + +- 3bbb4d98c6: Changed to use from createApp +- ed09ad8093: Updated usage of the `LocationSpec` type from `@backstage/catalog-model`, which is deprecated. +- b776ce5aab: Replaced use of deprecated `useEntityListProvider` hook with `useEntityList`. +- d4f67fa728: Removed import of deprecated hook. +- 45e1706328: Continuation of [#9569](https://github.com/backstage/backstage/pull/9569), fix Tech Docs Reader search position to be the same width as content. +- 919cf2f836: Minor updates to match the new `targetRef` field of relations, and to stop consuming the `target` field +- Updated dependencies + - @backstage/plugin-catalog@0.9.0 + - @backstage/core-components@0.8.10 + - @backstage/plugin-catalog-react@0.7.0 + - @backstage/catalog-model@0.11.0 + - @backstage/core-plugin-api@0.7.0 + - @backstage/integration@0.7.5 + - @backstage/plugin-search@0.7.1 + - @backstage/integration-react@0.1.23 + +## 0.13.4 + +### Patch Changes + +- 1ed305728b: Bump `node-fetch` to version 2.6.7 and `cross-fetch` to version 3.1.5 +- c77c5c7eb6: Added `backstage.role` to `package.json` +- 6553985cd4: Match text size of admonitions to main content text size. +- 9df7b43e1a: Improve overall appearance of highlighted code in docs. +- Updated dependencies + - @backstage/core-components@0.8.9 + - @backstage/core-plugin-api@0.6.1 + - @backstage/errors@0.2.1 + - @backstage/integration@0.7.3 + - @backstage/integration-react@0.1.22 + - @backstage/plugin-catalog@0.8.0 + - @backstage/plugin-catalog-react@0.6.15 + - @backstage/plugin-search@0.7.0 + - @backstage/catalog-model@0.10.0 + - @backstage/config@0.1.14 + - @backstage/theme@0.2.15 + ## 0.13.3 ### Patch Changes diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index 9a25cb6375..917c1d5d85 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -5,7 +5,6 @@ ```ts /// -import { Action } from '@material-table/core'; import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { Config } from '@backstage/config'; @@ -15,122 +14,76 @@ import { Entity } from '@backstage/catalog-model'; import { EntityName } from '@backstage/catalog-model'; import { FetchApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; -import { LocationSpec } from '@backstage/catalog-model'; +import { PropsWithChildren } from 'react'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { TableColumn } from '@backstage/core-components'; import { TableProps } from '@backstage/core-components'; import { UserListFilterKind } from '@backstage/plugin-catalog-react'; -// Warning: (ae-missing-release-tag) "createCopyDocsUrlAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -function createCopyDocsUrlAction(copyToClipboard: Function): ( - row: DocsTableRow, -) => { - icon: () => JSX.Element; - tooltip: string; - onClick: () => any; -}; +// @public +export const DefaultTechDocsHome: ( + props: DefaultTechDocsHomeProps, +) => JSX.Element; -// Warning: (ae-missing-release-tag) "createNameColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -function createNameColumn(): TableColumn; - -// Warning: (ae-missing-release-tag) "createOwnerColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -function createOwnerColumn(): TableColumn; - -// Warning: (ae-missing-release-tag) "createStarEntityAction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -function createStarEntityAction( - isStarredEntity: Function, - toggleStarredEntity: Function, -): ({ entity }: DocsTableRow) => { - cellStyle: { - paddingLeft: string; - }; - icon: () => JSX.Element; - tooltip: string; - onClick: () => any; -}; - -// Warning: (ae-missing-release-tag) "createTypeColumn" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -function createTypeColumn(): TableColumn; - -// Warning: (ae-missing-release-tag) "DefaultTechDocsHome" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const DefaultTechDocsHome: ({ - initialFilter, - columns, - actions, -}: { - initialFilter?: UserListFilterKind | undefined; - columns?: TableColumn[] | undefined; +// @public +export type DefaultTechDocsHomeProps = { + initialFilter?: UserListFilterKind; + columns?: TableColumn[]; actions?: TableProps['actions']; -}) => JSX.Element; +}; -// Warning: (ae-missing-release-tag) "DocsCardGrid" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const DocsCardGrid: ({ - entities, -}: { +// @public +export const DocsCardGrid: (props: DocsCardGridProps) => JSX.Element | null; + +// @public +export type DocsCardGridProps = { entities: Entity[] | undefined; -}) => JSX.Element | null; +}; -// Warning: (ae-missing-release-tag) "DocsResultListItem" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const DocsResultListItem: ({ - result, - lineClamp, - asListItem, - asLink, - title, -}: { - result: any; - lineClamp?: number | undefined; - asListItem?: boolean | undefined; - asLink?: boolean | undefined; - title?: string | undefined; -}) => JSX.Element; +// @public @deprecated (undocumented) +export const DocsResultListItem: ( + props: TechDocsSearchResultListItemProps, +) => JSX.Element; -// Warning: (ae-missing-release-tag) "DocsTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const DocsTable: ({ - entities, - title, - loading, - columns, - actions, -}: { +// @public +export const DocsTable: { + (props: DocsTableProps): JSX.Element | null; + columns: { + createNameColumn(): TableColumn; + createOwnerColumn(): TableColumn; + createTypeColumn(): TableColumn; + }; + actions: { + createCopyDocsUrlAction(copyToClipboard: Function): (row: DocsTableRow) => { + icon: () => JSX.Element; + tooltip: string; + onClick: () => any; + }; + createStarEntityAction( + isStarredEntity: Function, + toggleStarredEntity: Function, + ): ({ entity }: DocsTableRow) => { + cellStyle: { + paddingLeft: string; + }; + icon: () => JSX.Element; + tooltip: string; + onClick: () => any; + }; + }; +}; + +// @public +export type DocsTableProps = { entities: Entity[] | undefined; title?: string | undefined; loading?: boolean | undefined; - columns?: TableColumn[] | undefined; - actions?: - | ( - | Action - | { - action: (rowData: DocsTableRow) => Action; - position: string; - } - | ((rowData: DocsTableRow) => Action) - )[] - | undefined; -}) => JSX.Element | null; + columns?: TableColumn[]; + actions?: TableProps['actions']; +}; -// Warning: (ae-missing-release-tag) "DocsTableRow" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export type DocsTableRow = { entity: Entity; resolved: { @@ -140,76 +93,101 @@ export type DocsTableRow = { }; }; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "EmbeddedDocsRouter" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const EmbeddedDocsRouter: (_props: Props_2) => JSX.Element; +// @public +export const EmbeddedDocsRouter: () => JSX.Element; -// Warning: (ae-missing-release-tag) "EntityListDocsGrid" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const EntityListDocsGrid: () => JSX.Element; -// Warning: (ae-missing-release-tag) "EntityListDocsTable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const EntityListDocsTable: { - ({ - columns, - actions, - }: { - columns?: TableColumn[] | undefined; - actions?: TableProps['actions']; - }): JSX.Element; - columns: typeof columnFactories; - actions: typeof actionFactories; + (props: EntityListDocsTableProps): JSX.Element; + columns: { + createNameColumn(): TableColumn; + createOwnerColumn(): TableColumn; + createTypeColumn(): TableColumn; + }; + actions: { + createCopyDocsUrlAction(copyToClipboard: Function): (row: DocsTableRow) => { + icon: () => JSX.Element; + tooltip: string; + onClick: () => any; + }; + createStarEntityAction( + isStarredEntity: Function, + toggleStarredEntity: Function, + ): ({ entity }: DocsTableRow) => { + cellStyle: { + paddingLeft: string; + }; + icon: () => JSX.Element; + tooltip: string; + onClick: () => any; + }; + }; }; -// Warning: (ae-missing-release-tag) "EntityTechdocsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const EntityTechdocsContent: (_props: { - entity?: Entity | undefined; -}) => JSX.Element; +// @public +export type EntityListDocsTableProps = { + columns?: TableColumn[]; + actions?: TableProps['actions']; +}; -// Warning: (ae-missing-release-tag) "isTechDocsAvailable" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export const EntityTechdocsContent: () => JSX.Element; + +// @public export const isTechDocsAvailable: (entity: Entity) => boolean; -// Warning: (ae-missing-release-tag) "PanelType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export interface PanelConfig { + // (undocumented) + description: string; + // (undocumented) + filterPredicate: ((entity: Entity) => boolean) | string; + // (undocumented) + panelCSS?: CSSProperties; + // (undocumented) + panelType: PanelType; + // (undocumented) + title: string; +} + +// @public export type PanelType = 'DocsCardGrid' | 'DocsTable'; -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "Reader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const Reader: ({ - entityRef, - onReady, - withSearch, -}: Props_3) => JSX.Element; +// @public +export const Reader: (props: ReaderProps) => JSX.Element; -// Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type ReaderProps = { + entityRef: EntityName; + withSearch?: boolean; + onReady?: () => void; +}; + +// @public export const Router: () => JSX.Element; // @public export type SyncResult = 'cached' | 'updated'; +// @public +export interface TabConfig { + // (undocumented) + label: string; + // (undocumented) + panels: PanelConfig[]; +} + +// @public +export type TabsConfig = TabConfig[]; + // @public export interface TechDocsApi { getApiOrigin(): Promise; - // Warning: (ae-forgotten-export) The symbol "TechDocsEntityMetadata" needs to be exported by the entry point index.d.ts - // // (undocumented) getEntityMetadata(entityId: EntityName): Promise; - // Warning: (ae-forgotten-export) The symbol "TechDocsMetadata" needs to be exported by the entry point index.d.ts - // // (undocumented) getTechDocsMetadata(entityId: EntityName): Promise; } @@ -234,83 +212,64 @@ export class TechDocsClient implements TechDocsApi { getTechDocsMetadata(entityId: EntityName): Promise; } -// Warning: (ae-missing-release-tag) "TechDocsCustomHome" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const TechDocsCustomHome: ({ - tabsConfig, -}: { - tabsConfig: TabsConfig; -}) => JSX.Element; +// @public +export const TechDocsCustomHome: ( + props: TechDocsCustomHomeProps, +) => JSX.Element; -// Warning: (ae-missing-release-tag) "TechDocsIndexPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public +export type TechDocsCustomHomeProps = { + tabsConfig: TabsConfig; +}; + +// @public +export type TechDocsEntityMetadata = Entity & { + locationMetadata?: { + type: string; + target: string; + }; +}; + +// @public export const TechDocsIndexPage: () => JSX.Element; -// Warning: (ae-missing-release-tag) "TechDocsPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const TechDocsPage: ({ children }: TechDocsPageProps) => JSX.Element; +// @public +export type TechDocsMetadata = { + site_name: string; + site_description: string; +}; -// Warning: (ae-missing-release-tag) "TechdocsPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public @deprecated (undocumented) +export const TechDocsPage: (props: TechDocsReaderPageProps) => JSX.Element; + +// @public export const TechdocsPage: () => JSX.Element; -// Warning: (ae-missing-release-tag) "TechDocsPageHeader" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const TechDocsPageHeader: ({ - entityRef, - entityMetadata, - techDocsMetadata, -}: TechDocsPageHeaderProps) => JSX.Element; +// @public @deprecated (undocumented) +export const TechDocsPageHeader: ( + props: TechDocsReaderPageHeaderProps, +) => JSX.Element; -// Warning: (ae-missing-release-tag) "TechDocsPageHeaderProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type TechDocsPageHeaderProps = { - entityRef: EntityName; - entityMetadata?: TechDocsEntityMetadata; - techDocsMetadata?: TechDocsMetadata; +// @public @deprecated (undocumented) +export type TechDocsPageHeaderProps = TechDocsReaderPageHeaderProps; + +// @public @deprecated (undocumented) +export type TechDocsPageRenderFunction = TechDocsReaderPageRenderFunction; + +// @public +export const TechDocsPageWrapper: ( + props: TechDocsPageWrapperProps, +) => JSX.Element; + +// @public +export type TechDocsPageWrapperProps = { + children?: React_2.ReactNode; }; -// Warning: (ae-missing-release-tag) "TechDocsPageProps" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type TechDocsPageProps = { - children?: TechDocsPageRenderFunction | React_2.ReactNode; -}; - -// Warning: (ae-missing-release-tag) "TechDocsPageRenderFunction" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type TechDocsPageRenderFunction = ({ - techdocsMetadataValue, - entityMetadataValue, - entityRef, -}: { - techdocsMetadataValue?: TechDocsMetadata | undefined; - entityMetadataValue?: TechDocsEntityMetadata | undefined; - entityRef: EntityName; - onReady: () => void; -}) => JSX.Element; - -// Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "TechDocsPageWrapper" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const TechDocsPageWrapper: ({ children }: Props) => JSX.Element; - -// Warning: (ae-missing-release-tag) "TechDocsPicker" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public export const TechDocsPicker: () => null; -// Warning: (ae-missing-release-tag) "techdocsPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) +// @public const techdocsPlugin: BackstagePlugin< { root: RouteRef; @@ -326,12 +285,62 @@ const techdocsPlugin: BackstagePlugin< export { techdocsPlugin as plugin }; export { techdocsPlugin }; -// Warning: (ae-missing-release-tag) "TechDocsReaderPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export const TechDocsReaderPage: ({ - children, -}: TechDocsPageProps) => JSX.Element; +// @public +export const TechDocsReaderPage: ( + props: TechDocsReaderPageProps, +) => JSX.Element; + +// @public +export const TechDocsReaderPageHeader: ( + props: TechDocsReaderPageHeaderProps, +) => JSX.Element; + +// @public +export type TechDocsReaderPageHeaderProps = PropsWithChildren<{ + entityRef: EntityName; + entityMetadata?: TechDocsEntityMetadata; + techDocsMetadata?: TechDocsMetadata; +}>; + +// @public +export type TechDocsReaderPageProps = { + children?: TechDocsReaderPageRenderFunction | React_2.ReactNode; +}; + +// @public +export type TechDocsReaderPageRenderFunction = ({ + techdocsMetadataValue, + entityMetadataValue, + entityRef, +}: { + techdocsMetadataValue?: TechDocsMetadata | undefined; + entityMetadataValue?: TechDocsEntityMetadata | undefined; + entityRef: EntityName; + onReady: () => void; +}) => JSX.Element; + +// @public +export const TechDocsSearch: (props: TechDocsSearchProps) => JSX.Element; + +// @public +export type TechDocsSearchProps = { + entityId: EntityName; + debounceTime?: number; +}; + +// @public +export const TechDocsSearchResultListItem: ( + props: TechDocsSearchResultListItemProps, +) => JSX.Element; + +// @public +export type TechDocsSearchResultListItemProps = { + result: any; + lineClamp?: number; + asListItem?: boolean; + asLink?: boolean; + title?: string; +}; // @public export interface TechDocsStorageApi { @@ -390,10 +399,4 @@ export class TechDocsStorageClient implements TechDocsStorageApi { logHandler?: (line: string) => void, ): Promise; } - -// Warnings were encountered during analysis: -// -// src/home/components/EntityListDocsTable.d.ts:11:5 - (ae-forgotten-export) The symbol "columnFactories" needs to be exported by the entry point index.d.ts -// src/home/components/EntityListDocsTable.d.ts:12:5 - (ae-forgotten-export) The symbol "actionFactories" needs to be exported by the entry point index.d.ts -// src/plugin.d.ts:29:5 - (ae-forgotten-export) The symbol "TabsConfig" needs to be exported by the entry point index.d.ts ``` diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 794e386136..7bc3e8726b 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs", "description": "The Backstage plugin that renders technical documentation for your components", - "version": "0.13.3", + "version": "0.14.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -11,6 +11,9 @@ "main": "dist/index.esm.js", "types": "dist/index.d.ts" }, + "backstage": { + "role": "frontend-plugin" + }, "homepage": "https://backstage.io", "repository": { "type": "git", @@ -22,27 +25,27 @@ "techdocs" ], "scripts": { - "build": "backstage-cli plugin:build", - "start": "backstage-cli plugin:serve", - "lint": "backstage-cli lint", - "test": "backstage-cli test", + "build": "backstage-cli package build", + "start": "backstage-cli package start", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", "diff": "backstage-cli plugin:diff", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/catalog-model": "^0.9.10", - "@backstage/config": "^0.1.13", - "@backstage/core-components": "^0.8.8", - "@backstage/core-plugin-api": "^0.6.0", - "@backstage/errors": "^0.2.0", - "@backstage/integration": "^0.7.2", - "@backstage/integration-react": "^0.1.21", - "@backstage/plugin-catalog": "^0.7.12", - "@backstage/plugin-catalog-react": "^0.6.14", - "@backstage/plugin-search": "^0.6.2", - "@backstage/theme": "^0.2.14", + "@backstage/catalog-model": "^0.11.0", + "@backstage/config": "^0.1.15", + "@backstage/core-components": "^0.8.10", + "@backstage/core-plugin-api": "^0.7.0", + "@backstage/errors": "^0.2.2", + "@backstage/integration": "^0.7.5", + "@backstage/integration-react": "^0.1.23", + "@backstage/plugin-catalog": "^0.9.0", + "@backstage/plugin-catalog-react": "^0.7.0", + "@backstage/plugin-search": "^0.7.1", + "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -62,10 +65,10 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.13.2", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.21", - "@backstage/test-utils": "^0.2.4", + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.4", + "@backstage/dev-utils": "^0.2.23", + "@backstage/test-utils": "^0.2.6", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^7.0.2", diff --git a/plugins/techdocs/src/Router.tsx b/plugins/techdocs/src/Router.tsx index 102cda979a..3a49f6edf9 100644 --- a/plugins/techdocs/src/Router.tsx +++ b/plugins/techdocs/src/Router.tsx @@ -19,15 +19,25 @@ import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; import { Route, Routes } from 'react-router-dom'; import { TechDocsIndexPage } from './home/components/TechDocsIndexPage'; -import { TechDocsPage as TechDocsReaderPage } from './reader/components/TechDocsPage'; +import { TechDocsReaderPage } from './reader/components/TechDocsReaderPage'; import { EntityPageDocs } from './EntityPageDocs'; import { MissingAnnotationEmptyState } from '@backstage/core-components'; const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref'; +/** + * Helper that takes in entity and returns true/false if TechDocs is available for the entity + * + * @public + */ export const isTechDocsAvailable = (entity: Entity) => Boolean(entity?.metadata?.annotations?.[TECHDOCS_ANNOTATION]); +/** + * Responsible for registering routes for TechDocs, TechDocs Homepage and separate TechDocs page + * + * @public + */ export const Router = () => { return ( @@ -40,12 +50,12 @@ export const Router = () => { ); }; -type Props = { - /** @deprecated The entity is now grabbed from context instead */ - entity?: Entity; -}; - -export const EmbeddedDocsRouter = (_props: Props) => { +/** + * Responsible for registering route to view docs on Entity page + * + * @public + */ +export const EmbeddedDocsRouter = () => { const { entity } = useEntity(); const projectId = entity.metadata.annotations?.[TECHDOCS_ANNOTATION]; diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx index ff4bf63724..4dcff501ed 100644 --- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.tsx @@ -34,20 +34,28 @@ import { UserListFilterKind, UserListPicker, } from '@backstage/plugin-catalog-react'; -import { EntityListDocsTable } from './EntityListDocsTable'; import { TechDocsPageWrapper } from './TechDocsPageWrapper'; import { TechDocsPicker } from './TechDocsPicker'; -import { DocsTableRow } from './types'; +import { DocsTableRow, EntityListDocsTable } from './Tables'; -export const DefaultTechDocsHome = ({ - initialFilter = 'all', - columns, - actions, -}: { +/** + * Props for {@link DefaultTechDocsHome} + * + * @public + */ +export type DefaultTechDocsHomeProps = { initialFilter?: UserListFilterKind; columns?: TableColumn[]; actions?: TableProps['actions']; -}) => { +}; + +/** + * Component which renders a default documentation landing page. + * + * @public + */ +export const DefaultTechDocsHome = (props: DefaultTechDocsHomeProps) => { + const { initialFilter = 'all', columns, actions } = props; return ( diff --git a/plugins/techdocs/src/home/components/DocsCardGrid.test.tsx b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.test.tsx similarity index 98% rename from plugins/techdocs/src/home/components/DocsCardGrid.test.tsx rename to plugins/techdocs/src/home/components/Grids/DocsCardGrid.test.tsx index e52636a54a..311f83544a 100644 --- a/plugins/techdocs/src/home/components/DocsCardGrid.test.tsx +++ b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.test.tsx @@ -18,7 +18,7 @@ import { configApiRef } from '@backstage/core-plugin-api'; import { wrapInTestApp } from '@backstage/test-utils'; import { render } from '@testing-library/react'; import React from 'react'; -import { rootDocsRouteRef } from '../../routes'; +import { rootDocsRouteRef } from '../../../routes'; import { DocsCardGrid } from './DocsCardGrid'; // Hacky way to mock a specific boolean config value. diff --git a/plugins/techdocs/src/home/components/DocsCardGrid.tsx b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx similarity index 84% rename from plugins/techdocs/src/home/components/DocsCardGrid.tsx rename to plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx index 1aef793bd2..6f1d9fbe61 100644 --- a/plugins/techdocs/src/home/components/DocsCardGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/DocsCardGrid.tsx @@ -14,25 +14,34 @@ * limitations under the License. */ -import React from 'react'; - +import { rootDocsRouteRef } from '../../../routes'; +import { toLowerMaybe } from '../../../helpers'; import { Entity } from '@backstage/catalog-model'; import { useApi, useRouteRef, configApiRef } from '@backstage/core-plugin-api'; -import { Card, CardActions, CardContent, CardMedia } from '@material-ui/core'; -import { rootDocsRouteRef } from '../../routes'; - import { Button, ItemCardGrid, ItemCardHeader, } from '@backstage/core-components'; -import { toLowerMaybe } from '../../helpers'; +import { Card, CardActions, CardContent, CardMedia } from '@material-ui/core'; +import React from 'react'; -export const DocsCardGrid = ({ - entities, -}: { +/** + * Props for {@link DocsCardGrid} + * + * @public + */ +export type DocsCardGridProps = { entities: Entity[] | undefined; -}) => { +}; + +/** + * Component which accepts a list of entities and renders a item card for each entity + * + * @public + */ +export const DocsCardGrid = (props: DocsCardGridProps) => { + const { entities } = props; const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); if (!entities) return null; diff --git a/plugins/techdocs/src/home/components/EntityListDocsGrid.tsx b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx similarity index 84% rename from plugins/techdocs/src/home/components/EntityListDocsGrid.tsx rename to plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx index 8c04a29f66..348104f5cc 100644 --- a/plugins/techdocs/src/home/components/EntityListDocsGrid.tsx +++ b/plugins/techdocs/src/home/components/Grids/EntityListDocsGrid.tsx @@ -13,17 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import { DocsCardGrid } from './DocsCardGrid'; import { CodeSnippet, Progress, WarningPanel, } from '@backstage/core-components'; -import { useEntityListProvider } from '@backstage/plugin-catalog-react'; +import { useEntityList } from '@backstage/plugin-catalog-react'; import React from 'react'; -import { DocsCardGrid } from './DocsCardGrid'; +/** + * Component responsible to get entities from entity list context and pass down to DocsCardGrid + * + * @public + */ export const EntityListDocsGrid = () => { - const { loading, error, entities } = useEntityListProvider(); + const { loading, error, entities } = useEntityList(); if (error) { return ( diff --git a/plugins/techdocs/src/home/components/Grids/index.ts b/plugins/techdocs/src/home/components/Grids/index.ts new file mode 100644 index 0000000000..c28b5a0723 --- /dev/null +++ b/plugins/techdocs/src/home/components/Grids/index.ts @@ -0,0 +1,18 @@ +/* + * Copyright 2022 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 './EntityListDocsGrid'; +export * from './DocsCardGrid'; diff --git a/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx b/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx index 34ea416026..536c2c80c8 100644 --- a/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx +++ b/plugins/techdocs/src/home/components/LegacyTechDocsHome.tsx @@ -17,6 +17,9 @@ import React from 'react'; import { PanelType, TechDocsCustomHome } from './TechDocsCustomHome'; +/** + * @deprecated Use {@link TechDocsCustomHome} instead. + */ export const LegacyTechDocsHome = () => { const tabsConfig = [ { diff --git a/plugins/techdocs/src/home/components/DocsTable.test.tsx b/plugins/techdocs/src/home/components/Tables/DocsTable.test.tsx similarity index 96% rename from plugins/techdocs/src/home/components/DocsTable.test.tsx rename to plugins/techdocs/src/home/components/Tables/DocsTable.test.tsx index 4564725db6..730354f253 100644 --- a/plugins/techdocs/src/home/components/DocsTable.test.tsx +++ b/plugins/techdocs/src/home/components/Tables/DocsTable.test.tsx @@ -18,7 +18,7 @@ import { render } from '@testing-library/react'; import { wrapInTestApp } from '@backstage/test-utils'; import { configApiRef } from '@backstage/core-plugin-api'; import { DocsTable } from './DocsTable'; -import { rootDocsRouteRef } from '../../routes'; +import { rootDocsRouteRef } from '../../../routes'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; // Hacky way to mock a specific boolean config value. @@ -66,6 +66,7 @@ describe('DocsTable test', () => { namespace: 'default', name: 'owned', }, + targetRef: 'user:default/owned', type: 'ownedBy', }, ], @@ -86,6 +87,7 @@ describe('DocsTable test', () => { namespace: 'default', name: 'not-owned', }, + targetRef: 'user:default/not-owned', type: 'ownedBy', }, ], @@ -137,6 +139,7 @@ describe('DocsTable test', () => { namespace: 'default', name: 'owned', }, + targetRef: 'user:default/owned', type: 'ownedBy', }, ], diff --git a/plugins/techdocs/src/home/components/DocsTable.tsx b/plugins/techdocs/src/home/components/Tables/DocsTable.tsx similarity index 86% rename from plugins/techdocs/src/home/components/DocsTable.tsx rename to plugins/techdocs/src/home/components/Tables/DocsTable.tsx index a00d608b0f..b875ba4e98 100644 --- a/plugins/techdocs/src/home/components/DocsTable.tsx +++ b/plugins/techdocs/src/home/components/Tables/DocsTable.tsx @@ -23,7 +23,7 @@ import { formatEntityRefTitle, getEntityRelations, } from '@backstage/plugin-catalog-react'; -import { rootDocsRouteRef } from '../../routes'; +import { rootDocsRouteRef } from '../../../routes'; import { Button, EmptyState, @@ -31,24 +31,31 @@ import { TableColumn, TableProps, } from '@backstage/core-components'; -import * as actionFactories from './actions'; -import * as columnFactories from './columns'; +import { actionFactories } from './actions'; +import { columnFactories } from './columns'; +import { toLowerMaybe } from '../../../helpers'; import { DocsTableRow } from './types'; -import { toLowerMaybe } from '../../helpers'; -export const DocsTable = ({ - entities, - title, - loading, - columns, - actions, -}: { +/** + * Props for {@link DocsTable}. + * + * @public + */ +export type DocsTableProps = { entities: Entity[] | undefined; title?: string | undefined; loading?: boolean | undefined; columns?: TableColumn[]; actions?: TableProps['actions']; -}) => { +}; + +/** + * Component which renders a table documents + * + * @public + */ +export const DocsTable = (props: DocsTableProps) => { + const { entities, title, loading, columns, actions } = props; const [, copyToClipboard] = useCopyToClipboard(); const getRouteToReaderPageFor = useRouteRef(rootDocsRouteRef); const config = useApi(configApiRef); @@ -124,3 +131,6 @@ export const DocsTable = ({ ); }; + +DocsTable.columns = columnFactories; +DocsTable.actions = actionFactories; diff --git a/plugins/techdocs/src/home/components/EntityListDocsTable.tsx b/plugins/techdocs/src/home/components/Tables/EntityListDocsTable.tsx similarity index 80% rename from plugins/techdocs/src/home/components/EntityListDocsTable.tsx rename to plugins/techdocs/src/home/components/Tables/EntityListDocsTable.tsx index 4d136591ff..d47ddb13c4 100644 --- a/plugins/techdocs/src/home/components/EntityListDocsTable.tsx +++ b/plugins/techdocs/src/home/components/Tables/EntityListDocsTable.tsx @@ -24,22 +24,32 @@ import { WarningPanel, } from '@backstage/core-components'; import { - useEntityListProvider, + useEntityList, useStarredEntities, } from '@backstage/plugin-catalog-react'; import { DocsTable } from './DocsTable'; -import * as actionFactories from './actions'; -import * as columnFactories from './columns'; +import { actionFactories } from './actions'; +import { columnFactories } from './columns'; import { DocsTableRow } from './types'; -export const EntityListDocsTable = ({ - columns, - actions, -}: { +/** + * Props for {@link EntityListDocsTable}. + * + * @public + */ +export type EntityListDocsTableProps = { columns?: TableColumn[]; actions?: TableProps['actions']; -}) => { - const { loading, error, entities, filters } = useEntityListProvider(); +}; + +/** + * Component which renders a table with entities from catalog. + * + * @public + */ +export const EntityListDocsTable = (props: EntityListDocsTableProps) => { + const { columns, actions } = props; + const { loading, error, entities, filters } = useEntityList(); const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const [, copyToClipboard] = useCopyToClipboard(); diff --git a/plugins/techdocs/src/home/components/Tables/actions.tsx b/plugins/techdocs/src/home/components/Tables/actions.tsx new file mode 100644 index 0000000000..d0dbb8bf47 --- /dev/null +++ b/plugins/techdocs/src/home/components/Tables/actions.tsx @@ -0,0 +1,55 @@ +/* + * 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 ShareIcon from '@material-ui/icons/Share'; +import { + favoriteEntityIcon, + favoriteEntityTooltip, +} from '@backstage/plugin-catalog-react'; +import { DocsTableRow } from './types'; + +/** + * Not directly exported, but through DocsTable.actions and EntityListDocsTable.actions + * + * @public + */ +export const actionFactories = { + createCopyDocsUrlAction(copyToClipboard: Function) { + return (row: DocsTableRow) => { + return { + icon: () => , + tooltip: 'Click to copy documentation link to clipboard', + onClick: () => + copyToClipboard(`${window.location.origin}${row.resolved.docsUrl}`), + }; + }; + }, + createStarEntityAction( + isStarredEntity: Function, + toggleStarredEntity: Function, + ) { + return ({ entity }: DocsTableRow) => { + const isStarred = isStarredEntity(entity); + return { + cellStyle: { paddingLeft: '1em' }, + icon: () => favoriteEntityIcon(isStarred), + tooltip: favoriteEntityTooltip(isStarred), + onClick: () => toggleStarredEntity(entity), + }; + }; + }, +}; diff --git a/plugins/techdocs/src/home/components/Tables/columns.tsx b/plugins/techdocs/src/home/components/Tables/columns.tsx new file mode 100644 index 0000000000..774cfc5e8a --- /dev/null +++ b/plugins/techdocs/src/home/components/Tables/columns.tsx @@ -0,0 +1,66 @@ +/* + * 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 { Link, SubvalueCell, TableColumn } from '@backstage/core-components'; +import { EntityRefLinks } from '@backstage/plugin-catalog-react'; +import { Entity } from '@backstage/catalog-model'; +import { DocsTableRow } from './types'; + +function customTitle(entity: Entity): string { + return entity.metadata.title || entity.metadata.name; +} + +/** + * Not directly exported, but through DocsTable.columns and EntityListDocsTable.columns + * + * @public + */ +export const columnFactories = { + createNameColumn(): TableColumn { + return { + title: 'Document', + field: 'entity.metadata.name', + highlight: true, + render: (row: DocsTableRow) => ( + {customTitle(row.entity)} + } + subvalue={row.entity.metadata.description} + /> + ), + }; + }, + createOwnerColumn(): TableColumn { + return { + title: 'Owner', + field: 'resolved.ownedByRelationsTitle', + render: ({ resolved }) => ( + + ), + }; + }, + createTypeColumn(): TableColumn { + return { + title: 'Type', + field: 'entity.spec.type', + }; + }, +}; diff --git a/plugins/techdocs/src/home/components/Tables/index.ts b/plugins/techdocs/src/home/components/Tables/index.ts new file mode 100644 index 0000000000..2db7f0ae89 --- /dev/null +++ b/plugins/techdocs/src/home/components/Tables/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2022 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 './EntityListDocsTable'; +export * from './DocsTable'; +export * from './types'; diff --git a/plugins/techdocs/src/home/components/types.ts b/plugins/techdocs/src/home/components/Tables/types.ts similarity index 90% rename from plugins/techdocs/src/home/components/types.ts rename to plugins/techdocs/src/home/components/Tables/types.ts index 6fd29f1716..c076d4c12d 100644 --- a/plugins/techdocs/src/home/components/types.ts +++ b/plugins/techdocs/src/home/components/Tables/types.ts @@ -16,6 +16,11 @@ import { Entity, EntityName } from '@backstage/catalog-model'; +/** + * Generic representing the metadata structure for a docs table row. + * + * @public + */ export type DocsTableRow = { entity: Entity; resolved: { diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx index b32cadaa05..c8149f1ff2 100644 --- a/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx +++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.tsx @@ -15,7 +15,7 @@ */ import React, { useState } from 'react'; -import useAsync from 'react-use/lib/useAsync'; +import useAsync, { AsyncState } from 'react-use/lib/useAsync'; import { makeStyles } from '@material-ui/core'; import { CSSProperties } from '@material-ui/styles'; import { @@ -23,11 +23,15 @@ import { catalogApiRef, CatalogApi, isOwnerOf, - useOwnUser, } from '@backstage/plugin-catalog-react'; -import { Entity } from '@backstage/catalog-model'; -import { DocsTable } from './DocsTable'; -import { DocsCardGrid } from './DocsCardGrid'; +import { + DEFAULT_NAMESPACE, + Entity, + parseEntityRef, + UserEntity, +} from '@backstage/catalog-model'; +import { DocsTable } from './Tables'; +import { DocsCardGrid } from './Grids'; import { TechDocsPageWrapper } from './TechDocsPageWrapper'; import { @@ -40,15 +44,25 @@ import { ContentHeader, } from '@backstage/core-components'; -import { useApi } from '@backstage/core-plugin-api'; +import { identityApiRef, useApi } from '@backstage/core-plugin-api'; const panels = { DocsTable: DocsTable, DocsCardGrid: DocsCardGrid, }; +/** + * Available panel types + * + * @public + */ export type PanelType = 'DocsCardGrid' | 'DocsTable'; +/** + * Type representing a TechDocsCustomHome panel. + * + * @public + */ export interface PanelConfig { title: string; description: string; @@ -57,11 +71,21 @@ export interface PanelConfig { filterPredicate: ((entity: Entity) => boolean) | string; } +/** + * Type representing a TechDocsCustomHome tab. + * + * @public + */ export interface TabConfig { label: string; panels: PanelConfig[]; } +/** + * Type representing a list of TechDocsCustomHome tabs. + * + * @public + */ export type TabsConfig = TabConfig[]; const CustomPanel = ({ @@ -114,11 +138,17 @@ const CustomPanel = ({ ); }; -export const TechDocsCustomHome = ({ - tabsConfig, -}: { +/** + * Props for {@link TechDocsCustomHome} + * + * @public + */ +export type TechDocsCustomHomeProps = { tabsConfig: TabsConfig; -}) => { +}; + +export const TechDocsCustomHome = (props: TechDocsCustomHomeProps) => { + const { tabsConfig } = props; const [selectedTab, setSelectedTab] = useState(0); const catalogApi: CatalogApi = useApi(catalogApiRef); @@ -195,3 +225,18 @@ export const TechDocsCustomHome = ({ ); }; + +function useOwnUser(): AsyncState { + const catalogApi = useApi(catalogApiRef); + const identityApi = useApi(identityApiRef); + + return useAsync(async () => { + const identity = await identityApi.getBackstageIdentity(); + return catalogApi.getEntityByName( + parseEntityRef(identity.userEntityRef, { + defaultKind: 'User', + defaultNamespace: DEFAULT_NAMESPACE, + }), + ) as Promise; + }, [catalogApi, identityApi]); +} diff --git a/plugins/techdocs/src/home/components/TechDocsIndexPage.test.tsx b/plugins/techdocs/src/home/components/TechDocsIndexPage.test.tsx index fbeca7cfd4..1d018eec70 100644 --- a/plugins/techdocs/src/home/components/TechDocsIndexPage.test.tsx +++ b/plugins/techdocs/src/home/components/TechDocsIndexPage.test.tsx @@ -24,8 +24,8 @@ jest.mock('react-router', () => ({ useOutlet: jest.fn().mockReturnValue('Route Children'), })); -jest.mock('./LegacyTechDocsHome', () => ({ - LegacyTechDocsHome: jest.fn().mockReturnValue('LegacyTechDocsHomeMock'), +jest.mock('./DefaultTechDocsHome', () => ({ + DefaultTechDocsHome: jest.fn().mockReturnValue('DefaultTechDocsHomeMock'), })); describe('TechDocsIndexPage', () => { @@ -35,10 +35,10 @@ describe('TechDocsIndexPage', () => { expect(getByText('Route Children')).toBeInTheDocument(); }); - it('renders legacy TechDocs home when no router children are provided', async () => { + it('renders DefaultTechDocsHome when no router children are provided', async () => { (useOutlet as jest.Mock).mockReturnValueOnce(null); const { getByText } = await renderInTestApp(); - expect(getByText('LegacyTechDocsHomeMock')).toBeInTheDocument(); + expect(getByText('DefaultTechDocsHomeMock')).toBeInTheDocument(); }); }); diff --git a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx index ff694d3570..4467baf2a1 100644 --- a/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx +++ b/plugins/techdocs/src/home/components/TechDocsIndexPage.tsx @@ -16,10 +16,10 @@ import React from 'react'; import { useOutlet } from 'react-router'; -import { LegacyTechDocsHome } from './LegacyTechDocsHome'; +import { DefaultTechDocsHome } from './DefaultTechDocsHome'; export const TechDocsIndexPage = () => { const outlet = useOutlet(); - return outlet || ; + return outlet || ; }; diff --git a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx index 453cfc22cc..52511bda19 100644 --- a/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx +++ b/plugins/techdocs/src/home/components/TechDocsPageWrapper.tsx @@ -19,11 +19,22 @@ import React from 'react'; import { PageWithHeader } from '@backstage/core-components'; import { useApi, configApiRef } from '@backstage/core-plugin-api'; -type Props = { +/** + * Props for {@link TechDocsPageWrapper} + * + * @public + */ +export type TechDocsPageWrapperProps = { children?: React.ReactNode; }; -export const TechDocsPageWrapper = ({ children }: Props) => { +/** + * Component wrapping a techdocs page with Page and Header components + * + * @public + */ +export const TechDocsPageWrapper = (props: TechDocsPageWrapperProps) => { + const { children } = props; const configApi = useApi(configApiRef); const generatedSubtitle = `Documentation available in ${ configApi.getOptionalString('organization.name') ?? 'Backstage' diff --git a/plugins/techdocs/src/home/components/TechDocsPicker.tsx b/plugins/techdocs/src/home/components/TechDocsPicker.tsx index 9ed21da368..32c7b800eb 100644 --- a/plugins/techdocs/src/home/components/TechDocsPicker.tsx +++ b/plugins/techdocs/src/home/components/TechDocsPicker.tsx @@ -19,7 +19,7 @@ import { CATALOG_FILTER_EXISTS, DefaultEntityFilters, EntityFilter, - useEntityListProvider, + useEntityList, } from '@backstage/plugin-catalog-react'; class TechDocsFilter implements EntityFilter { @@ -34,8 +34,13 @@ type CustomFilters = DefaultEntityFilters & { techdocs?: TechDocsFilter; }; +/** + * Component responsible for updating TechDocs filters + * + * @public + */ export const TechDocsPicker = () => { - const { updateFilters } = useEntityListProvider(); + const { updateFilters } = useEntityList(); useEffect(() => { updateFilters({ diff --git a/plugins/techdocs/src/home/components/actions.tsx b/plugins/techdocs/src/home/components/actions.tsx deleted file mode 100644 index eb09c1d429..0000000000 --- a/plugins/techdocs/src/home/components/actions.tsx +++ /dev/null @@ -1,49 +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 ShareIcon from '@material-ui/icons/Share'; -import { - favoriteEntityIcon, - favoriteEntityTooltip, -} from '@backstage/plugin-catalog-react'; -import { DocsTableRow } from './types'; - -export function createCopyDocsUrlAction(copyToClipboard: Function) { - return (row: DocsTableRow) => { - return { - icon: () => , - tooltip: 'Click to copy documentation link to clipboard', - onClick: () => - copyToClipboard(`${window.location.origin}${row.resolved.docsUrl}`), - }; - }; -} - -export function createStarEntityAction( - isStarredEntity: Function, - toggleStarredEntity: Function, -) { - return ({ entity }: DocsTableRow) => { - const isStarred = isStarredEntity(entity); - return { - cellStyle: { paddingLeft: '1em' }, - icon: () => favoriteEntityIcon(isStarred), - tooltip: favoriteEntityTooltip(isStarred), - onClick: () => toggleStarredEntity(entity), - }; - }; -} diff --git a/plugins/techdocs/src/home/components/columns.tsx b/plugins/techdocs/src/home/components/columns.tsx deleted file mode 100644 index d29f44da75..0000000000 --- a/plugins/techdocs/src/home/components/columns.tsx +++ /dev/null @@ -1,59 +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 { Link, SubvalueCell, TableColumn } from '@backstage/core-components'; -import { EntityRefLinks } from '@backstage/plugin-catalog-react'; -import { Entity } from '@backstage/catalog-model'; -import { DocsTableRow } from './types'; - -function customTitle(entity: Entity): string { - return entity.metadata.title || entity.metadata.name; -} - -export function createNameColumn(): TableColumn { - return { - title: 'Document', - field: 'entity.metadata.name', - highlight: true, - render: (row: DocsTableRow) => ( - {customTitle(row.entity)}} - subvalue={row.entity.metadata.description} - /> - ), - }; -} - -export function createOwnerColumn(): TableColumn { - return { - title: 'Owner', - field: 'resolved.ownedByRelationsTitle', - render: ({ resolved }) => ( - - ), - }; -} - -export function createTypeColumn(): TableColumn { - return { - title: 'Type', - field: 'entity.spec.type', - }; -} diff --git a/plugins/techdocs/src/home/components/index.ts b/plugins/techdocs/src/home/components/index.ts index d9d627cf8d..d21f992cef 100644 --- a/plugins/techdocs/src/home/components/index.ts +++ b/plugins/techdocs/src/home/components/index.ts @@ -14,10 +14,15 @@ * limitations under the License. */ -export { DefaultTechDocsHome } from './DefaultTechDocsHome'; -export { EntityListDocsGrid } from './EntityListDocsGrid'; -export { EntityListDocsTable } from './EntityListDocsTable'; -export type { PanelType } from './TechDocsCustomHome'; -export { TechDocsPageWrapper } from './TechDocsPageWrapper'; -export { TechDocsPicker } from './TechDocsPicker'; -export type { DocsTableRow } from './types'; +export * from './Grids'; +export * from './Tables'; +export * from './DefaultTechDocsHome'; +export type { + PanelType, + PanelConfig, + TabConfig, + TabsConfig, + TechDocsCustomHomeProps, +} from './TechDocsCustomHome'; +export * from './TechDocsPageWrapper'; +export * from './TechDocsPicker'; diff --git a/plugins/techdocs/src/home/index.ts b/plugins/techdocs/src/home/index.ts new file mode 100644 index 0000000000..1be7f65c25 --- /dev/null +++ b/plugins/techdocs/src/home/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 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 './components'; diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts index a6a95e9264..c31f8b7370 100644 --- a/plugins/techdocs/src/index.ts +++ b/plugins/techdocs/src/index.ts @@ -20,29 +20,19 @@ * @packageDocumentation */ +export * from './types'; export * from './api'; -export { techdocsApiRef, techdocsStorageApiRef } from './api'; -export type { TechDocsApi, TechDocsStorageApi } from './api'; -export { TechDocsClient, TechDocsStorageClient } from './client'; -export * from './components/DocsResultListItem'; +export * from './client'; +export * from './reader'; +export * from './search'; +export * from './home'; export { - DefaultTechDocsHome, - EntityListDocsGrid, - EntityListDocsTable, - TechDocsPageWrapper, - TechDocsPicker, -} from './home/components'; -export type { DocsTableRow, PanelType } from './home/components'; -export { - DocsCardGrid, - DocsTable, EntityTechdocsContent, TechDocsCustomHome, TechDocsIndexPage, TechdocsPage, + TechDocsReaderPage, techdocsPlugin as plugin, techdocsPlugin, - TechDocsReaderPage, } from './plugin'; -export * from './reader'; -export { EmbeddedDocsRouter, isTechDocsAvailable, Router } from './Router'; +export * from './Router'; diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts index a37ad61f80..2e3967cb6d 100644 --- a/plugins/techdocs/src/plugin.ts +++ b/plugins/techdocs/src/plugin.ts @@ -24,7 +24,6 @@ import { import { configApiRef, createApiFactory, - createComponentExtension, createPlugin, createRoutableExtension, discoveryApiRef, @@ -32,6 +31,11 @@ import { identityApiRef, } from '@backstage/core-plugin-api'; +/** + * The Backstage plugin that renders technical documentation for your components + * + * @public + */ export const techdocsPlugin = createPlugin({ id: 'techdocs', apis: [ @@ -73,6 +77,11 @@ export const techdocsPlugin = createPlugin({ }, }); +/** + * Routable extension used to render docs + * + * @public + */ export const TechdocsPage = techdocsPlugin.provide( createRoutableExtension({ name: 'TechdocsPage', @@ -81,6 +90,11 @@ export const TechdocsPage = techdocsPlugin.provide( }), ); +/** + * Routable extension used to render docs on Entity page + * + * @public + */ export const EntityTechdocsContent = techdocsPlugin.provide( createRoutableExtension({ name: 'EntityTechdocsContent', @@ -89,28 +103,11 @@ export const EntityTechdocsContent = techdocsPlugin.provide( }), ); -// takes a list of entities and renders documentation cards -export const DocsCardGrid = techdocsPlugin.provide( - createComponentExtension({ - name: 'DocsCardGrid', - component: { - lazy: () => - import('./home/components/DocsCardGrid').then(m => m.DocsCardGrid), - }, - }), -); - -// takes a list of entities and renders table listing documentation -export const DocsTable = techdocsPlugin.provide( - createComponentExtension({ - name: 'DocsTable', - component: { - lazy: () => import('./home/components/DocsTable').then(m => m.DocsTable), - }, - }), -); - -// takes a custom tabs config object and renders a documentation landing page +/** + * Component which takes a custom tabs config object and renders a documentation landing page. + * + * @public + */ export const TechDocsCustomHome = techdocsPlugin.provide( createRoutableExtension({ name: 'TechDocsCustomHome', @@ -122,6 +119,11 @@ export const TechDocsCustomHome = techdocsPlugin.provide( }), ); +/** + * Responsible for rendering the provided router element + * + * @public + */ export const TechDocsIndexPage = techdocsPlugin.provide( createRoutableExtension({ name: 'TechDocsIndexPage', @@ -133,11 +135,18 @@ export const TechDocsIndexPage = techdocsPlugin.provide( }), ); +/** + * Component responsible for composing a TechDocs reader page experience + * + * @public + */ export const TechDocsReaderPage = techdocsPlugin.provide( createRoutableExtension({ name: 'TechDocsReaderPage', component: () => - import('./reader/components/TechDocsPage').then(m => m.TechDocsPage), + import('./reader/components/TechDocsReaderPage').then( + m => m.TechDocsReaderPage, + ), mountPoint: rootDocsRouteRef, }), ); diff --git a/plugins/techdocs/src/reader/components/LegacyTechDocsPage.tsx b/plugins/techdocs/src/reader/components/LegacyTechDocsPage.tsx index f3384e21b3..4bfda57ace 100644 --- a/plugins/techdocs/src/reader/components/LegacyTechDocsPage.tsx +++ b/plugins/techdocs/src/reader/components/LegacyTechDocsPage.tsx @@ -22,8 +22,11 @@ import { TechDocsNotFound } from './TechDocsNotFound'; import { useApi } from '@backstage/core-plugin-api'; import { Page, Content } from '@backstage/core-components'; import { Reader } from './Reader'; -import { TechDocsPageHeader } from './TechDocsPageHeader'; +import { TechDocsReaderPageHeader } from './TechDocsReaderPageHeader'; +/** + * @deprecated Use {@link TechDocsReaderPage} instead. + */ export const LegacyTechDocsPage = () => { const [documentReady, setDocumentReady] = useState(false); const { namespace, kind, name } = useParams(); @@ -53,7 +56,7 @@ export const LegacyTechDocsPage = () => { return ( - void; @@ -62,12 +75,13 @@ type Props = { const useStyles = makeStyles(theme => ({ searchBar: { - marginLeft: '20rem', - maxWidth: 'calc(100% - 20rem * 2 - 3rem)', - marginTop: theme.spacing(1), + maxWidth: 'calc(100% - 16rem * 2 - 2.4rem)', + marginTop: 0, + marginBottom: theme.spacing(1), + marginLeft: 'calc(16rem + 1.2rem)', '@media screen and (max-width: 76.1875em)': { - marginLeft: '10rem', - maxWidth: 'calc(100% - 10rem)', + marginLeft: '0', + maxWidth: '100%', }, }, })); @@ -121,6 +135,15 @@ export const withTechDocsReaderProvider = */ export const useTechDocsReader = () => useContext(TechDocsReaderContext); +type TypographyHeadings = Pick< + Theme['typography'], + 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' +>; + +type TypographyHeadingsKeys = keyof TypographyHeadings; + +const headings: TypographyHeadingsKeys[] = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; + /** * Hook that encapsulates the behavior of getting raw HTML and applying * transforms to it in order to make it function at a basic level in the @@ -154,11 +177,20 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => { if (!dom || !sidebars) return; // set sidebar height so they don't initially render in wrong position const mdTabs = dom.querySelector('.md-container > .md-tabs'); + const sidebarsCollapsed = window.matchMedia( + 'screen and (max-width: 76.1875em)', + ).matches; + const newTop = Math.max(dom.getBoundingClientRect().top, 0); sidebars.forEach(sidebar => { - const newTop = Math.max(dom.getBoundingClientRect().top, 0); - sidebar.style.top = mdTabs - ? `${newTop + mdTabs.getBoundingClientRect().height}px` - : `${newTop}px`; + if (sidebarsCollapsed) { + sidebar.style.top = '0px'; + } else if (mdTabs) { + sidebar.style.top = `${ + newTop + mdTabs.getBoundingClientRect().height + }px`; + } else { + sidebar.style.top = `${newTop}px`; + } }); }, [dom, sidebars]); @@ -205,189 +237,512 @@ export const useTechDocsReaderDom = (entityRef: EntityName): Element | null => { path: contentPath, }), rewriteDocLinks(), + addSidebarToggle(), removeMkdocsHeader(), simplifyMkdocsFooter(), addGitFeedbackLink(scmIntegrationsApi), injectCss({ + // Variables css: ` - body { - font-family: ${theme.typography.fontFamily}; - --md-text-color: ${theme.palette.text.primary}; - --md-text-link-color: ${theme.palette.primary.main}; + /* + As the MkDocs output is rendered in shadow DOM, the CSS variable definitions on the root selector are not applied. Instead, they have to be applied on :host. + As there is no way to transform the served main*.css yet (for example in the backend), we have to copy from main*.css and modify them. + */ + :host { + /* FONT */ + --md-default-fg-color: ${theme.palette.text.primary}; + --md-default-fg-color--light: ${theme.palette.text.secondary}; + --md-default-fg-color--lighter: ${lighten( + theme.palette.text.secondary, + 0.7, + )}; + --md-default-fg-color--lightest: ${lighten( + theme.palette.text.secondary, + 0.3, + )}; - --md-code-fg-color: ${theme.palette.text.primary}; - --md-code-bg-color: ${theme.palette.background.paper}; - --md-accent-fg-color: ${theme.palette.primary.main}; - --md-default-fg-color--lightest: ${theme.palette.textVerySubtle}; - } - .codehilite .gd, .highlight .gd { - background-color: ${isDarkTheme ? 'rgba(248,81,73,0.65)' : '#fdd'}; - } - .codehilite .gi, .highlight .gi { - background-color: ${isDarkTheme ? 'rgba(46,160,67,0.65)' : '#dfd'}; - } - .highlight .kd { - color: ${isDarkTheme ? '#4aaaf7' : '#3b78e7'}; - } - .highlight .k { - color: ${isDarkTheme ? '#4aaaf7' : '#3b78e7'}; - } - .highlight .nx { - color: ${isDarkTheme ? '#ff53a3' : '#ec407a'}; - } - .highlight .s1 { - color: ${isDarkTheme ? '#1cad46' : 'rgb(13, 144, 79)'}; - } - .highlight .kt { - color: ${isDarkTheme ? '#4aaaf7' : '#3e61a2'}; - } - .md-main__inner { margin-top: 0; } - .md-sidebar { position: fixed; bottom: 100px; width: 20rem; } - .md-sidebar--secondary { right: 2rem; } - .md-content { margin-bottom: 50px } - .md-footer { position: fixed; bottom: 0px; } - .md-footer-nav__link { width: 20rem;} - .md-content { margin-left: 20rem; max-width: calc(100% - 20rem * 2 - 3rem); } - .md-typeset { font-size: 1rem; } - .md-typeset h1, .md-typeset h2, .md-typeset h3 { font-weight: bold; } - .md-nav { font-size: 1rem; } - .md-grid { max-width: 90vw; margin: 0 } - .md-typeset blockquote { - color: ${theme.palette.textSubtle}; - border-left: 0.2rem solid ${theme.palette.textVerySubtle}; - } - .md-typeset hr { - border-bottom: 0.05rem dotted ${theme.palette.textVerySubtle}; - } - .md-typeset table:not([class]) { - font-size: 1rem; - border: 1px solid ${theme.palette.text.primary}; - border-bottom: none; - border-collapse: collapse; - } - .md-typeset table:not([class]) td, .md-typeset table:not([class]) th { - border-bottom: 1px solid ${theme.palette.text.primary}; - } - .md-typeset table:not([class]) th { font-weight: bold; } - .md-typeset .admonition, .md-typeset details { - font-size: 1rem; + /* BACKGROUND */ + --md-default-bg-color:${theme.palette.background.default}; + --md-default-bg-color--light: ${theme.palette.background.paper}; + --md-default-bg-color--lighter: ${lighten( + theme.palette.background.paper, + 0.7, + )}; + --md-default-bg-color--lightest: ${lighten( + theme.palette.background.paper, + 0.3, + )}; + + /* PRIMARY */ + --md-primary-fg-color: ${theme.palette.primary.main}; + --md-primary-fg-color--light: ${theme.palette.primary.light}; + --md-primary-fg-color--dark: ${theme.palette.primary.dark}; + --md-primary-bg-color: ${theme.palette.primary.contrastText}; + --md-primary-bg-color--light: ${lighten( + theme.palette.primary.contrastText, + 0.7, + )}; + + /* ACCENT */ + --md-accent-fg-color: var(--md-primary-fg-color); + + /* SHADOW */ + --md-shadow-z1: ${theme.shadows[1]}; + --md-shadow-z2: ${theme.shadows[2]}; + --md-shadow-z3: ${theme.shadows[3]}; + + /* EXTENSIONS */ + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + /* Admonitions and others are using SVG masks to define icons. These masks are defined as CSS variables. */ + --md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--info: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--tip: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--success: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--question: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--warning: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--failure: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--danger: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--bug: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--example: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--quote: url('data:image/svg+xml;charset=utf-8,'); + --md-footnotes-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-details-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); + --md-nav-icon--prev: url('data:image/svg+xml;charset=utf-8,'); + --md-nav-icon--next: url('data:image/svg+xml;charset=utf-8,'); + --md-toc-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-clipboard-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-search-result-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-source-forks-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-source-repositories-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-source-stars-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-source-version-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-version-icon: url('data:image/svg+xml;charset=utf-8,'); } - /* style the checkmarks of the task list */ - .md-typeset .task-list-control .task-list-indicator::before { - background-color: ${theme.palette.action.disabledBackground}; + :host > * { + /* CODE */ + --md-code-fg-color: ${theme.palette.text.primary}; + --md-code-bg-color: ${theme.palette.background.paper}; + --md-code-hl-color: ${alpha(theme.palette.warning.main, 0.5)}; + --md-code-hl-keyword-color: ${ + isDarkTheme + ? theme.palette.primary.light + : theme.palette.primary.dark + }; + --md-code-hl-function-color: ${ + isDarkTheme + ? theme.palette.secondary.light + : theme.palette.secondary.dark + }; + --md-code-hl-string-color: ${ + isDarkTheme + ? theme.palette.success.light + : theme.palette.success.dark + }; + --md-code-hl-number-color: ${ + isDarkTheme ? theme.palette.error.light : theme.palette.error.dark + }; + --md-code-hl-constant-color: var(--md-code-hl-function-color); + --md-code-hl-special-color: var(--md-code-hl-function-color); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + + /* TYPESET */ + --md-typeset-font-size: 1rem; + --md-typeset-color: var(--md-default-fg-color); + --md-typeset-a-color: var(--md-accent-fg-color); + --md-typeset-table-color: ${theme.palette.text.primary}; + --md-typeset-del-color: ${ + isDarkTheme + ? alpha(theme.palette.error.dark, 0.5) + : alpha(theme.palette.error.light, 0.5) + }; + --md-typeset-ins-color: ${ + isDarkTheme + ? alpha(theme.palette.success.dark, 0.5) + : alpha(theme.palette.success.light, 0.5) + }; + --md-typeset-mark-color: ${ + isDarkTheme + ? alpha(theme.palette.warning.dark, 0.5) + : alpha(theme.palette.warning.light, 0.5) + }; } - .md-typeset .task-list-control [type="checkbox"]:checked + .task-list-indicator:before { - background-color: ${theme.palette.success.main}; - } - /**/ @media screen and (max-width: 76.1875em) { - .md-nav { - background-color: ${theme.palette.background.default}; - transition: none !important - } - .md-sidebar--secondary { display: none; } - .md-sidebar--primary { left: ${ - isPinned ? '242px' : '72px' - }; width: 10rem } - .md-content { margin-left: 10rem; max-width: calc(100% - 10rem); } - .md-content__inner { font-size: 0.9rem } - .md-footer { - position: static; - padding-left: 10rem; - } - .md-footer-nav__link { - /* footer links begin to overlap at small sizes without setting width */ - width: 50%; - } - .md-nav--primary .md-nav__title { - white-space: normal; - height: auto; - line-height: 1rem; - cursor: auto; - } - .md-nav--primary > .md-nav__title [for="none"] { - padding-top: 0; + :host > * { + /* TYPESET */ + --md-typeset-font-size: .9rem; } } - `, + + @media screen and (max-width: 600px) { + :host > * { + /* TYPESET */ + --md-typeset-font-size: .7rem; + } + } + `, }), injectCss({ - // Disable CSS animations on link colors as they lead to issues in dark - // mode. The dark mode color theme is applied later and theirfore there - // is always an animation from light to dark mode when navigation - // between pages. + // Reset css: ` - .md-nav__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink { - transition: none; + body { + --md-text-color: var(--md-default-fg-color); + --md-text-link-color: var(--md-accent-fg-color); + --md-text-font-family: ${theme.typography.fontFamily}; + font-family: var(--md-text-font-family); + background-color: unset; } `, }), injectCss({ - // Properly style code blocks. + // Layout css: ` + .md-grid { + max-width: 100%; + margin: 0; + } + + .md-nav { + font-size: calc(var(--md-typeset-font-size) * 0.9); + } + .md-nav__link { + display: flex; + align-items: center; + justify-content: space-between; + } + .md-nav__icon { + height: 20px !important; + width: 20px !important; + margin-left:${theme.spacing(1)}px; + } + .md-nav__icon svg { + margin: 0; + width: 20px !important; + height: 20px !important; + } + .md-nav__icon:after { + width: 20px !important; + height: 20px !important; + } + + .md-main__inner { + margin-top: 0; + } + + .md-sidebar { + height: calc(100% - 100px); + position: fixed; + width: 16rem; + } + .md-sidebar .md-sidebar__scrollwrap { + max-height: calc(100% - 100px); + } + .md-sidebar--secondary { + right: ${theme.spacing(3)}px; + } + + .md-content { + max-width: calc(100% - 16rem * 2); + margin-left: 16rem; + margin-bottom: 50px; + } + + .md-footer { + position: fixed; + bottom: 0px; + } + .md-footer__title { + background-color: unset; + } + .md-footer-nav__link { + width: 16rem; + } + + .md-dialog { + background-color: unset; + } + + @media screen and (max-width: 76.1875em) { + .md-nav { + transition: none !important; + background-color: var(--md-default-bg-color) + } + .md-nav--primary .md-nav__title { + cursor: auto; + color: var(--md-default-fg-color); + font-weight: 700; + white-space: normal; + line-height: 1rem; + height: auto; + display: flex; + flex-flow: column; + row-gap: 1.6rem; + padding: 1.2rem .8rem .8rem; + background-color: var(--md-default-bg-color); + } + .md-nav--primary .md-nav__title~.md-nav__list { + box-shadow: none; + } + .md-nav--primary .md-nav__title ~ .md-nav__list > :first-child { + border-top: none; + } + .md-nav--primary .md-nav__title .md-nav__button { + display: none; + } + .md-nav--primary .md-nav__title .md-nav__icon { + color: var(--md-default-fg-color); + position: static; + height: auto; + margin: 0 0 0 -0.2rem; + } + .md-nav--primary > .md-nav__title [for="none"] { + padding-top: 0; + } + .md-nav--primary .md-nav__item { + border-top: none; + } + .md-nav--primary :is(.md-nav__title,.md-nav__item) { + font-size : var(--md-typeset-font-size); + } + .md-nav .md-source { + display: none; + } + + .md-sidebar { + height: 100%; + } + .md-sidebar--primary { + width: 12.1rem !important; + z-index: 200; + left: ${ + isPinned ? 'calc(-12.1rem + 242px)' : 'calc(-12.1rem + 72px)' + } !important; + } + .md-sidebar--secondary:not([hidden]) { + display: none; + } + + .md-content { + max-width: 100%; + margin-left: 0; + } + + .md-header__button { + margin: 0.4rem 0; + margin-left: 0.4rem; + padding: 0; + } + + .md-overlay { + left: 0; + } + + .md-footer { + position: static; + padding-left: 0; + } + .md-footer-nav__link { + /* footer links begin to overlap at small sizes without setting width */ + width: 50%; + } + } + + @media screen and (max-width: 600px) { + .md-sidebar--primary { + left: -12.1rem !important; + width: 12.1rem; + } + } + `, + }), + injectCss({ + // Typeset + css: ` + .md-typeset { + font-size: var(--md-typeset-font-size); + } + + ${headings.reduce((style, heading) => { + const styles = theme.typography[heading]; + const { lineHeight, fontFamily, fontWeight, fontSize } = styles; + const calculate = (value: typeof fontSize) => { + let factor: number | string = 1; + if (typeof value === 'number') { + // 60% of the size defined because it is too big + factor = (value / 16) * 0.6; + } + if (typeof value === 'string') { + factor = value.replace('rem', ''); + } + return `calc(${factor} * var(--md-typeset-font-size))`; + }; + return style.concat(` + .md-typeset ${heading} { + color: var(--md-default-fg-color); + line-height: ${lineHeight}; + font-family: ${fontFamily}; + font-weight: ${fontWeight}; + font-size: ${calculate(fontSize)}; + } + `); + }, '')} + + .md-typeset .md-content__button { + color: var(--md-default-fg-color); + } + + .md-typeset hr { + border-bottom: 0.05rem dotted ${theme.palette.divider}; + } + + .md-typeset details { + font-size: var(--md-typeset-font-size) !important; + } + .md-typeset details summary { + padding-left: 2.5rem !important; + } + .md-typeset details summary:before, + .md-typeset details summary:after { + top: 50% !important; + width: 20px !important; + height: 20px !important; + transform: rotate(0deg) translateY(-50%) !important; + } + .md-typeset details[open] > summary:after { + transform: rotate(90deg) translateX(-50%) !important; + } + + .md-typeset blockquote { + color: var(--md-default-fg-color--light); + border-left: 0.2rem solid var(--md-default-fg-color--light); + } + + .md-typeset table:not([class]) { + font-size: var(--md-typeset-font-size); + border: 1px solid var(--md-default-fg-color); + border-bottom: none; + border-collapse: collapse; + } + .md-typeset table:not([class]) th { + font-weight: bold; + } + .md-typeset table:not([class]) td, .md-typeset table:not([class]) th { + border-bottom: 1px solid var(--md-default-fg-color); + } + .md-typeset pre > code::-webkit-scrollbar-thumb { background-color: hsla(0, 0%, 0%, 0.32); } .md-typeset pre > code::-webkit-scrollbar-thumb:hover { background-color: hsla(0, 0%, 0%, 0.87); } - `, + `, }), injectCss({ - // Admonitions and others are using SVG masks to define icons. These - // masks are defined as CSS variables. - // As the MkDocs output is rendered in shadow DOM, the CSS variable - // definitions on the root selector are not applied. Instead, the have - // to be applied on :host. - // As there is no way to transform the served main*.css yet (for - // example in the backend), we have to copy from main*.css and modify - // them. + // Animations css: ` - :host { - --md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--info: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--tip: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--success: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--question: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--warning: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--failure: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--danger: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--bug: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--example: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--quote: url('data:image/svg+xml;charset=utf-8,'); + /* + Disable CSS animations on link colors as they lead to issues in dark mode. + The dark mode color theme is applied later and theirfore there is always an animation from light to dark mode when navigation between pages. + */ + .md-dialog, .md-nav__link, .md-footer__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink { + transition: none; } - :host { - --md-footnotes-icon: url('data:image/svg+xml;charset=utf-8,'); + `, + }), + injectCss({ + // Extensions + css: ` + /* HIGHLIGHT */ + .highlight .md-clipboard:after { + content: unset; } - :host { - --md-details-icon: url('data:image/svg+xml;charset=utf-8,'); + + .highlight .nx { + color: ${isDarkTheme ? '#ff53a3' : '#ec407a'}; } - :host { - --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); + + /* CODE HILITE */ + .codehilite .gd { + background-color: ${ + isDarkTheme ? 'rgba(248,81,73,0.65)' : '#fdd' + }; } - `, + + .codehilite .gi { + background-color: ${ + isDarkTheme ? 'rgba(46,160,67,0.65)' : '#dfd' + }; + } + + /* TABBED */ + .tabbed-set>input:nth-child(1):checked~.tabbed-labels>:nth-child(1), + .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2), + .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3), + .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4), + .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5), + .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6), + .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7), + .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8), + .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9), + .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10), + .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11), + .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12), + .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13), + .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14), + .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15), + .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16), + .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17), + .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18), + .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19), + .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20) { + color: var(--md-accent-fg-color); + border-color: var(--md-accent-fg-color); + } + + /* TASK-LIST */ + .task-list-control .task-list-indicator::before { + background-color: ${theme.palette.action.disabledBackground}; + } + .task-list-control [type="checkbox"]:checked + .task-list-indicator:before { + background-color: ${theme.palette.success.main}; + } + + /* ADMONITION */ + .admonition { + font-size: var(--md-typeset-font-size) !important; + } + .admonition .admonition-title { + padding-left: 2.5rem !important; + } + + .admonition .admonition-title:before { + top: 50% !important; + width: 20px !important; + height: 20px !important; + transform: translateY(-50%) !important; + } + `, }), ]), [ - techdocsSanitizer, - techdocsStorageApi, kind, name, namespace, scmIntegrationsApi, - theme.typography.fontFamily, - theme.palette.text.primary, - theme.palette.primary.main, - theme.palette.background.paper, - theme.palette.background.default, - theme.palette.textVerySubtle, - theme.palette.textSubtle, - theme.palette.action.disabledBackground, - theme.palette.success.main, + techdocsSanitizer, + techdocsStorageApi, + theme, isDarkTheme, isPinned, ], @@ -490,7 +845,7 @@ const TheReader = ({ entityRef, onReady = () => {}, withSearch = true, -}: Props) => { +}: ReaderProps) => { const classes = useStyles(); const dom = useTechDocsReaderDom(entityRef); const shadowDomRef = useRef(null); @@ -527,16 +882,20 @@ const TheReader = ({ ); }; -export const Reader = ({ - entityRef, - onReady = () => {}, - withSearch = true, -}: Props) => ( - - - -); +/** + * Component responsible for rendering TechDocs documentation + * + * @public + */ +export const Reader = (props: ReaderProps) => { + const { entityRef, onReady = () => {}, withSearch = true } = props; + return ( + + + + ); +}; diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage.test.tsx similarity index 94% rename from plugins/techdocs/src/reader/components/TechDocsPage.test.tsx rename to plugins/techdocs/src/reader/components/TechDocsReaderPage.test.tsx index b297466a62..55637806a3 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage.test.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ import React from 'react'; -import { TechDocsPage } from './TechDocsPage'; +import { TechDocsReaderPage } from './TechDocsReaderPage'; import { render, act } from '@testing-library/react'; import { ConfigReader } from '@backstage/config'; import { @@ -40,10 +40,10 @@ jest.mock('react-router-dom', () => { }; }); -jest.mock('./TechDocsPageHeader', () => { +jest.mock('./TechDocsReaderPageHeader', () => { return { __esModule: true, - TechDocsPageHeader: () =>
, + TechDocsReaderPageHeader: () =>
, }; }); @@ -51,7 +51,7 @@ const { useParams }: { useParams: jest.Mock } = jest.requireMock('react-router-dom'); global.scroll = jest.fn(); -describe('', () => { +describe('', () => { it('should render techdocs page', async () => { useParams.mockReturnValue({ entityRef: 'Component::backstage', @@ -101,7 +101,7 @@ describe('', () => { const rendered = render( wrapInTestApp( - + , ), ); @@ -158,7 +158,7 @@ describe('', () => { const rendered = render( wrapInTestApp( - + {({ techdocsMetadataValue }) => (
', () => { subtitle={techdocsMetadataValue?.site_name} /> )} - + , ), ); diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx similarity index 72% rename from plugins/techdocs/src/reader/components/TechDocsPage.tsx rename to plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx index aae88d406e..d03ea2ef61 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPage.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPage.tsx @@ -19,14 +19,18 @@ import { useOutlet } from 'react-router'; import { useParams } from 'react-router-dom'; import useAsync from 'react-use/lib/useAsync'; import { techdocsApiRef } from '../../api'; -import { TechDocsNotFound } from './TechDocsNotFound'; import { LegacyTechDocsPage } from './LegacyTechDocsPage'; import { TechDocsEntityMetadata, TechDocsMetadata } from '../../types'; import { EntityName } from '@backstage/catalog-model'; -import { useApi } from '@backstage/core-plugin-api'; +import { useApi, useApp } from '@backstage/core-plugin-api'; import { Page } from '@backstage/core-components'; -export type TechDocsPageRenderFunction = ({ +/** + * Helper function that gives the children of {@link TechDocsReaderPage} acccess to techdocs and entity metadata + * + * @public + */ +export type TechDocsReaderPageRenderFunction = ({ techdocsMetadataValue, entityMetadataValue, entityRef, @@ -37,11 +41,18 @@ export type TechDocsPageRenderFunction = ({ onReady: () => void; }) => JSX.Element; -export type TechDocsPageProps = { - children?: TechDocsPageRenderFunction | React.ReactNode; +/** + * Props for {@link TechDocsReaderPage} + * + * @public + */ +export type TechDocsReaderPageProps = { + children?: TechDocsReaderPageRenderFunction | React.ReactNode; }; -export const TechDocsPage = ({ children }: TechDocsPageProps) => { +export const TechDocsReaderPage = (props: TechDocsReaderPageProps) => { + const { children } = props; + const { NotFoundErrorPage } = useApp().getComponents(); const outlet = useOutlet(); const [documentReady, setDocumentReady] = useState(false); @@ -66,9 +77,7 @@ export const TechDocsPage = ({ children }: TechDocsPageProps) => { setDocumentReady(true); }, [setDocumentReady]); - if (entityMetadataError) { - return ; - } + if (entityMetadataError) return ; if (!children) return outlet || ; @@ -85,3 +94,16 @@ export const TechDocsPage = ({ children }: TechDocsPageProps) => { ); }; + +/** + * @public + * @deprecated use {@link TechDocsReaderPage} instead + */ +export const TechDocsPage = TechDocsReaderPage; + +/** + * @public + * @deprecated use {@link TechDocsReaderPageRenderFunction} instead + */ + +export type TechDocsPageRenderFunction = TechDocsReaderPageRenderFunction; diff --git a/plugins/techdocs/src/reader/components/TechDocsPageHeader.test.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader.test.tsx similarity index 93% rename from plugins/techdocs/src/reader/components/TechDocsPageHeader.test.tsx rename to plugins/techdocs/src/reader/components/TechDocsReaderPageHeader.test.tsx index 4fa43bfed5..9245bb66c5 100644 --- a/plugins/techdocs/src/reader/components/TechDocsPageHeader.test.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageHeader.test.tsx @@ -14,17 +14,17 @@ * limitations under the License. */ import React from 'react'; -import { TechDocsPageHeader } from './TechDocsPageHeader'; +import { TechDocsReaderPageHeader } from './TechDocsReaderPageHeader'; import { act } from '@testing-library/react'; import { renderInTestApp } from '@backstage/test-utils'; import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { rootRouteRef } from '../../routes'; -describe('', () => { +describe('', () => { it('should render a techdocs page header', async () => { await act(async () => { const rendered = await renderInTestApp( - ', () => { it('should render a techdocs page header even if metadata is missing', async () => { await act(async () => { const rendered = await renderInTestApp( - ', () => { it('should render a link back to the component page', async () => { await act(async () => { const rendered = await renderInTestApp( - ; -export const TechDocsPageHeader = ({ - entityRef, - entityMetadata, - techDocsMetadata, -}: TechDocsPageHeaderProps) => { +/** + * Component responsible for rendering a Header with metadata on TechDocs reader page. + * + * @public + */ +export const TechDocsReaderPageHeader = ( + props: TechDocsReaderPageHeaderProps, +) => { + const { entityRef, entityMetadata, techDocsMetadata, children } = props; const { name } = entityRef; const { site_name: siteName, site_description: siteDescription } = @@ -107,6 +118,20 @@ export const TechDocsPageHeader = ({ typeLink={docsRootLink} > {labels} + {children}
); }; + +/** + * @public + * @deprecated use {@link TechDocsReaderPageHeader} instead + */ +export const TechDocsPageHeader = TechDocsReaderPageHeader; + +/** + * @public + * @deprecated use {@link TechDocsReaderPageHeader} instead + */ + +export type TechDocsPageHeaderProps = TechDocsReaderPageHeaderProps; diff --git a/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx b/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx index e70c17d14a..417dd5727c 100644 --- a/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsStateIndicator.tsx @@ -23,7 +23,10 @@ import { TechDocsBuildLogs } from './TechDocsBuildLogs'; import { TechDocsNotFound } from './TechDocsNotFound'; import { useTechDocsReader } from './Reader'; -const useStyles = makeStyles(() => ({ +const useStyles = makeStyles(theme => ({ + root: { + marginBottom: theme.spacing(2), + }, message: { // `word-break: break-word` is deprecated, but gives legacy support to browsers not supporting `overflow-wrap` yet // https://developer.mozilla.org/en-US/docs/Web/CSS/word-break @@ -58,6 +61,7 @@ export const TechDocsStateIndicator = () => { if (state === 'INITIAL_BUILD') { StateAlert = ( } @@ -76,6 +80,7 @@ export const TechDocsStateIndicator = () => { severity="info" icon={} action={} + classes={{ root: classes.root }} > A newer version of this documentation is being prepared and will be available shortly. @@ -93,6 +98,7 @@ export const TechDocsStateIndicator = () => { Refresh } + classes={{ root: classes.root }} > A newer version of this documentation is now available, please refresh to view. @@ -106,7 +112,7 @@ export const TechDocsStateIndicator = () => { variant="outlined" severity="error" action={} - classes={{ message: classes.message }} + classes={{ root: classes.root, message: classes.message }} > Building a newer version of this documentation failed.{' '} {syncErrorMessage} @@ -122,7 +128,7 @@ export const TechDocsStateIndicator = () => { variant="outlined" severity="error" action={} - classes={{ message: classes.message }} + classes={{ root: classes.root, message: classes.message }} > Building a newer version of this documentation failed.{' '} {syncErrorMessage} diff --git a/plugins/techdocs/src/reader/components/index.ts b/plugins/techdocs/src/reader/components/index.ts index 99a0a7a3bc..3124a92f93 100644 --- a/plugins/techdocs/src/reader/components/index.ts +++ b/plugins/techdocs/src/reader/components/index.ts @@ -15,8 +15,13 @@ */ export * from './Reader'; -export * from './TechDocsPage'; -export * from './TechDocsPageHeader'; +export type { + TechDocsReaderPageProps, + TechDocsPageRenderFunction, + TechDocsReaderPageRenderFunction, +} from './TechDocsReaderPage'; +export { TechDocsPage } from './TechDocsReaderPage'; +export * from './TechDocsReaderPageHeader'; export * from './TechDocsStateIndicator'; /** @@ -31,4 +36,3 @@ export * from './TechDocsStateIndicator'; * todo: Make public or stop exporting (ctrl+f "altReaderExperiments") * @internal */ -export { TechDocsSearch } from './TechDocsSearch'; diff --git a/plugins/techdocs/src/reader/index.tsx b/plugins/techdocs/src/reader/index.ts similarity index 100% rename from plugins/techdocs/src/reader/index.tsx rename to plugins/techdocs/src/reader/index.ts diff --git a/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts b/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts new file mode 100644 index 0000000000..9d9db15e45 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/addSidebarToggle.ts @@ -0,0 +1,44 @@ +/* + * 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 type { Transformer } from './index'; +import MenuIcon from '@material-ui/icons/Menu'; +import React from 'react'; +import ReactDOM from 'react-dom'; + +export const addSidebarToggle = (): Transformer => { + return dom => { + // attempting to use selectors that are more likely to be static as MkDocs updates over time + const mkdocsToggleSidebar = dom.querySelector( + '.md-header label[for="__drawer"]', + ) as HTMLLabelElement; + const article = dom.querySelector('article') as HTMLElement; + + // Fail gracefully + if (!mkdocsToggleSidebar || !article) { + return dom; + } + + const toggleSidebar = mkdocsToggleSidebar.cloneNode() as HTMLLabelElement; + ReactDOM.render(React.createElement(MenuIcon), toggleSidebar); + toggleSidebar.style.paddingLeft = '5px'; + toggleSidebar.classList.add('md-content__button'); + toggleSidebar.title = 'Toggle Sidebar'; + toggleSidebar.id = 'toggle-sidebar'; + article?.prepend(toggleSidebar); + return dom; + }; +}; diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index aa0fba2a18..dcd79180a7 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -16,6 +16,7 @@ export * from './addBaseUrl'; export * from './addGitFeedbackLink'; +export * from './addSidebarToggle'; export * from './rewriteDocLinks'; export * from './addLinkClickListener'; export * from './copyToClipboard'; diff --git a/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.test.ts b/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.test.ts index 020befd522..fb4044f730 100644 --- a/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.test.ts +++ b/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.test.ts @@ -27,10 +27,22 @@ describe('simplifyMkdocsFooter', () => { }, ); - expect(shadowDom.querySelector('.md-footer-copyright')).toBeTruthy(); + expect(shadowDom.querySelector('.md-footer .md-copyright')).toBeTruthy(); }); - it('does remove mkdocs copyright', async () => { + it('does remove new mkdocs copyright', async () => { + const shadowDom = await createTestShadowDom( + FIXTURES.FIXTURE_STANDARD_PAGE, + { + preTransformers: [simplifyMkdocsFooter()], + postTransformers: [], + }, + ); + + expect(shadowDom.querySelector('.md-footer .md-copyright')).toBeFalsy(); + }); + + it('does remove old mkdocs copyright', async () => { const shadowDom = await createTestShadowDom( FIXTURES.FIXTURE_STANDARD_PAGE, { diff --git a/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.ts b/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.ts index 2b96926270..505c88dc71 100644 --- a/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.ts +++ b/plugins/techdocs/src/reader/transformers/simplifyMkdocsFooter.ts @@ -18,9 +18,10 @@ import type { Transformer } from './transformer'; export const simplifyMkdocsFooter = (): Transformer => { return dom => { - // Remove mkdocs copyright + // Remove new mkdocs copyright + dom.querySelector('.md-footer .md-copyright')?.remove(); + // Remove old mkdocs copyright dom.querySelector('.md-footer-copyright')?.remove(); - return dom; }; }; diff --git a/plugins/techdocs/src/reader/components/TechDocsSearch.test.tsx b/plugins/techdocs/src/search/components/TechDocsSearch.test.tsx similarity index 66% rename from plugins/techdocs/src/reader/components/TechDocsSearch.test.tsx rename to plugins/techdocs/src/search/components/TechDocsSearch.test.tsx index 5b3d714a04..06d5ac6aac 100644 --- a/plugins/techdocs/src/reader/components/TechDocsSearch.test.tsx +++ b/plugins/techdocs/src/search/components/TechDocsSearch.test.tsx @@ -23,7 +23,7 @@ import { waitFor, within, } from '@testing-library/react'; -import React from 'react'; +import React, { useState } from 'react'; import { TechDocsSearch } from './TechDocsSearch'; const entityId = { @@ -48,7 +48,7 @@ const singleResult = Promise.resolve({ ], }); -describe('', () => { +describe('', () => { it('should render techdocs search bar', async () => { const query = () => emptyResults; const querySpy = jest.fn(query); @@ -70,6 +70,7 @@ describe('', () => { expect(rendered.getByTestId('techdocs-search-bar')).toBeInTheDocument(); }); }); + it('should trigger query when autocomplete input changed', async () => { const query = () => singleResult; const querySpy = jest.fn(query); @@ -119,4 +120,61 @@ describe('', () => { ); }); }); + + it('should update filter values when a new entityName is provided', async () => { + const query = () => singleResult; + const querySpy = jest.fn(query); + const searchApi = { query: querySpy }; + const apiRegistry = TestApiRegistry.from([searchApiRef, searchApi]); + const newEntityId = { + name: 'test-diff', + namespace: 'testspace-diff', + kind: 'TestableDiff', + }; + + const WrappedSearchBar = () => { + const [entityName, setEntityName] = useState(entityId); + return wrapInTestApp( + + + + , + ); + }; + + await act(async () => { + const rendered = render(); + + await singleResult; + expect(querySpy).toBeCalledWith({ + filters: { + kind: 'Testable', + name: 'test', + namespace: 'testspace', + }, + pageCursor: '', + term: '', + types: ['techdocs'], + }); + + const button = rendered.getByText('Update Entity'); + button.click(); + + await singleResult; + await waitFor(() => + expect(querySpy).toBeCalledWith({ + filters: { + kind: 'TestableDiff', + name: 'test-diff', + namespace: 'testspace-diff', + }, + pageCursor: '', + term: '', + types: ['techdocs'], + }), + ); + }); + }); }); diff --git a/plugins/techdocs/src/reader/components/TechDocsSearch.tsx b/plugins/techdocs/src/search/components/TechDocsSearch.tsx similarity index 52% rename from plugins/techdocs/src/reader/components/TechDocsSearch.tsx rename to plugins/techdocs/src/search/components/TechDocsSearch.tsx index 0760eb47f8..c817fb3217 100644 --- a/plugins/techdocs/src/reader/components/TechDocsSearch.tsx +++ b/plugins/techdocs/src/search/components/TechDocsSearch.tsx @@ -17,8 +17,8 @@ import { EntityName } from '@backstage/catalog-model'; import { SearchContextProvider, useSearch } from '@backstage/plugin-search'; import { + makeStyles, CircularProgress, - Grid, IconButton, InputAdornment, TextField, @@ -28,9 +28,20 @@ import Autocomplete from '@material-ui/lab/Autocomplete'; import React, { ChangeEvent, useEffect, useState } from 'react'; import { useNavigate } from 'react-router'; import useDebounce from 'react-use/lib/useDebounce'; -import { DocsResultListItem } from '../../components/DocsResultListItem'; +import { TechDocsSearchResultListItem } from './TechDocsSearchResultListItem'; -type TechDocsSearchProps = { +const useStyles = makeStyles({ + root: { + width: '100%', + }, +}); + +/** + * Props for {@link TechDocsSearch} + * + * @public + */ +export type TechDocsSearchProps = { entityId: EntityName; debounceTime?: number; }; @@ -49,17 +60,17 @@ type TechDocsSearchResult = { document: TechDocsDoc; }; -const TechDocsSearchBar = ({ - entityId, - debounceTime = 150, -}: TechDocsSearchProps) => { +const TechDocsSearchBar = (props: TechDocsSearchProps) => { + const { entityId, debounceTime = 150 } = props; const [open, setOpen] = useState(false); const navigate = useNavigate(); const { term, setTerm, + setFilters, result: { loading, value: searchVal }, } = useSearch(); + const classes = useStyles(); const [options, setOptions] = useState([]); useEffect(() => { let mounted = true; @@ -80,6 +91,20 @@ const TechDocsSearchBar = ({ useDebounce(() => setTerm(value), debounceTime, [value]); + // Update the filter context when the entityId changes, e.g. when the search + // bar continues to be rendered, navigating between different TechDocs sites. + const { kind, name, namespace } = entityId; + useEffect(() => { + setFilters(prevFilters => { + return { + ...prevFilters, + kind, + namespace, + name, + }; + }); + }, [kind, namespace, name, setFilters]); + const handleQuery = (e: ChangeEvent) => { if (!open) { setOpen(true); @@ -95,70 +120,74 @@ const TechDocsSearchBar = ({ }; return ( - - ''} - filterOptions={x => { - return x; // This is needed to get renderOption to be called after options change. Bug in material-ui? - }} - onClose={() => { - setOpen(false); - }} - onFocus={() => { - setOpen(true); - }} - onChange={handleSelection} - blurOnSelect - noOptionsText="No results found" - value={null} - options={options} - renderOption={({ document }) => ( - - )} - loading={loading} - renderInput={params => ( - - - - - - ), - endAdornment: ( - - {loading ? ( - - ) : null} - {params.InputProps.endAdornment} - - ), - }} - /> - )} - /> - + ''} + filterOptions={x => { + return x; // This is needed to get renderOption to be called after options change. Bug in material-ui? + }} + onClose={() => { + setOpen(false); + }} + onFocus={() => { + setOpen(true); + }} + onChange={handleSelection} + blurOnSelect + noOptionsText="No results found" + value={null} + options={options} + renderOption={({ document }) => ( + + )} + loading={loading} + renderInput={params => ( + + + + + + ), + endAdornment: ( + + {loading ? ( + + ) : null} + {params.InputProps.endAdornment} + + ), + }} + /> + )} + /> ); }; +/** + * Component used to render search bar on TechDocs page, scoped to + * + * @public + */ export const TechDocsSearch = (props: TechDocsSearchProps) => { const initialState = { term: '', diff --git a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx similarity index 84% rename from plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx rename to plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx index 807914c5ad..7ebdd6c340 100644 --- a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.test.tsx +++ b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.test.tsx @@ -16,7 +16,7 @@ import React from 'react'; import { render } from '@testing-library/react'; -import { DocsResultListItem } from './DocsResultListItem'; +import { TechDocsSearchResultListItem } from './TechDocsSearchResultListItem'; // Using canvas to render text.. jest.mock('react-text-truncate', () => { @@ -44,9 +44,11 @@ const validResultWithTitle = { lifecycle: 'production', }; -describe('DocsResultListItem test', () => { +describe('TechDocsSearchResultListItem test', () => { it('should render search doc passed in', async () => { - const { findByText } = render(); + const { findByText } = render( + , + ); expect( await findByText('Documentation | Backstage docs'), @@ -60,7 +62,10 @@ describe('DocsResultListItem test', () => { it('should use title if defined', async () => { const { findByText } = render( - , + , ); expect(await findByText('Count Dookumentation')).toBeInTheDocument(); @@ -73,7 +78,7 @@ describe('DocsResultListItem test', () => { it('should use entity title if defined', async () => { const { findByText } = render( - , + , ); expect( diff --git a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.tsx similarity index 78% rename from plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx rename to plugins/techdocs/src/search/components/TechDocsSearchResultListItem.tsx index 03cf011c32..152fa2e3d3 100644 --- a/plugins/techdocs/src/components/DocsResultListItem/DocsResultListItem.tsx +++ b/plugins/techdocs/src/search/components/TechDocsSearchResultListItem.tsx @@ -29,19 +29,34 @@ const useStyles = makeStyles({ }, }); -export const DocsResultListItem = ({ - result, - lineClamp = 5, - asListItem = true, - asLink = true, - title, -}: { +/** + * Props for {@link TechDocsSearchResultListItem}. + * + * @public + */ +export type TechDocsSearchResultListItemProps = { result: any; lineClamp?: number; asListItem?: boolean; asLink?: boolean; title?: string; -}) => { +}; + +/** + * Component which renders documentation and related metadata. + * + * @public + */ +export const TechDocsSearchResultListItem = ( + props: TechDocsSearchResultListItemProps, +) => { + const { + result, + lineClamp = 5, + asListItem = true, + asLink = true, + title, + } = props; const classes = useStyles(); const TextItem = () => ( ); }; + +/** + * @public + * @deprecated use {@link TechDocsSearchResultListItem} instead + */ +export const DocsResultListItem = TechDocsSearchResultListItem; diff --git a/plugins/techdocs/src/components/DocsResultListItem/index.ts b/plugins/techdocs/src/search/components/index.ts similarity index 88% rename from plugins/techdocs/src/components/DocsResultListItem/index.ts rename to plugins/techdocs/src/search/components/index.ts index 4e1e511ea1..1f7615b4ff 100644 --- a/plugins/techdocs/src/components/DocsResultListItem/index.ts +++ b/plugins/techdocs/src/search/components/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ -export { DocsResultListItem } from './DocsResultListItem'; +export * from './TechDocsSearchResultListItem'; +export * from './TechDocsSearch'; diff --git a/plugins/techdocs/src/search/index.ts b/plugins/techdocs/src/search/index.ts new file mode 100644 index 0000000000..6f9ea2a52d --- /dev/null +++ b/plugins/techdocs/src/search/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './components'; diff --git a/plugins/techdocs/src/test-utils/fixtures/mkdocs-index.ts b/plugins/techdocs/src/test-utils/fixtures/mkdocs-index.ts index afb84dc10b..21d703fa2e 100644 --- a/plugins/techdocs/src/test-utils/fixtures/mkdocs-index.ts +++ b/plugins/techdocs/src/test-utils/fixtures/mkdocs-index.ts @@ -1520,7 +1520,7 @@ css img js mkdocs sitemap.xml