diff --git a/.changeset/bright-plants-travel.md b/.changeset/bright-plants-travel.md
new file mode 100644
index 0000000000..ad8a77d17b
--- /dev/null
+++ b/.changeset/bright-plants-travel.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-kubernetes': patch
+---
+
+The Kubernetes plugin will now re-fetch the kubernetes objects every ten seconds (not current configurable), this allows users to track the progress of deployments without refreshing the browser.
diff --git a/.changeset/cold-wolves-eat.md b/.changeset/cold-wolves-eat.md
new file mode 100644
index 0000000000..fa3a2180b2
--- /dev/null
+++ b/.changeset/cold-wolves-eat.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-sentry': patch
+---
+
+feature: sentry-plugin allows passing search query for listing issues
diff --git a/.changeset/dirty-bugs-care.md b/.changeset/dirty-bugs-care.md
new file mode 100644
index 0000000000..4d90a9c939
--- /dev/null
+++ b/.changeset/dirty-bugs-care.md
@@ -0,0 +1,9 @@
+---
+'@backstage/test-utils': patch
+'@backstage/test-utils-core': patch
+---
+
+Migrates all utility methods from `test-utils-core` into `test-utils` and delete exports from the old package.
+This should have no impact since this package is considered internal and have no usages outside core packages.
+
+Notable changes are that the testing tool `msw.setupDefaultHandlers()` have been deprecated in favour of `setupRequestMockHandlers()`.
diff --git a/.changeset/dull-news-visit.md b/.changeset/dull-news-visit.md
new file mode 100644
index 0000000000..e1fab41ebf
--- /dev/null
+++ b/.changeset/dull-news-visit.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-techdocs': patch
+---
+
+Reader will now scroll to the top of the page when navigating between pages
diff --git a/.changeset/friendly-olives-drop.md b/.changeset/friendly-olives-drop.md
new file mode 100644
index 0000000000..371cf0e4db
--- /dev/null
+++ b/.changeset/friendly-olives-drop.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder': patch
+---
+
+Surfacing all components of the ScaffolderPage outside of the plugin so you can customize the page
diff --git a/.changeset/green-parrots-thank.md b/.changeset/green-parrots-thank.md
new file mode 100644
index 0000000000..9296a5119a
--- /dev/null
+++ b/.changeset/green-parrots-thank.md
@@ -0,0 +1,10 @@
+---
+'@backstage/plugin-azure-devops': patch
+---
+
+Azure DevOps frontend refactoring items from issue #7641
+
+- Remove backend setup documentation and linked to the Azure DevOps backend plugin for these instructions
+- Improved documentation to be easier to expand with new features in the future
+- Removed Router based on feedback from maintainers
+- Added tests for `getBuildResultComponent` and `getBuildStateComponent` from the BuildTable
diff --git a/.changeset/kind-islands-attack.md b/.changeset/kind-islands-attack.md
new file mode 100644
index 0000000000..a549839bc7
--- /dev/null
+++ b/.changeset/kind-islands-attack.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-azure-devops-backend': patch
+---
+
+Added duration (startTime and finishTime) and identity (uniqueName) to the RepoBuild results. Also did a bit of refactoring to help finish up the backend items in issue #7641
diff --git a/.changeset/popular-apples-sparkle.md b/.changeset/popular-apples-sparkle.md
new file mode 100644
index 0000000000..b8437795da
--- /dev/null
+++ b/.changeset/popular-apples-sparkle.md
@@ -0,0 +1,26 @@
+---
+'@backstage/backend-common': patch
+'@backstage/catalog-model': patch
+'@backstage/cli': patch
+'@backstage/config-loader': patch
+'@backstage/core-app-api': patch
+'@backstage/errors': patch
+'@backstage/search-common': patch
+'@backstage/test-utils': patch
+'@backstage/plugin-app-backend': patch
+'@backstage/plugin-catalog-backend': patch
+'@backstage/plugin-catalog-backend-module-ldap': patch
+'@backstage/plugin-catalog-graphql': patch
+'@backstage/plugin-catalog-react': patch
+'@backstage/plugin-config-schema': patch
+'@backstage/plugin-scaffolder': patch
+'@backstage/plugin-scaffolder-backend': patch
+'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch
+'@backstage/plugin-scaffolder-backend-module-rails': patch
+'@backstage/plugin-scaffolder-backend-module-yeoman': patch
+'@backstage/plugin-scaffolder-common': patch
+'@backstage/plugin-search': patch
+'@backstage/plugin-shortcuts': patch
+---
+
+Switch to use the json and observable types from `@backstage/types`
diff --git a/.changeset/quick-walls-know.md b/.changeset/quick-walls-know.md
new file mode 100644
index 0000000000..14dd78364f
--- /dev/null
+++ b/.changeset/quick-walls-know.md
@@ -0,0 +1,5 @@
+---
+'@backstage/techdocs-common': patch
+---
+
+Support optional bucketRootPath configuration parameter in S3 and GCS publishers
diff --git a/.changeset/rude-starfishes-walk.md b/.changeset/rude-starfishes-walk.md
new file mode 100644
index 0000000000..87ec76fd0a
--- /dev/null
+++ b/.changeset/rude-starfishes-walk.md
@@ -0,0 +1,7 @@
+---
+'@backstage/config': patch
+'@backstage/core-app-api': patch
+'@backstage/core-plugin-api': patch
+---
+
+Replace usage of test-utils-core with test-utils
diff --git a/.changeset/tough-avocados-give.md b/.changeset/tough-avocados-give.md
new file mode 100644
index 0000000000..08b8a7ea72
--- /dev/null
+++ b/.changeset/tough-avocados-give.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-sentry': patch
+---
+
+fix: sentry-plugin correction for allowed period values
diff --git a/docs/assets/getting-started/portal.png b/docs/assets/getting-started/portal.png
new file mode 100644
index 0000000000..f16ad44757
Binary files /dev/null and b/docs/assets/getting-started/portal.png differ
diff --git a/docs/assets/getting-started/startup.png b/docs/assets/getting-started/startup.png
new file mode 100644
index 0000000000..626f7b1297
Binary files /dev/null and b/docs/assets/getting-started/startup.png differ
diff --git a/docs/assets/getting-started/wizard.png b/docs/assets/getting-started/wizard.png
new file mode 100644
index 0000000000..679878e252
Binary files /dev/null and b/docs/assets/getting-started/wizard.png differ
diff --git a/docs/features/techdocs/configuration.md b/docs/features/techdocs/configuration.md
index 46cbc2a8a2..fbe9f36540 100644
--- a/docs/features/techdocs/configuration.md
+++ b/docs/features/techdocs/configuration.md
@@ -63,6 +63,10 @@ techdocs:
# (Required) Cloud Storage Bucket Name
bucketName: 'techdocs-storage'
+ # (Optional) Location in storage bucket to save files
+ # If not set, the default location will be the root of the storage bucket
+ bucketRootPath: '/'
+
# (Optional) An API key is required to write to a storage bucket.
# If missing, GOOGLE_APPLICATION_CREDENTIALS environment variable will be used.
# https://cloud.google.com/docs/authentication/production
@@ -75,6 +79,10 @@ techdocs:
# (Required) AWS S3 Bucket Name
bucketName: 'techdocs-storage'
+ # (Optional) Location in storage bucket to save files
+ # If not set, the default location will be the root of the storage bucket
+ bucketRootPath: '/'
+
# (Optional) An API key is required to write to a storage bucket.
# If not set, environment variables or aws config file will be used to authenticate.
# https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-environment.html
diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md
index b01afac5c3..9632be71bf 100644
--- a/docs/getting-started/index.md
+++ b/docs/getting-started/index.md
@@ -4,49 +4,99 @@ title: Getting Started
description: Documentation on How to get started with Backstage
---
-There are two different ways to get started with Backstage:
+For most Backstage installations, installing the standalone app will bring you
+the best and most streamlined experience. In this guide you will:
-- **Recommended:** Create a standalone app
-- **Contributors:** Clone the Backstage repository
+- Deploy Backstage Standalone with npm packages
+- Run Backstage Standalone with a SQLite in-memory database and demo content
-Creating a standalone app makes it simpler to customize the application for your
-needs and stay up to date with the project. You will depend on `@backstage`
-packages from npm, making your app much smaller. This is the recommended
-approach for most installations.
+This guide assumes a basic understanding of working on a Linux based operating
+system using tools like apt-get, npm, yarn, curl. Docker knowledge is also
+helpful for making the best use of your Backstage installation.
-If you want to contribute plugins or to the project in general, it's easier to
-fork and clone the repository. The `@backstage` packages will be included in the
-clone. That will let you stay up to date with the latest changes, and give you
-an easier path to make Pull Requests.
+If you are planning to contribute plugins or the project in general, we advise
+you to use the
+[Getting Started for Contributors](https://backstage.io/docs/getting-started/running-backstage-locally)
+guide to do a repository-based installation.
+
+### Prerequisites
+
+- Access to a Linux-based operating system, such as Linux, MacOS or
+ [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/)
+- An account with elevated rights
+- `curl` or `wget` installed
+- Node.js Active LTS Release installed (currently v14) using one of these
+ methods:
+ - Using `nvm` (recommended)
+ - [Installing nvm](https://github.com/nvm-sh/nvm#install--update-script)
+ - [Install and change Node version with nvm](https://nodejs.org/en/download/package-manager/#nvm)
+ - [Binary Download](https://nodejs.org/en/download/)
+ - [Package manager](https://nodejs.org/en/download/package-manager/)
+ - [Using NodeSource packages](https://github.com/nodesource/distributions/blob/master/README.md)
+- `yarn` [Installation](https://classic.yarnpkg.com/en/docs/install)
+- `docker` [installation](https://docs.docker.com/engine/install/)
+- `git` [installation](https://github.com/git-guides/install-git)
+- If the system is not directly accessible over your network, the following
+ ports need to be opened: 3000, 7000
### Create your Backstage App
-Backstage provides the `@backstage/create-app` package to scaffold standalone
-instances of Backstage. You will need to have
-[Node.js](https://nodejs.org/en/download/) Active LTS Release installed
-(currently v14) and [Yarn](https://classic.yarnpkg.com/en/docs/install). You
-will also need to have [Docker](https://docs.docker.com/engine/install/)
-installed to use some features like Software Templates and TechDocs.
-
-Using `npx` you can then run the following to create an app in a chosen
-subdirectory of your current working directory:
+To install the Backstage Standalone app, we make use of `npx`, a tool to run
+Node executables straight from the registry. Running the command below will
+install Backstage. The wizard will create a subdirectory inside your current
+working directory.
```bash
npx @backstage/create-app
```
-You will be taken through a wizard to create your app. You can read more about
-this process in [Create an app](./create-an-app.md).
+The wizard will ask you
-### Contributing to Backstage
+- The name of the app, which will also be the name of the directory
+- The database type to use for the backend. For this guide, you'll be using the
+ SQLite option.
-If you intend to make changes to the core project's packages, certain plugins,
-or project documentation, then you can fork and clone
-[https://github.com/backstage/backstage](https://github.com/backstage/backstage).
+
+
+
-This will let you run the latest code off of the `master` branch, fix bugs or
-contribute new features, run test suites, etc.
+### Run the Backstage app
-You can read more in our
-[CONTRIBUTING](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md)
-guide, which can help you get setup with a Backstage development environment.
+When the installation is complete you can go to the application directory and
+start the app. The `yarn dev` command will run both the frontend and backend as
+separate processes (named `[0]` and `[1]`) in the same window.
+
+```bash
+cd my-backstage-app
+yarn dev
+```
+
+
+
+
+
+It might take a little while, but as soon as the message
+`[0] webpack compiled successfully` appears, you can open a browser and directly
+navigate to your freshly installed Backstage portal at `http://localhost:3000`.
+You can start exploring the demo immediately.
+
+
+
+
+
+Congratulations! That should be it. Let us know how it went:
+[on discord](https://discord.gg/EBHEGzX), file issues for any
+[feature](https://github.com/backstage/backstage/issues/new?labels=help+wanted&template=feature_template.md)
+or
+[plugin suggestions](https://github.com/backstage/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME),
+or
+[bugs](https://github.com/backstage/backstage/issues/new?labels=bug&template=bug_template.md)
+you have, and feel free to
+[contribute](https://github.com/backstage/backstage/blob/master/CONTRIBUTING.md)!
+
+The most common next steps are to configure Backstage, add a plugin and moving
+to a more persistent database:
+
+- [Setting up Authentication](https://backstage.io/docs/auth/)
+- [Switching from SQLite to PostgresQL](https://backstage.io/docs/tutorials/switching-sqlite-postgres)
+- [Adding a plugin](https://backstage.io/docs/getting-started/configure-app-with-plugins)
diff --git a/packages/app/public/index.html b/packages/app/public/index.html
index da4d3cf3b8..d86f09f0b0 100644
--- a/packages/app/public/index.html
+++ b/packages/app/public/index.html
@@ -83,7 +83,7 @@
window,
document,
'script',
- 'https://www.datadoghq-browser-agent.com/datadog-rum.js',
+ 'https://www.datadoghq-browser-agent.com/datadog-rum-v3.js',
'DD_RUM',
);
DD_RUM.onReady(function () {
diff --git a/packages/backend-common/api-report.md b/packages/backend-common/api-report.md
index e2d68055bf..2336a57ec3 100644
--- a/packages/backend-common/api-report.md
+++ b/packages/backend-common/api-report.md
@@ -18,7 +18,7 @@ import { GithubCredentialsProvider } from '@backstage/integration';
import { GitHubIntegration } from '@backstage/integration';
import { GitLabIntegration } from '@backstage/integration';
import { isChildPath } from '@backstage/cli-common';
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { Knex } from 'knex';
import { Logger as Logger_2 } from 'winston';
import { MergeResult } from 'isomorphic-git';
diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json
index 8da4184f71..60824f0c40 100644
--- a/packages/backend-common/package.json
+++ b/packages/backend-common/package.json
@@ -34,6 +34,7 @@
"@backstage/config-loader": "^0.7.0",
"@backstage/errors": "^0.1.3",
"@backstage/integration": "^0.6.7",
+ "@backstage/types": "^0.1.1",
"@google-cloud/storage": "^5.8.0",
"@octokit/rest": "^18.5.3",
"@types/cors": "^2.8.6",
diff --git a/packages/backend-common/src/cache/CacheClient.ts b/packages/backend-common/src/cache/CacheClient.ts
index 79f48a2757..8e046f7f19 100644
--- a/packages/backend-common/src/cache/CacheClient.ts
+++ b/packages/backend-common/src/cache/CacheClient.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { createHash } from 'crypto';
import Keyv from 'keyv';
diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts
index 04cf1ac2bb..fa49d5f015 100644
--- a/packages/backend-common/src/config.ts
+++ b/packages/backend-common/src/config.ts
@@ -18,7 +18,8 @@ import { resolve as resolvePath } from 'path';
import parseArgs from 'minimist';
import { Logger } from 'winston';
import { findPaths } from '@backstage/cli-common';
-import { Config, ConfigReader, JsonValue } from '@backstage/config';
+import { Config, ConfigReader } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { loadConfig } from '@backstage/config-loader';
export class ObservableConfigProxy implements Config {
diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts
index de39279a4f..c0511c6b78 100644
--- a/packages/backend-common/src/database/DatabaseManager.ts
+++ b/packages/backend-common/src/database/DatabaseManager.ts
@@ -15,7 +15,8 @@
*/
import { Knex } from 'knex';
import { omit } from 'lodash';
-import { Config, ConfigReader, JsonObject } from '@backstage/config';
+import { Config, ConfigReader } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import {
createDatabaseClient,
ensureDatabaseExists,
diff --git a/packages/backend-common/src/database/connection.ts b/packages/backend-common/src/database/connection.ts
index 8ea46070eb..1c5cce3718 100644
--- a/packages/backend-common/src/database/connection.ts
+++ b/packages/backend-common/src/database/connection.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { Config, JsonObject } from '@backstage/config';
+import { Config } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { InputError } from '@backstage/errors';
import knexFactory, { Knex } from 'knex';
import { mergeDatabaseConfig } from './config';
diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts
index 6959edcc8b..301543e8c9 100644
--- a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts
+++ b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ConfigReader, JsonObject } from '@backstage/config';
+
+import { ConfigReader } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { getVoidLogger } from '../logging';
import { DefaultReadTreeResponseFactory } from './tree';
import { AwsS3UrlReader } from './AwsS3UrlReader';
diff --git a/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts
index d8aac4886d..31cf2956f8 100644
--- a/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts
+++ b/packages/backend-common/src/reading/GoogleGcsUrlReader.test.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { ConfigReader, JsonObject } from '@backstage/config';
+import { ConfigReader } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { getVoidLogger } from '../logging';
import { DefaultReadTreeResponseFactory } from './tree';
import { GoogleGcsUrlReader } from './GoogleGcsUrlReader';
diff --git a/packages/catalog-model/api-report.md b/packages/catalog-model/api-report.md
index c9429f9bdb..52f36493b5 100644
--- a/packages/catalog-model/api-report.md
+++ b/packages/catalog-model/api-report.md
@@ -3,9 +3,9 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { JSONSchema7 } from 'json-schema';
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { SerializedError } from '@backstage/errors';
import * as yup from 'yup';
diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json
index 69d1c34ba9..615e80b684 100644
--- a/packages/catalog-model/package.json
+++ b/packages/catalog-model/package.json
@@ -32,6 +32,7 @@
"dependencies": {
"@backstage/config": "^0.1.10",
"@backstage/errors": "^0.1.3",
+ "@backstage/types": "^0.1.1",
"@types/json-schema": "^7.0.5",
"@types/yup": "^0.29.13",
"ajv": "^7.0.3",
diff --git a/packages/catalog-model/src/entity/Entity.ts b/packages/catalog-model/src/entity/Entity.ts
index 7a4729c309..9d65d3e8f3 100644
--- a/packages/catalog-model/src/entity/Entity.ts
+++ b/packages/catalog-model/src/entity/Entity.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { EntityName } from '../types';
import { UNSTABLE_EntityStatus } from './EntityStatus';
diff --git a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts b/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts
index 1b1966d05d..f4c2dfb5bc 100644
--- a/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts
+++ b/packages/catalog-model/src/kinds/TemplateEntityV1beta2.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import type { Entity } from '../entity/Entity';
import schema from '../schema/kinds/Template.v1beta2.schema.json';
import { ajvCompiledJsonSchemaValidator } from './util';
diff --git a/packages/catalog-model/src/types.ts b/packages/catalog-model/src/types.ts
index 8ec1303a40..1a0fab267c 100644
--- a/packages/catalog-model/src/types.ts
+++ b/packages/catalog-model/src/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { JSONSchema7 } from 'json-schema';
/**
diff --git a/packages/cli/package.json b/packages/cli/package.json
index da9e161fef..62a008087e 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -32,6 +32,7 @@
"@backstage/config": "^0.1.10",
"@backstage/config-loader": "^0.7.0",
"@backstage/errors": "^0.1.3",
+ "@backstage/types": "^0.1.1",
"@hot-loader/react-dom": "^16.13.0",
"@lerna/package-graph": "^4.0.0",
"@lerna/project": "^4.0.0",
diff --git a/packages/cli/src/commands/config/docs.ts b/packages/cli/src/commands/config/docs.ts
index 198244b941..7a807f7c78 100644
--- a/packages/cli/src/commands/config/docs.ts
+++ b/packages/cli/src/commands/config/docs.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { mergeConfigSchemas } from '@backstage/config-loader';
import { Command } from 'commander';
import { JSONSchema7 as JSONSchema } from 'json-schema';
diff --git a/packages/cli/src/commands/config/schema.ts b/packages/cli/src/commands/config/schema.ts
index a36fc24f06..be228e1e7c 100644
--- a/packages/cli/src/commands/config/schema.ts
+++ b/packages/cli/src/commands/config/schema.ts
@@ -18,7 +18,7 @@ import { Command } from 'commander';
import { JSONSchema7 as JSONSchema } from 'json-schema';
import { stringify as stringifyYaml } from 'yaml';
import { loadCliConfig } from '../../lib/config';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { mergeConfigSchemas } from '@backstage/config-loader';
export default async (cmd: Command) => {
diff --git a/packages/cli/src/commands/install/types.ts b/packages/cli/src/commands/install/types.ts
index 5479032de5..3105f78822 100644
--- a/packages/cli/src/commands/install/types.ts
+++ b/packages/cli/src/commands/install/types.ts
@@ -15,7 +15,7 @@
*/
import { YarnInfoInspectData } from '../../lib/versioning';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
/**
* TODO: possible types
diff --git a/packages/config-loader/api-report.md b/packages/config-loader/api-report.md
index b47119d470..19b5ee9e27 100644
--- a/packages/config-loader/api-report.md
+++ b/packages/config-loader/api-report.md
@@ -4,7 +4,7 @@
```ts
import { AppConfig } from '@backstage/config';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { JSONSchema7 } from 'json-schema';
// @public
diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json
index 03ea61abe2..a731225e2b 100644
--- a/packages/config-loader/package.json
+++ b/packages/config-loader/package.json
@@ -33,6 +33,7 @@
"@backstage/cli-common": "^0.1.4",
"@backstage/config": "^0.1.9",
"@backstage/errors": "^0.1.3",
+ "@backstage/types": "^0.1.1",
"@types/json-schema": "^7.0.6",
"ajv": "^7.0.3",
"chokidar": "^3.5.2",
diff --git a/packages/config-loader/src/lib/env.ts b/packages/config-loader/src/lib/env.ts
index 57cfc077a7..638b49dbc8 100644
--- a/packages/config-loader/src/lib/env.ts
+++ b/packages/config-loader/src/lib/env.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { AppConfig, JsonObject } from '@backstage/config';
+import { AppConfig } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { assertError } from '@backstage/errors';
const ENV_PREFIX = 'APP_CONFIG_';
diff --git a/packages/config-loader/src/lib/schema/collect.ts b/packages/config-loader/src/lib/schema/collect.ts
index de6aff376f..8e7f582e44 100644
--- a/packages/config-loader/src/lib/schema/collect.ts
+++ b/packages/config-loader/src/lib/schema/collect.ts
@@ -23,7 +23,7 @@ import {
} from 'path';
import { ConfigSchemaPackageEntry } from './types';
import { getProgramFromFiles, generateSchema } from 'typescript-json-schema';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { assertError } from '@backstage/errors';
type Item = {
diff --git a/packages/config-loader/src/lib/schema/filtering.test.ts b/packages/config-loader/src/lib/schema/filtering.test.ts
index 3b9e548026..0395c7da6a 100644
--- a/packages/config-loader/src/lib/schema/filtering.test.ts
+++ b/packages/config-loader/src/lib/schema/filtering.test.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { ConfigVisibility } from './types';
import { filterByVisibility, filterErrorsByVisibility } from './filtering';
diff --git a/packages/config-loader/src/lib/schema/filtering.ts b/packages/config-loader/src/lib/schema/filtering.ts
index 2b13f7ac3f..b4226d7dad 100644
--- a/packages/config-loader/src/lib/schema/filtering.ts
+++ b/packages/config-loader/src/lib/schema/filtering.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue } from '@backstage/types';
import {
ConfigVisibility,
DEFAULT_CONFIG_VISIBILITY,
diff --git a/packages/config-loader/src/lib/schema/load.ts b/packages/config-loader/src/lib/schema/load.ts
index f3ea0b900e..2d78785304 100644
--- a/packages/config-loader/src/lib/schema/load.ts
+++ b/packages/config-loader/src/lib/schema/load.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { AppConfig, JsonObject } from '@backstage/config';
+import { AppConfig } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { compileConfigSchemas } from './compile';
import { collectConfigSchemas } from './collect';
import { filterByVisibility, filterErrorsByVisibility } from './filtering';
diff --git a/packages/config-loader/src/lib/schema/types.ts b/packages/config-loader/src/lib/schema/types.ts
index d1c07f4dac..53f4dec88e 100644
--- a/packages/config-loader/src/lib/schema/types.ts
+++ b/packages/config-loader/src/lib/schema/types.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { AppConfig, JsonObject } from '@backstage/config';
+import { AppConfig } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
/**
* An sub-set of configuration schema.
diff --git a/packages/config-loader/src/lib/transform/apply.ts b/packages/config-loader/src/lib/transform/apply.ts
index 091f9c23c5..80ddad90d0 100644
--- a/packages/config-loader/src/lib/transform/apply.ts
+++ b/packages/config-loader/src/lib/transform/apply.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue } from '@backstage/types';
import { assertError } from '@backstage/errors';
import { TransformFunc } from './types';
import { isObject } from './utils';
diff --git a/packages/config-loader/src/lib/transform/include.ts b/packages/config-loader/src/lib/transform/include.ts
index 5ea7161e5f..569b61c82c 100644
--- a/packages/config-loader/src/lib/transform/include.ts
+++ b/packages/config-loader/src/lib/transform/include.ts
@@ -16,7 +16,7 @@
import yaml from 'yaml';
import { extname, dirname, resolve as resolvePath } from 'path';
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue } from '@backstage/types';
import { isObject } from './utils';
import { TransformFunc, EnvFunc, ReadFileFunc } from './types';
diff --git a/packages/config-loader/src/lib/transform/substitution.ts b/packages/config-loader/src/lib/transform/substitution.ts
index 21edfb7e57..3c25784dc2 100644
--- a/packages/config-loader/src/lib/transform/substitution.ts
+++ b/packages/config-loader/src/lib/transform/substitution.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { TransformFunc, EnvFunc } from './types';
/**
diff --git a/packages/config-loader/src/lib/transform/types.ts b/packages/config-loader/src/lib/transform/types.ts
index 20e5f88718..0b743cb0fe 100644
--- a/packages/config-loader/src/lib/transform/types.ts
+++ b/packages/config-loader/src/lib/transform/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
export type EnvFunc = (name: string) => Promise;
diff --git a/packages/config-loader/src/lib/transform/utils.ts b/packages/config-loader/src/lib/transform/utils.ts
index a49e9b14bd..b28effa023 100644
--- a/packages/config-loader/src/lib/transform/utils.ts
+++ b/packages/config-loader/src/lib/transform/utils.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonValue, JsonObject } from '@backstage/config';
+import { JsonValue, JsonObject } from '@backstage/types';
export function isObject(obj: JsonValue | undefined): obj is JsonObject {
if (typeof obj !== 'object') {
diff --git a/packages/config/package.json b/packages/config/package.json
index da741b7a3b..719ab22772 100644
--- a/packages/config/package.json
+++ b/packages/config/package.json
@@ -30,11 +30,11 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/types": "^0.1.0",
+ "@backstage/types": "^0.1.1",
"lodash": "^4.17.21"
},
"devDependencies": {
- "@backstage/test-utils-core": "^0.1.2",
+ "@backstage/test-utils": "^0.1.19",
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32"
},
diff --git a/packages/config/src/reader.test.ts b/packages/config/src/reader.test.ts
index 0cea80dca1..3cbab17637 100644
--- a/packages/config/src/reader.test.ts
+++ b/packages/config/src/reader.test.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { withLogCollector } from '@backstage/test-utils-core';
+import { withLogCollector } from '@backstage/test-utils';
import { ConfigReader } from './reader';
const DATA = {
diff --git a/packages/core-app-api/api-report.md b/packages/core-app-api/api-report.md
index 594a0825a7..03c29daa01 100644
--- a/packages/core-app-api/api-report.md
+++ b/packages/core-app-api/api-report.md
@@ -40,8 +40,7 @@ import { IconComponent } from '@backstage/core-plugin-api';
import { microsoftAuthApiRef } from '@backstage/core-plugin-api';
import { OAuthApi } from '@backstage/core-plugin-api';
import { OAuthRequestApi } from '@backstage/core-plugin-api';
-import { Observable } from '@backstage/core-plugin-api';
-import { Observable as Observable_2 } from '@backstage/types';
+import { Observable } from '@backstage/types';
import { oktaAuthApiRef } from '@backstage/core-plugin-api';
import { oneloginAuthApiRef } from '@backstage/core-plugin-api';
import { OpenIdConnectApi } from '@backstage/core-plugin-api';
@@ -341,7 +340,7 @@ export const defaultConfigLoader: AppConfigLoader;
export class ErrorAlerter implements ErrorApi {
constructor(alertApi: AlertApi, errorApi: ErrorApi);
// (undocumented)
- error$(): Observable_2<{
+ error$(): Observable<{
error: {
name: string;
message: string;
diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json
index d11b7122ce..aee8eb9f02 100644
--- a/packages/core-app-api/package.json
+++ b/packages/core-app-api/package.json
@@ -33,6 +33,7 @@
"@backstage/config": "^0.1.10",
"@backstage/core-plugin-api": "^0.1.11",
"@backstage/theme": "^0.2.11",
+ "@backstage/types": "^0.1.1",
"@backstage/version-bridge": "^0.1.0",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
@@ -47,7 +48,6 @@
"devDependencies": {
"@backstage/cli": "^0.8.0",
"@backstage/test-utils": "^0.1.19",
- "@backstage/test-utils-core": "^0.1.3",
"@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-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts b/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts
index 0ba275ea4d..5e649e2f4c 100644
--- a/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts
+++ b/packages/core-app-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { AlertApi, AlertMessage, Observable } from '@backstage/core-plugin-api';
+
+import { AlertApi, AlertMessage } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { PublishSubject } from '../../../lib/subjects';
/**
diff --git a/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts b/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts
index 4dceed4749..3dfa43b661 100644
--- a/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts
+++ b/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { AppThemeApi, AppTheme, Observable } from '@backstage/core-plugin-api';
+import { AppThemeApi, AppTheme } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { BehaviorSubject } from '../../../lib/subjects';
const STORAGE_KEY = 'theme';
diff --git a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts
index 9c9a6f20f1..0edbd2f353 100644
--- a/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts
+++ b/packages/core-app-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts
@@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ErrorApi, ErrorContext, Observable } from '@backstage/core-plugin-api';
+
+import { ErrorApi, ErrorContext } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { PublishSubject } from '../../../lib/subjects';
/**
diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts
index d91cb0ebb0..be643d274b 100644
--- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts
+++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { Observable } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { BehaviorSubject } from '../../../lib/subjects';
type RequestQueueEntry = {
diff --git a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts
index 6c00cd85cd..1cd25c4b56 100644
--- a/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts
+++ b/packages/core-app-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts
@@ -19,8 +19,8 @@ import {
PendingAuthRequest,
AuthRequester,
AuthRequesterOptions,
- Observable,
} from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests';
import { BehaviorSubject } from '../../../lib/subjects';
diff --git a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts
index d1f712e72a..08c4df7e8c 100644
--- a/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts
+++ b/packages/core-app-api/src/apis/implementations/StorageApi/WebStorage.ts
@@ -13,12 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import {
StorageApi,
StorageValueChange,
ErrorApi,
- Observable,
} from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import ObservableImpl from 'zen-observable';
const buckets = new Map();
diff --git a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts
index 85e3e79ad5..72b4209a34 100644
--- a/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts
+++ b/packages/core-app-api/src/apis/implementations/auth/github/GithubAuth.ts
@@ -24,8 +24,8 @@ import {
ProfileInfo,
BackstageIdentity,
AuthRequestOptions,
- Observable,
} from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { SessionManager } from '../../../../lib/AuthSessionManager/types';
import {
AuthSessionStore,
diff --git a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts
index 24db8d2560..8f685b9266 100644
--- a/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts
+++ b/packages/core-app-api/src/apis/implementations/auth/oauth2/OAuth2.ts
@@ -28,8 +28,8 @@ import {
SessionState,
SessionApi,
BackstageIdentityApi,
- Observable,
} from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { OAuth2Session } from './types';
import { OAuthApiCreateOptions } from '../types';
diff --git a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts
index ae4f80c9b1..0a7fb50101 100644
--- a/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts
+++ b/packages/core-app-api/src/apis/implementations/auth/saml/SamlAuth.ts
@@ -25,8 +25,8 @@ import {
ProfileInfoApi,
BackstageIdentityApi,
SessionApi,
- Observable,
} from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { SamlSession } from './types';
import {
AuthSessionStore,
diff --git a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx
index d1793f890e..c08674adfe 100644
--- a/packages/core-app-api/src/apis/system/ApiProvider.test.tsx
+++ b/packages/core-app-api/src/apis/system/ApiProvider.test.tsx
@@ -25,7 +25,7 @@ import {
import { ApiProvider } from './ApiProvider';
import { ApiRegistry } from './ApiRegistry';
import { render } from '@testing-library/react';
-import { withLogCollector } from '@backstage/test-utils-core';
+import { withLogCollector } from '@backstage/test-utils';
import { useVersionedContext } from '@backstage/version-bridge';
describe('ApiProvider', () => {
diff --git a/packages/core-app-api/src/app/createApp.tsx b/packages/core-app-api/src/app/createApp.tsx
index e9c37a1420..901697edba 100644
--- a/packages/core-app-api/src/app/createApp.tsx
+++ b/packages/core-app-api/src/app/createApp.tsx
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { AppConfig, JsonObject } from '@backstage/config';
+import { AppConfig } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { Button } from '@material-ui/core';
import { ErrorPage, ErrorPanel, Progress } from '@backstage/core-components';
import { darkTheme, lightTheme } from '@backstage/theme';
diff --git a/packages/core-app-api/src/lib/AuthSessionManager/OptionalRefreshSessionManagerMux.test.ts b/packages/core-app-api/src/lib/AuthSessionManager/OptionalRefreshSessionManagerMux.test.ts
index 919948f531..35b9a808b2 100644
--- a/packages/core-app-api/src/lib/AuthSessionManager/OptionalRefreshSessionManagerMux.test.ts
+++ b/packages/core-app-api/src/lib/AuthSessionManager/OptionalRefreshSessionManagerMux.test.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { Observable, SessionState } from '@backstage/core-plugin-api';
+import { SessionState } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { OptionalRefreshSessionManagerMux } from './OptionalRefreshSessionManagerMux';
import { MutableSessionManager, SessionManager } from './types';
diff --git a/packages/core-app-api/src/lib/AuthSessionManager/OptionalRefreshSessionManagerMux.ts b/packages/core-app-api/src/lib/AuthSessionManager/OptionalRefreshSessionManagerMux.ts
index 221a7635ea..80dfa1879e 100644
--- a/packages/core-app-api/src/lib/AuthSessionManager/OptionalRefreshSessionManagerMux.ts
+++ b/packages/core-app-api/src/lib/AuthSessionManager/OptionalRefreshSessionManagerMux.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { Observable, SessionState } from '@backstage/core-plugin-api';
+import { SessionState } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import {
SessionManager,
MutableSessionManager,
diff --git a/packages/core-app-api/src/lib/AuthSessionManager/SessionStateTracker.ts b/packages/core-app-api/src/lib/AuthSessionManager/SessionStateTracker.ts
index b70b4bf4ca..4523a868b4 100644
--- a/packages/core-app-api/src/lib/AuthSessionManager/SessionStateTracker.ts
+++ b/packages/core-app-api/src/lib/AuthSessionManager/SessionStateTracker.ts
@@ -15,7 +15,8 @@
*/
import { BehaviorSubject } from '../subjects';
-import { Observable, SessionState } from '@backstage/core-plugin-api';
+import { SessionState } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
export class SessionStateTracker {
private readonly subject = new BehaviorSubject(
diff --git a/packages/core-app-api/src/lib/AuthSessionManager/types.ts b/packages/core-app-api/src/lib/AuthSessionManager/types.ts
index 43655b8395..a2a5800974 100644
--- a/packages/core-app-api/src/lib/AuthSessionManager/types.ts
+++ b/packages/core-app-api/src/lib/AuthSessionManager/types.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { Observable, SessionState } from '@backstage/core-plugin-api';
+import { SessionState } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
export type GetSessionOptions = {
optional?: boolean;
diff --git a/packages/core-app-api/src/lib/subjects.ts b/packages/core-app-api/src/lib/subjects.ts
index e122da819b..79c2c25675 100644
--- a/packages/core-app-api/src/lib/subjects.ts
+++ b/packages/core-app-api/src/lib/subjects.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { Observable } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import ObservableImpl from 'zen-observable';
// TODO(Rugvip): These are stopgap and probably incomplete implementations of subjects.
diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json
index aeb35faa2f..c59668a432 100644
--- a/packages/core-plugin-api/package.json
+++ b/packages/core-plugin-api/package.json
@@ -31,7 +31,7 @@
"dependencies": {
"@backstage/config": "^0.1.9",
"@backstage/theme": "^0.2.9",
- "@backstage/types": "^0.1.0",
+ "@backstage/types": "^0.1.1",
"@backstage/version-bridge": "^0.1.0",
"@material-ui/core": "^4.12.2",
"@types/react": "*",
@@ -45,8 +45,7 @@
"devDependencies": {
"@backstage/cli": "^0.8.0",
"@backstage/core-app-api": "^0.1.18",
- "@backstage/test-utils": "^0.1.18",
- "@backstage/test-utils-core": "^0.1.3",
+ "@backstage/test-utils": "^0.1.19",
"@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/extensions/extensions.test.tsx b/packages/core-plugin-api/src/extensions/extensions.test.tsx
index fabf90eebb..29c696c71f 100644
--- a/packages/core-plugin-api/src/extensions/extensions.test.tsx
+++ b/packages/core-plugin-api/src/extensions/extensions.test.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { withLogCollector } from '@backstage/test-utils-core';
+import { withLogCollector } from '@backstage/test-utils';
import { render, screen } from '@testing-library/react';
import React from 'react';
import { useAnalyticsContext } from '../analytics/AnalyticsContext';
diff --git a/packages/errors/api-report.md b/packages/errors/api-report.md
index ffb111884e..a96132cee7 100644
--- a/packages/errors/api-report.md
+++ b/packages/errors/api-report.md
@@ -3,7 +3,7 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
// @public
export function assertError(value: unknown): asserts value is ErrorLike;
diff --git a/packages/errors/package.json b/packages/errors/package.json
index e3d49f55c7..b1f214e5af 100644
--- a/packages/errors/package.json
+++ b/packages/errors/package.json
@@ -30,7 +30,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@backstage/config": "^0.1.9",
+ "@backstage/types": "^0.1.1",
"cross-fetch": "^3.0.6",
"serialize-error": "^8.0.1"
},
diff --git a/packages/errors/src/serialization/error.ts b/packages/errors/src/serialization/error.ts
index b1f63063cf..8d031949bf 100644
--- a/packages/errors/src/serialization/error.ts
+++ b/packages/errors/src/serialization/error.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import {
deserializeError as deserializeErrorInternal,
serializeError as serializeErrorInternal,
diff --git a/packages/search-common/api-report.md b/packages/search-common/api-report.md
index d8d9c2922a..c4950e3556 100644
--- a/packages/search-common/api-report.md
+++ b/packages/search-common/api-report.md
@@ -3,7 +3,7 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
// Warning: (ae-missing-release-tag) "DocumentCollator" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
diff --git a/packages/search-common/package.json b/packages/search-common/package.json
index 46e3518b51..fee0fe59dd 100644
--- a/packages/search-common/package.json
+++ b/packages/search-common/package.json
@@ -36,7 +36,7 @@
"url": "https://github.com/backstage/backstage/issues"
},
"dependencies": {
- "@backstage/config": "^0.1.6"
+ "@backstage/types": "^0.1.1"
},
"devDependencies": {
"@backstage/cli": "^0.8.0"
diff --git a/packages/search-common/src/types.ts b/packages/search-common/src/types.ts
index 063e0cb96c..f73665de5b 100644
--- a/packages/search-common/src/types.ts
+++ b/packages/search-common/src/types.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
export interface SearchQuery {
term: string;
diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js
index 4afcac335c..5bdc6b3a2f 100644
--- a/packages/storybook/.storybook/main.js
+++ b/packages/storybook/.storybook/main.js
@@ -1,61 +1,77 @@
const path = require('path');
const WebpackPluginFailBuildOnWarning = require('./webpack-plugin-fail-build-on-warning');
-module.exports = {
- stories: [
- '../../core-components/src/**/*.stories.tsx',
- '../../../plugins/**/src/**/*.stories.tsx',
- ],
- addons: [
- '@storybook/addon-a11y',
- '@storybook/addon-actions',
- '@storybook/addon-links',
- '@storybook/addon-storysource',
- 'storybook-dark-mode/register',
- ],
- webpackFinal: async config => {
- // Mirror config in packages/cli/src/lib/bundler
- config.resolve.mainFields = ['browser', 'module', 'main'];
+module.exports = ({ args }) => {
+ // Calling storybook with no args causes our default list of stories to be used.
+ // This set of stories are the ones that we publish to backstage.io
+ //
+ // If it's called with args, each arg should be the path to a package that we will
+ // show the stories from, for example `yarn storybook plugins/catalog`.
+ let stories;
+ if (args.length === 0) {
+ stories = [
+ '../../core-components/src/**/*.stories.tsx',
+ '../../../plugins/org/src/**/*.stories.tsx',
+ '../../../plugins/search/src/**/*.stories.tsx',
+ ];
+ } else {
+ const rootDir = path.resolve(__dirname, '../../..');
+ stories = args.map(arg => path.join(rootDir, arg, 'src/**/*.stories.tsx'));
+ }
- // Remove the default babel-loader for js files, we're using sucrase instead
- const [jsLoader] = config.module.rules.splice(0, 1);
- if (!jsLoader.use[0].loader.includes('babel-loader')) {
- throw new Error(
- `Unexpected loader removed from storybook config, ${jsLoader.use[0].loader}`,
+ return {
+ stories,
+ addons: [
+ '@storybook/addon-a11y',
+ '@storybook/addon-actions',
+ '@storybook/addon-links',
+ '@storybook/addon-storysource',
+ 'storybook-dark-mode/register',
+ ],
+ webpackFinal: async config => {
+ // Mirror config in packages/cli/src/lib/bundler
+ config.resolve.mainFields = ['browser', 'module', 'main'];
+
+ // Remove the default babel-loader for js files, we're using sucrase instead
+ const [jsLoader] = config.module.rules.splice(0, 1);
+ if (!jsLoader.use[0].loader.includes('babel-loader')) {
+ throw new Error(
+ `Unexpected loader removed from storybook config, ${jsLoader.use[0].loader}`,
+ );
+ }
+
+ config.resolve.extensions.push('.ts', '.tsx');
+
+ config.module.rules.push(
+ {
+ test: /\.(tsx?)$/,
+ exclude: /node_modules/,
+ loader: require.resolve('@sucrase/webpack-loader'),
+ options: {
+ transforms: ['typescript', 'jsx', 'react-hot-loader'],
+ },
+ },
+ {
+ test: /\.(jsx?|mjs)$/,
+ exclude: /node_modules/,
+ loader: require.resolve('@sucrase/webpack-loader'),
+ options: {
+ transforms: ['jsx', 'react-hot-loader'],
+ },
+ },
);
- }
- config.resolve.extensions.push('.ts', '.tsx');
+ // Disable ProgressPlugin which logs verbose webpack build progress. Warnings and Errors are still logged.
+ config.plugins = config.plugins.filter(
+ ({ constructor }) => constructor.name !== 'ProgressPlugin',
+ );
- config.module.rules.push(
- {
- test: /\.(tsx?)$/,
- exclude: /node_modules/,
- loader: require.resolve('@sucrase/webpack-loader'),
- options: {
- transforms: ['typescript', 'jsx', 'react-hot-loader'],
- },
- },
- {
- test: /\.(jsx?|mjs)$/,
- exclude: /node_modules/,
- loader: require.resolve('@sucrase/webpack-loader'),
- options: {
- transforms: ['jsx', 'react-hot-loader'],
- },
- },
- );
+ // Fail storybook build on CI if there are webpack warnings.
+ if (process.env.CI) {
+ config.plugins.push(new WebpackPluginFailBuildOnWarning());
+ }
- // Disable ProgressPlugin which logs verbose webpack build progress. Warnings and Errors are still logged.
- config.plugins = config.plugins.filter(
- ({ constructor }) => constructor.name !== 'ProgressPlugin',
- );
-
- // Fail storybook build on CI if there are webpack warnings.
- if (process.env.CI) {
- config.plugins.push(new WebpackPluginFailBuildOnWarning());
- }
-
- return config;
- },
+ return config;
+ },
+ };
};
diff --git a/packages/storybook/README.md b/packages/storybook/README.md
index f966a358d3..600e2409ab 100644
--- a/packages/storybook/README.md
+++ b/packages/storybook/README.md
@@ -2,6 +2,16 @@
This package provides a Storybook build for Backstage. See https://backstage.io/storybook/.
+## Usage
+
+To run the storybook locally, call `yarn storybook` in repo root. This will show the default set of stories that we publish to https://backstage.io/storybook
+
+If you want to show stories other than the default set, you can pass one or more package paths as an argument when calling storybook, for example:
+
+```sh
+yarn storybook plugins/search
+```
+
## Why is this not part of `@backstage/core-components`?
This separate storybook package exists because of dependency conflicts with `@backstage/cli`. It uses `nohoist` to avoid the conflicts, and since you can only use that in private packages it has to be separated out of `@backstage/core-components`.
diff --git a/packages/techdocs-common/src/stages/publish/awsS3.test.ts b/packages/techdocs-common/src/stages/publish/awsS3.test.ts
index 3a637ebbc6..9b74d89dda 100644
--- a/packages/techdocs-common/src/stages/publish/awsS3.test.ts
+++ b/packages/techdocs-common/src/stages/publish/awsS3.test.ts
@@ -43,9 +43,11 @@ const loggerErrorSpy = jest.spyOn(logger, 'error');
const createPublisherFromConfig = ({
bucketName = 'bucketName',
+ bucketRootPath = '/',
legacyUseCaseSensitiveTripletPaths = false,
}: {
bucketName?: string;
+ bucketRootPath?: string;
legacyUseCaseSensitiveTripletPaths?: boolean;
} = {}) => {
const mockConfig = new ConfigReader({
@@ -59,6 +61,7 @@ const createPublisherFromConfig = ({
secretAccessKey: 'secretAccessKey',
},
bucketName,
+ bucketRootPath,
},
},
legacyUseCaseSensitiveTripletPaths,
@@ -153,6 +156,21 @@ describe('AwsS3Publish', () => {
expect(await publisher.publish({ entity, directory })).toBeUndefined();
});
+ it('should publish a directory when root path is specified', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ });
+ expect(await publisher.publish({ entity, directory })).toBeUndefined();
+ });
+
+ it('should publish a directory when root path is specified and legacy casing is used', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ legacyUseCaseSensitiveTripletPaths: true,
+ });
+ expect(await publisher.publish({ entity, directory })).toBeUndefined();
+ });
+
it('should fail to publish a directory', async () => {
const wrongPathToGeneratedDirectory = path.join(
rootDir,
@@ -214,6 +232,23 @@ describe('AwsS3Publish', () => {
expect(await publisher.hasDocsBeenGenerated(entity)).toBe(true);
});
+ it('should return true if docs has been generated if root path is specified', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ });
+ await publisher.publish({ entity, directory });
+ expect(await publisher.hasDocsBeenGenerated(entity)).toBe(true);
+ });
+
+ it('should return true if docs has been generated if root path is specified and legacy casing is used', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ legacyUseCaseSensitiveTripletPaths: true,
+ });
+ await publisher.publish({ entity, directory });
+ expect(await publisher.hasDocsBeenGenerated(entity)).toBe(true);
+ });
+
it('should return false if docs has not been generated', async () => {
const publisher = createPublisherFromConfig();
expect(
@@ -247,6 +282,27 @@ describe('AwsS3Publish', () => {
);
});
+ it('should return tech docs metadata even if root path is specified', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ });
+ await publisher.publish({ entity, directory });
+ expect(await publisher.fetchTechDocsMetadata(entityName)).toStrictEqual(
+ techdocsMetadata,
+ );
+ });
+
+ it('should return tech docs metadata if root path is specified and legacy casing is used', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ legacyUseCaseSensitiveTripletPaths: true,
+ });
+ await publisher.publish({ entity, directory });
+ expect(await publisher.fetchTechDocsMetadata(entityName)).toStrictEqual(
+ techdocsMetadata,
+ );
+ });
+
it('should return tech docs metadata when json encoded with single quotes', async () => {
const techdocsMetadataPath = path.join(
directory,
@@ -335,6 +391,47 @@ describe('AwsS3Publish', () => {
expect(jsResponse.text).toEqual('found it too');
});
+ it('should pass expected object path to bucket if root path is specified', async () => {
+ const rootPath = 'backstage-data/techdocs';
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: rootPath,
+ });
+ await publisher.publish({ entity, directory });
+ app = express().use(publisher.docsRouter());
+
+ const pngResponse = await request(app).get(
+ `/${rootPath}/${entityTripletPath}/img/with%20spaces.png`,
+ );
+ expect(Buffer.from(pngResponse.body).toString('utf8')).toEqual(
+ 'found it',
+ );
+ const jsResponse = await request(app).get(
+ `/${rootPath}/${entityTripletPath}/some%20folder/also%20with%20spaces.js`,
+ );
+ expect(jsResponse.text).toEqual('found it too');
+ });
+
+ it('should pass expected object path to bucket if root path is specified and legacy case is enabled', async () => {
+ const rootPath = 'backstage-data/techdocs';
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: rootPath,
+ legacyUseCaseSensitiveTripletPaths: true,
+ });
+ await publisher.publish({ entity, directory });
+ app = express().use(publisher.docsRouter());
+
+ const pngResponse = await request(app).get(
+ `/${rootPath}/${entityTripletPath}/img/with%20spaces.png`,
+ );
+ expect(Buffer.from(pngResponse.body).toString('utf8')).toEqual(
+ 'found it',
+ );
+ const jsResponse = await request(app).get(
+ `/${rootPath}/${entityTripletPath}/some%20folder/also%20with%20spaces.js`,
+ );
+ expect(jsResponse.text).toEqual('found it too');
+ });
+
it('should pass text/plain content-type for html', async () => {
const htmlResponse = await request(app).get(
`/${entityTripletPath}/html/unsafe.html`,
diff --git a/packages/techdocs-common/src/stages/publish/awsS3.ts b/packages/techdocs-common/src/stages/publish/awsS3.ts
index ee48db575f..29eecb8d03 100644
--- a/packages/techdocs-common/src/stages/publish/awsS3.ts
+++ b/packages/techdocs-common/src/stages/publish/awsS3.ts
@@ -34,6 +34,7 @@ import {
getStaleFiles,
lowerCaseEntityTriplet,
lowerCaseEntityTripletInStoragePath,
+ normalizeExternalStorageRootPath,
} from './helpers';
import {
PublisherBase,
@@ -67,6 +68,10 @@ export class AwsS3Publish implements PublisherBase {
);
}
+ const bucketRootPath = normalizeExternalStorageRootPath(
+ config.getOptionalString('techdocs.publisher.awsS3.bucketRootPath') || '',
+ );
+
// Credentials is an optional config. If missing, the default ways of authenticating AWS SDK V2 will be used.
// 1. AWS environment variables
// https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html
@@ -112,6 +117,7 @@ export class AwsS3Publish implements PublisherBase {
bucketName,
legacyPathCasing,
logger,
+ bucketRootPath,
);
}
@@ -151,11 +157,13 @@ export class AwsS3Publish implements PublisherBase {
private readonly bucketName: string,
private readonly legacyPathCasing: boolean,
private readonly logger: Logger,
+ private readonly bucketRootPath: string,
) {
this.storageClient = storageClient;
this.bucketName = bucketName;
this.legacyPathCasing = legacyPathCasing;
this.logger = logger;
+ this.bucketRootPath = bucketRootPath;
}
/**
@@ -193,6 +201,8 @@ export class AwsS3Publish implements PublisherBase {
*/
async publish({ entity, directory }: PublishRequest): Promise {
const useLegacyPathCasing = this.legacyPathCasing;
+ const bucketRootPath = this.bucketRootPath;
+
// First, try to retrieve a list of all individual files currently existing
let existingFiles: string[] = [];
try {
@@ -200,6 +210,7 @@ export class AwsS3Publish implements PublisherBase {
entity,
undefined,
useLegacyPathCasing,
+ bucketRootPath,
);
existingFiles = await this.getAllObjectsFromBucket({
prefix: remoteFolder,
@@ -230,6 +241,7 @@ export class AwsS3Publish implements PublisherBase {
entity,
relativeFilePath,
useLegacyPathCasing,
+ bucketRootPath,
),
Body: fileStream,
};
@@ -257,6 +269,7 @@ export class AwsS3Publish implements PublisherBase {
entity,
path.relative(directory, absoluteFilePath),
useLegacyPathCasing,
+ bucketRootPath,
),
);
const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);
@@ -289,10 +302,12 @@ export class AwsS3Publish implements PublisherBase {
try {
return await new Promise(async (resolve, reject) => {
const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;
- const entityRootDir = this.legacyPathCasing
+ const entityDir = this.legacyPathCasing
? entityTriplet
: lowerCaseEntityTriplet(entityTriplet);
+ const entityRootDir = path.join(this.bucketRootPath, entityDir);
+
const stream = this.storageClient
.getObject({
Bucket: this.bucketName,
@@ -332,10 +347,17 @@ export class AwsS3Publish implements PublisherBase {
// Decode and trim the leading forward slash
const decodedUri = decodeURI(req.path.replace(/^\//, ''));
+ // Root path is removed from the Uri so that legacy casing can be applied
+ // to the entity triplet without manipulating the root path
+ const decodedUriNoRoot = path.relative(this.bucketRootPath, decodedUri);
+
// filePath example - /default/component/documented-component/index.html
- const filePath = this.legacyPathCasing
- ? decodedUri
- : lowerCaseEntityTripletInStoragePath(decodedUri);
+ const filePathNoRoot = this.legacyPathCasing
+ ? decodedUriNoRoot
+ : lowerCaseEntityTripletInStoragePath(decodedUriNoRoot);
+
+ // Re-prepend the root path to the relative file path
+ const filePath = path.join(this.bucketRootPath, filePathNoRoot);
// Files with different extensions (CSS, HTML) need to be served with different headers
const fileExtension = path.extname(filePath);
@@ -370,10 +392,12 @@ export class AwsS3Publish implements PublisherBase {
async hasDocsBeenGenerated(entity: Entity): Promise {
try {
const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
- const entityRootDir = this.legacyPathCasing
+ const entityDir = this.legacyPathCasing
? entityTriplet
: lowerCaseEntityTriplet(entityTriplet);
+ const entityRootDir = path.join(this.bucketRootPath, entityDir);
+
await this.storageClient
.headObject({
Bucket: this.bucketName,
diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts
index a04f61ac11..d31995ea2e 100644
--- a/packages/techdocs-common/src/stages/publish/googleStorage.test.ts
+++ b/packages/techdocs-common/src/stages/publish/googleStorage.test.ts
@@ -43,9 +43,11 @@ jest.spyOn(logger, 'error').mockReturnValue(logger);
const createPublisherFromConfig = ({
bucketName = 'bucketName',
+ bucketRootPath = '/',
legacyUseCaseSensitiveTripletPaths = false,
}: {
bucketName?: string;
+ bucketRootPath?: string;
legacyUseCaseSensitiveTripletPaths?: boolean;
} = {}) => {
const config = new ConfigReader({
@@ -56,6 +58,7 @@ const createPublisherFromConfig = ({
googleGcs: {
credentials: '{}',
bucketName,
+ bucketRootPath,
},
},
legacyUseCaseSensitiveTripletPaths,
@@ -149,6 +152,21 @@ describe('GoogleGCSPublish', () => {
expect(await publisher.publish({ entity, directory })).toBeUndefined();
});
+ it('should publish a directory when root path is specified', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ });
+ expect(await publisher.publish({ entity, directory })).toBeUndefined();
+ });
+
+ it('should publish a directory when root path is specified and legacy casing is used', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ legacyUseCaseSensitiveTripletPaths: true,
+ });
+ expect(await publisher.publish({ entity, directory })).toBeUndefined();
+ });
+
it('should fail to publish a directory', async () => {
const wrongPathToGeneratedDirectory = path.join(
rootDir,
@@ -212,6 +230,23 @@ describe('GoogleGCSPublish', () => {
expect(await publisher.hasDocsBeenGenerated(entity)).toBe(true);
});
+ it('should return true if docs has been generated if root path is specified', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ });
+ await publisher.publish({ entity, directory });
+ expect(await publisher.hasDocsBeenGenerated(entity)).toBe(true);
+ });
+
+ it('should return true if docs has been generated if root path is specified and legacy casing is used', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ legacyUseCaseSensitiveTripletPaths: true,
+ });
+ await publisher.publish({ entity, directory });
+ expect(await publisher.hasDocsBeenGenerated(entity)).toBe(true);
+ });
+
it('should return false if docs has not been generated', async () => {
const publisher = createPublisherFromConfig();
expect(
@@ -245,6 +280,27 @@ describe('GoogleGCSPublish', () => {
);
});
+ it('should return tech docs metadata even if root path is specified', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ });
+ await publisher.publish({ entity, directory });
+ expect(await publisher.fetchTechDocsMetadata(entityName)).toStrictEqual(
+ techdocsMetadata,
+ );
+ });
+
+ it('should return tech docs metadata if root path is specified and legacy casing is used', async () => {
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: 'backstage-data/techdocs',
+ legacyUseCaseSensitiveTripletPaths: true,
+ });
+ await publisher.publish({ entity, directory });
+ expect(await publisher.fetchTechDocsMetadata(entityName)).toStrictEqual(
+ techdocsMetadata,
+ );
+ });
+
it('should return tech docs metadata when json encoded with single quotes', async () => {
const techdocsMetadataPath = path.join(
directory,
@@ -291,6 +347,7 @@ describe('GoogleGCSPublish', () => {
describe('docsRouter', () => {
const entityTripletPath = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
+ // const entityTripletPath =
let app: Express.Application;
@@ -334,6 +391,47 @@ describe('GoogleGCSPublish', () => {
expect(jsResponse.text).toEqual('found it too');
});
+ it('should pass expected object path to bucket if root path is specified', async () => {
+ const rootPath = 'backstage-data/techdocs';
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: rootPath,
+ });
+ await publisher.publish({ entity, directory });
+ app = express().use(publisher.docsRouter());
+
+ const pngResponse = await request(app).get(
+ `/${rootPath}/${entityTripletPath}/img/with%20spaces.png`,
+ );
+ expect(Buffer.from(pngResponse.body).toString('utf8')).toEqual(
+ 'found it',
+ );
+ const jsResponse = await request(app).get(
+ `/${rootPath}/${entityTripletPath}/some%20folder/also%20with%20spaces.js`,
+ );
+ expect(jsResponse.text).toEqual('found it too');
+ });
+
+ it('should pass expected object path to bucket if root path is specified and legacy case is enabled', async () => {
+ const rootPath = 'backstage-data/techdocs';
+ const publisher = createPublisherFromConfig({
+ bucketRootPath: rootPath,
+ legacyUseCaseSensitiveTripletPaths: true,
+ });
+ await publisher.publish({ entity, directory });
+ app = express().use(publisher.docsRouter());
+
+ const pngResponse = await request(app).get(
+ `/${rootPath}/${entityTripletPath}/img/with%20spaces.png`,
+ );
+ expect(Buffer.from(pngResponse.body).toString('utf8')).toEqual(
+ 'found it',
+ );
+ const jsResponse = await request(app).get(
+ `/${rootPath}/${entityTripletPath}/some%20folder/also%20with%20spaces.js`,
+ );
+ expect(jsResponse.text).toEqual('found it too');
+ });
+
it('should pass text/plain content-type for html', async () => {
const htmlResponse = await request(app).get(
`/${entityTripletPath}/html/unsafe.html`,
diff --git a/packages/techdocs-common/src/stages/publish/googleStorage.ts b/packages/techdocs-common/src/stages/publish/googleStorage.ts
index dcce1b8ee9..5a142e800a 100644
--- a/packages/techdocs-common/src/stages/publish/googleStorage.ts
+++ b/packages/techdocs-common/src/stages/publish/googleStorage.ts
@@ -30,6 +30,7 @@ import {
bulkStorageOperation,
getCloudPathForLocalPath,
getStaleFiles,
+ normalizeExternalStorageRootPath,
} from './helpers';
import { MigrateWriteStream } from './migrations';
import {
@@ -51,6 +52,11 @@ export class GoogleGCSPublish implements PublisherBase {
);
}
+ const bucketRootPath = normalizeExternalStorageRootPath(
+ config.getOptionalString('techdocs.publisher.googleGcs.bucketRootPath') ||
+ '',
+ );
+
// Credentials is an optional config. If missing, default GCS environment variables will be used.
// Read more here https://cloud.google.com/docs/authentication/production
const credentials = config.getOptionalString(
@@ -83,6 +89,7 @@ export class GoogleGCSPublish implements PublisherBase {
bucketName,
legacyPathCasing,
logger,
+ bucketRootPath,
);
}
@@ -91,11 +98,13 @@ export class GoogleGCSPublish implements PublisherBase {
private readonly bucketName: string,
private readonly legacyPathCasing: boolean,
private readonly logger: Logger,
+ private readonly bucketRootPath: string,
) {
this.storageClient = storageClient;
this.bucketName = bucketName;
this.legacyPathCasing = legacyPathCasing;
this.logger = logger;
+ this.bucketRootPath = bucketRootPath;
}
/**
@@ -133,6 +142,7 @@ export class GoogleGCSPublish implements PublisherBase {
async publish({ entity, directory }: PublishRequest): Promise {
const useLegacyPathCasing = this.legacyPathCasing;
const bucket = this.storageClient.bucket(this.bucketName);
+ const bucketRootPath = this.bucketRootPath;
// First, try to retrieve a list of all individual files currently existing
let existingFiles: string[] = [];
@@ -141,6 +151,7 @@ export class GoogleGCSPublish implements PublisherBase {
entity,
undefined,
useLegacyPathCasing,
+ bucketRootPath,
);
existingFiles = await this.getFilesForFolder(remoteFolder);
} catch (e) {
@@ -166,6 +177,7 @@ export class GoogleGCSPublish implements PublisherBase {
entity,
relativeFilePath,
useLegacyPathCasing,
+ bucketRootPath,
),
});
},
@@ -190,6 +202,7 @@ export class GoogleGCSPublish implements PublisherBase {
entity,
path.relative(directory, absoluteFilePath),
useLegacyPathCasing,
+ bucketRootPath,
),
);
const staleFiles = getStaleFiles(relativeFilesToUpload, existingFiles);
@@ -214,10 +227,12 @@ export class GoogleGCSPublish implements PublisherBase {
fetchTechDocsMetadata(entityName: EntityName): Promise {
return new Promise((resolve, reject) => {
const entityTriplet = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;
- const entityRootDir = this.legacyPathCasing
+ const entityDir = this.legacyPathCasing
? entityTriplet
: lowerCaseEntityTriplet(entityTriplet);
+ const entityRootDir = path.join(this.bucketRootPath, entityDir);
+
const fileStreamChunks: Array = [];
this.storageClient
.bucket(this.bucketName)
@@ -246,10 +261,16 @@ export class GoogleGCSPublish implements PublisherBase {
// Decode and trim the leading forward slash
const decodedUri = decodeURI(req.path.replace(/^\//, ''));
- // filePath example - /default/component/documented-component/index.html
- const filePath = this.legacyPathCasing
- ? decodedUri
- : lowerCaseEntityTripletInStoragePath(decodedUri);
+ // Root path is removed from the Uri so that legacy casing can be applied
+ // to the entity triplet without manipulating the root path
+ const decodedUriNoRoot = path.relative(this.bucketRootPath, decodedUri);
+
+ const filePathNoRoot = this.legacyPathCasing
+ ? decodedUriNoRoot
+ : lowerCaseEntityTripletInStoragePath(decodedUriNoRoot);
+
+ // Re-prepend the root path to the relative file path
+ const filePath = path.join(this.bucketRootPath, filePathNoRoot);
// Files with different extensions (CSS, HTML) need to be served with different headers
const fileExtension = path.extname(filePath);
@@ -285,10 +306,12 @@ export class GoogleGCSPublish implements PublisherBase {
async hasDocsBeenGenerated(entity: Entity): Promise {
return new Promise(resolve => {
const entityTriplet = `${entity.metadata.namespace}/${entity.kind}/${entity.metadata.name}`;
- const entityRootDir = this.legacyPathCasing
+ const entityDir = this.legacyPathCasing
? entityTriplet
: lowerCaseEntityTriplet(entityTriplet);
+ const entityRootDir = path.join(this.bucketRootPath, entityDir);
+
this.storageClient
.bucket(this.bucketName)
.file(`${entityRootDir}/index.html`)
diff --git a/packages/techdocs-common/src/stages/publish/helpers.test.ts b/packages/techdocs-common/src/stages/publish/helpers.test.ts
index a3571ef0ab..6245898586 100644
--- a/packages/techdocs-common/src/stages/publish/helpers.test.ts
+++ b/packages/techdocs-common/src/stages/publish/helpers.test.ts
@@ -25,6 +25,7 @@ import {
bulkStorageOperation,
lowerCaseEntityTriplet,
lowerCaseEntityTripletInStoragePath,
+ normalizeExternalStorageRootPath,
} from './helpers';
describe('getHeadersForFileExtension', () => {
@@ -107,6 +108,34 @@ describe('lowerCaseEntityTripletInStoragePath', () => {
});
});
+describe('normalizeExternalStorageRootPath', () => {
+ it('returns an empty string when empty string provided', () => {
+ const originalPath = '';
+ const normalPath = normalizeExternalStorageRootPath(originalPath);
+ expect(normalPath).toBe('');
+ });
+ it('returns an empty string when only separator is provided', () => {
+ const originalPath = '/';
+ const normalPath = normalizeExternalStorageRootPath(originalPath);
+ expect(normalPath).toBe('');
+ });
+ it('returns normalized path from path with leading and trailing sep', () => {
+ const originalPath = '/backstage-data/techdocs/';
+ const normalPath = normalizeExternalStorageRootPath(originalPath);
+ expect(normalPath).toBe('backstage-data/techdocs');
+ });
+ it('returns normalized path from path without leading and trailing sep', () => {
+ const originalPath = 'backstage-data/techdocs';
+ const normalPath = normalizeExternalStorageRootPath(originalPath);
+ expect(normalPath).toBe('backstage-data/techdocs');
+ });
+ it('returns normalized path from path with trailing sep', () => {
+ const originalPath = 'backstage-data/techdocs/';
+ const normalPath = normalizeExternalStorageRootPath(originalPath);
+ expect(normalPath).toBe('backstage-data/techdocs');
+ });
+});
+
describe('getStaleFiles', () => {
const defaultFiles = [
'default/Component/backstage/index.html',
@@ -172,6 +201,60 @@ describe('getCloudPathForLocalPath', () => {
it('should throw error when entity is invalid', () => {
expect(() => getCloudPathForLocalPath({} as Entity)).toThrow();
});
+
+ it('should prepend root directory to destination', () => {
+ const localPath = 'index/html';
+ const rootPath = 'backstage-data/techdocs/';
+ const remoteBucket = getCloudPathForLocalPath(
+ entity,
+ localPath,
+ false,
+ rootPath,
+ );
+ expect(remoteBucket).toBe(
+ `backstage-data/techdocs/custom/component/backstage/${localPath}`,
+ );
+ });
+
+ it('should add trailing seperator to root directory', () => {
+ const localPath = 'index/html';
+ const rootPath = 'backstage-data/techdocs';
+ const remoteBucket = getCloudPathForLocalPath(
+ entity,
+ localPath,
+ false,
+ rootPath,
+ );
+ expect(remoteBucket).toBe(
+ `backstage-data/techdocs/custom/component/backstage/${localPath}`,
+ );
+ });
+
+ it('should remove leading seperator from root directory', () => {
+ const localPath = 'index/html';
+ const rootPath = '/backstage-data/techdocs/';
+ const remoteBucket = getCloudPathForLocalPath(
+ entity,
+ localPath,
+ false,
+ rootPath,
+ );
+ expect(remoteBucket).toBe(
+ `backstage-data/techdocs/custom/component/backstage/${localPath}`,
+ );
+ });
+
+ it('should ignore seperator if root directory is explicitly defined', () => {
+ const localPath = 'index/html';
+ const rootPath = '/';
+ const remoteBucket = getCloudPathForLocalPath(
+ entity,
+ localPath,
+ false,
+ rootPath,
+ );
+ expect(remoteBucket).toBe(`custom/component/backstage/${localPath}`);
+ });
});
describe('bulkStorageOperation', () => {
diff --git a/packages/techdocs-common/src/stages/publish/helpers.ts b/packages/techdocs-common/src/stages/publish/helpers.ts
index 9b935ccbfd..c23c72af38 100644
--- a/packages/techdocs-common/src/stages/publish/helpers.ts
+++ b/packages/techdocs-common/src/stages/publish/helpers.ts
@@ -140,6 +140,29 @@ export const lowerCaseEntityTripletInStoragePath = (
return lowerCaseEntityTriplet(parts.join(path.posix.sep));
};
+/**
+ * Take a posix path and return a path without leading and trailing
+ * separators
+ *
+ * @example
+ * normalizeExternalStorageRootPath('/backstage-data/techdocs/')
+ * // return backstage-data/techdocs
+ */
+export const normalizeExternalStorageRootPath = (posixPath: string): string => {
+ // remove leading slash
+ let normalizedPath = posixPath;
+ if (posixPath.startsWith(path.posix.sep)) {
+ normalizedPath = posixPath.slice(1);
+ }
+
+ // remove trailing slash
+ if (normalizedPath.endsWith(path.posix.sep)) {
+ normalizedPath = normalizedPath.slice(0, normalizedPath.length - 1);
+ }
+
+ return normalizedPath;
+};
+
// Only returns the files that existed previously and are not present anymore.
export const getStaleFiles = (
newFiles: string[],
@@ -157,6 +180,7 @@ export const getCloudPathForLocalPath = (
entity: Entity,
localPath = '',
useLegacyPathCasing = false,
+ externalStorageRootPath = '',
): string => {
// Convert destination file path to a POSIX path for uploading.
// GCS expects / as path separator and relativeFilePath will contain \\ on Windows.
@@ -169,11 +193,17 @@ export const getCloudPathForLocalPath = (
}/${entity.kind}/${entity.metadata.name}`;
const relativeFilePathTriplet = `${entityRootDir}/${relativeFilePathPosix}`;
+
const destination = useLegacyPathCasing
? relativeFilePathTriplet
: lowerCaseEntityTriplet(relativeFilePathTriplet);
- return destination; // Remote storage file relative path
+ const destinationWithRoot = path.join(
+ ...externalStorageRootPath.split(path.posix.sep),
+ destination,
+ );
+
+ return destinationWithRoot; // Remote storage file relative path
};
// Perform rate limited generic operations by passing a function and a list of arguments
diff --git a/packages/test-utils-core/api-report.md b/packages/test-utils-core/api-report.md
index 4dad7a3a8a..f21c749727 100644
--- a/packages/test-utils-core/api-report.md
+++ b/packages/test-utils-core/api-report.md
@@ -3,113 +3,5 @@
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
-import { ReactElement } from 'react';
-import { RenderResult } from '@testing-library/react';
-
-// Warning: (ae-missing-release-tag) "AsyncLogCollector" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
-export type AsyncLogCollector = () => Promise;
-
-// Warning: (ae-missing-release-tag) "CollectedLogs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
-export type CollectedLogs = {
- [key in T]: string[];
-};
-
-// Warning: (ae-missing-release-tag) "Keyboard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
-export class Keyboard {
- constructor(
- target: any,
- {
- debug,
- }?: {
- debug?: boolean | undefined;
- },
- );
- // (undocumented)
- click(): Promise;
- // (undocumented)
- debug: boolean;
- // (undocumented)
- document: any;
- // (undocumented)
- enter(value: any): Promise;
- // (undocumented)
- escape(): Promise;
- // (undocumented)
- get focused(): any;
- // (undocumented)
- static fromReadableInput(input: any): any;
- // (undocumented)
- _log(message: any, ...args: any[]): void;
- // (undocumented)
- _pretty(element: any): string;
- // (undocumented)
- send(chars: any): Promise;
- // (undocumented)
- _sendKey(key: any, charCode: any, action: any): Promise;
- // (undocumented)
- tab(): Promise;
- // (undocumented)
- static toReadableInput(chars: any): any;
- // (undocumented)
- toString(): string;
- // (undocumented)
- static type(target: any, input: any): Promise;
- // (undocumented)
- type(input: any): Promise;
- // (undocumented)
- static typeDebug(target: any, input: any): Promise;
-}
-
-// Warning: (ae-missing-release-tag) "LogCollector" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
-export type LogCollector = AsyncLogCollector | SyncLogCollector;
-
-// Warning: (ae-missing-release-tag) "LogFuncs" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
-export type LogFuncs = 'log' | 'warn' | 'error';
-
-// Warning: (ae-missing-release-tag) "renderWithEffects" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
-export function renderWithEffects(nodes: ReactElement): Promise;
-
-// Warning: (ae-missing-release-tag) "SyncLogCollector" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
-export type SyncLogCollector = () => void;
-
-// Warning: (ae-missing-release-tag) "withLogCollector" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-// Warning: (ae-missing-release-tag) "withLogCollector" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-// Warning: (ae-missing-release-tag) "withLogCollector" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-// Warning: (ae-missing-release-tag) "withLogCollector" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
-export function withLogCollector(
- callback: AsyncLogCollector,
-): Promise>;
-
-// @public (undocumented)
-export function withLogCollector(
- callback: SyncLogCollector,
-): CollectedLogs;
-
-// @public (undocumented)
-export function withLogCollector(
- logsToCollect: T[],
- callback: AsyncLogCollector,
-): Promise>;
-
-// @public (undocumented)
-export function withLogCollector(
- logsToCollect: T[],
- callback: SyncLogCollector,
-): CollectedLogs;
+// (No @packageDocumentation comment for this package)
```
diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json
index 474d62a670..8256714560 100644
--- a/packages/test-utils-core/package.json
+++ b/packages/test-utils-core/package.json
@@ -23,18 +23,12 @@
"scripts": {
"build": "backstage-cli build --outputs types,esm",
"lint": "backstage-cli lint",
- "test": "backstage-cli test",
+ "test": "backstage-cli test --passWithNoTests",
"prepack": "backstage-cli prepack",
"postpack": "backstage-cli postpack",
"clean": "backstage-cli clean"
},
- "dependencies": {
- "@testing-library/jest-dom": "^5.10.1",
- "@testing-library/react": "^11.2.5",
- "@types/react": "*",
- "react": "^16.12.0",
- "react-dom": "^16.12.0"
- },
+ "dependencies": {},
"devDependencies": {
"@types/jest": "^26.0.7",
"@types/node": "^14.14.32"
diff --git a/packages/test-utils-core/src/index.ts b/packages/test-utils-core/src/index.ts
index 5658523c11..8f0a5b31e1 100644
--- a/packages/test-utils-core/src/index.ts
+++ b/packages/test-utils-core/src/index.ts
@@ -14,10 +14,7 @@
* limitations under the License.
*/
-/**
- * Utilities to test Backstage core
- *
- * @packageDocumentation
- */
-
-export * from './testUtils';
+export {};
+throw new Error(
+ 'This module has been removed. Use @backstage/dev-utils instead',
+);
diff --git a/packages/test-utils-core/src/testUtils/index.tsx b/packages/test-utils-core/src/testUtils/index.tsx
deleted file mode 100644
index 6f6aa43ea9..0000000000
--- a/packages/test-utils-core/src/testUtils/index.tsx
+++ /dev/null
@@ -1,19 +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.
- */
-
-export * from './Keyboard';
-export * from './logCollector';
-export * from './testingLibrary';
diff --git a/packages/test-utils/api-report.md b/packages/test-utils/api-report.md
index f4b3fe12bb..c4936f6d96 100644
--- a/packages/test-utils/api-report.md
+++ b/packages/test-utils/api-report.md
@@ -9,7 +9,7 @@ import { ComponentType } from 'react';
import { ErrorApi } from '@backstage/core-plugin-api';
import { ErrorContext } from '@backstage/core-plugin-api';
import { ExternalRouteRef } from '@backstage/core-plugin-api';
-import { Observable } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { ReactElement } from 'react';
import { ReactNode } from 'react';
import { RenderResult } from '@testing-library/react';
@@ -17,9 +17,73 @@ import { RouteRef } from '@backstage/core-plugin-api';
import { StorageApi } from '@backstage/core-plugin-api';
import { StorageValueChange } from '@backstage/core-plugin-api';
-// Warning: (ae-missing-release-tag) "MockAnalyticsApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
+// @public
+export type AsyncLogCollector = () => Promise;
+
+// @public
+export type CollectedLogs = {
+ [key in T]: string[];
+};
+
+// @public
+export type ErrorWithContext = {
+ error: Error;
+ context?: ErrorContext;
+};
+
+// @public @deprecated (undocumented)
+export class Keyboard {
+ constructor(
+ target: any,
+ {
+ debug,
+ }?: {
+ debug?: boolean | undefined;
+ },
+ );
+ // (undocumented)
+ click(): Promise;
+ // (undocumented)
+ debug: boolean;
+ // (undocumented)
+ document: any;
+ // (undocumented)
+ enter(value: any): Promise;
+ // (undocumented)
+ escape(): Promise;
+ // (undocumented)
+ get focused(): any;
+ // (undocumented)
+ static fromReadableInput(input: any): any;
+ // (undocumented)
+ _log(message: any, ...args: any[]): void;
+ // (undocumented)
+ _pretty(element: any): string;
+ // (undocumented)
+ send(chars: any): Promise;
+ // (undocumented)
+ _sendKey(key: any, charCode: any, action: any): Promise;
+ // (undocumented)
+ tab(): Promise;
+ // (undocumented)
+ static toReadableInput(chars: any): any;
+ // (undocumented)
+ toString(): string;
+ // (undocumented)
+ static type(target: any, input: any): Promise;
+ // (undocumented)
+ type(input: any): Promise;
+ // (undocumented)
+ static typeDebug(target: any, input: any): Promise;
+}
+
+// @public
+export type LogCollector = AsyncLogCollector | SyncLogCollector;
+
+// @public
+export type LogFuncs = 'log' | 'warn' | 'error';
+
+// @public
export class MockAnalyticsApi implements AnalyticsApi {
// (undocumented)
captureEvent({
@@ -33,9 +97,6 @@ export class MockAnalyticsApi implements AnalyticsApi {
getEvents(): AnalyticsEvent[];
}
-// Warning: (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen
-// Warning: (ae-missing-release-tag) "mockBreakpoint" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
// @public
export function mockBreakpoint({
matches,
@@ -43,19 +104,14 @@ export function mockBreakpoint({
matches?: boolean | undefined;
}): void;
-// Warning: (ae-missing-release-tag) "MockErrorApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
+// @public
export class MockErrorApi implements ErrorApi {
- // Warning: (ae-forgotten-export) The symbol "Options" needs to be exported by the entry point index.d.ts
- constructor(options?: Options);
+ constructor(options?: MockErrorApiOptions);
// (undocumented)
error$(): Observable<{
error: Error;
context?: ErrorContext;
}>;
- // Warning: (ae-forgotten-export) The symbol "ErrorWithContext" needs to be exported by the entry point index.d.ts
- //
// (undocumented)
getErrors(): ErrorWithContext[];
// (undocumented)
@@ -64,9 +120,12 @@ export class MockErrorApi implements ErrorApi {
waitForError(pattern: RegExp, timeoutMs?: number): Promise;
}
-// Warning: (ae-missing-release-tag) "MockStorageApi" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
+// @public
+export type MockErrorApiOptions = {
+ collect?: boolean;
+};
+
+// @public
export class MockStorageApi implements StorageApi {
// (undocumented)
static create(data?: MockStorageBucket): MockStorageApi;
@@ -82,16 +141,12 @@ export class MockStorageApi implements StorageApi {
set(key: string, data: T): Promise;
}
-// Warning: (ae-missing-release-tag) "MockStorageBucket" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
+// @public
export type MockStorageBucket = {
[key: string]: any;
};
-// Warning: (ae-missing-release-tag) "msw" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
-// @public (undocumented)
+// @public @deprecated (undocumented)
export const msw: {
setupDefaultHandlers: (worker: {
listen: (t: any) => void;
@@ -100,22 +155,58 @@ export const msw: {
}) => void;
};
-// Warning: (ae-forgotten-export) The symbol "TestAppOptions" needs to be exported by the entry point index.d.ts
-// Warning: (ae-missing-release-tag) "renderInTestApp" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
// @public
export function renderInTestApp(
Component: ComponentType | ReactNode,
options?: TestAppOptions,
): Promise;
-// Warning: (ae-missing-release-tag) "wrapInTestApp" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
-//
+// @public
+export function renderWithEffects(nodes: ReactElement): Promise;
+
+// @public
+export function setupRequestMockHandlers(worker: {
+ listen: (t: any) => void;
+ close: () => void;
+ resetHandlers: () => void;
+}): void;
+
+// @public
+export type SyncLogCollector = () => void;
+
+// @public
+export type TestAppOptions = {
+ routeEntries?: string[];
+ mountedRoutes?: {
+ [path: string]: RouteRef | ExternalRouteRef;
+ };
+};
+
+// @public
+export function withLogCollector(
+ callback: AsyncLogCollector,
+): Promise>;
+
+// @public
+export function withLogCollector(
+ callback: SyncLogCollector,
+): CollectedLogs;
+
+// @public
+export function withLogCollector(
+ logsToCollect: T[],
+ callback: AsyncLogCollector,
+): Promise>;
+
+// @public
+export function withLogCollector(
+ logsToCollect: T[],
+ callback: SyncLogCollector,
+): CollectedLogs;
+
// @public
export function wrapInTestApp(
Component: ComponentType | ReactNode,
options?: TestAppOptions,
): ReactElement;
-
-export * from '@backstage/test-utils-core';
```
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index 22dc950bd4..624fdf28b3 100644
--- a/packages/test-utils/package.json
+++ b/packages/test-utils/package.json
@@ -31,8 +31,8 @@
"dependencies": {
"@backstage/core-app-api": "^0.1.18",
"@backstage/core-plugin-api": "^0.1.11",
- "@backstage/test-utils-core": "^0.1.3",
"@backstage/theme": "^0.2.11",
+ "@backstage/types": "^0.1.1",
"@material-ui/core": "^4.12.2",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
diff --git a/packages/test-utils/src/index.ts b/packages/test-utils/src/index.ts
index 545a5b9961..83a955baa7 100644
--- a/packages/test-utils/src/index.ts
+++ b/packages/test-utils/src/index.ts
@@ -21,4 +21,3 @@
*/
export * from './testUtils';
-export * from '@backstage/test-utils-core';
diff --git a/packages/test-utils-core/src/testUtils/Keyboard.js b/packages/test-utils/src/testUtils/Keyboard.js
similarity index 98%
rename from packages/test-utils-core/src/testUtils/Keyboard.js
rename to packages/test-utils/src/testUtils/Keyboard.js
index 3f45ca90f6..3f4724d562 100644
--- a/packages/test-utils-core/src/testUtils/Keyboard.js
+++ b/packages/test-utils/src/testUtils/Keyboard.js
@@ -23,6 +23,10 @@ const codes = {
Esc: 27,
};
+/**
+ * @public
+ * @deprecated because it has no usages. Perhaps resurfaced in the future when need be.
+ */
export class Keyboard {
static async type(target, input) {
await new Keyboard(target).type(input);
diff --git a/packages/test-utils-core/src/testUtils/Keyboard.test.js b/packages/test-utils/src/testUtils/Keyboard.test.js
similarity index 100%
rename from packages/test-utils-core/src/testUtils/Keyboard.test.js
rename to packages/test-utils/src/testUtils/Keyboard.test.js
diff --git a/packages/test-utils/src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.ts b/packages/test-utils/src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.ts
index 28585145ad..3e2fc2a01c 100644
--- a/packages/test-utils/src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.ts
+++ b/packages/test-utils/src/testUtils/apis/AnalyticsApi/MockAnalyticsApi.ts
@@ -16,6 +16,11 @@
import { AnalyticsApi, AnalyticsEvent } from '@backstage/core-plugin-api';
+/**
+ * Mock implementation of {@link core-plugin-api#AnalyticsApi} with helpers to ensure that events are sent correctly.
+ * Use getEvents in tests to verify captured events.
+ * @public
+ */
export class MockAnalyticsApi implements AnalyticsApi {
private events: AnalyticsEvent[] = [];
diff --git a/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts b/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts
index 20b6bd1724..87601e29e2 100644
--- a/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts
+++ b/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts
@@ -14,13 +14,23 @@
* limitations under the License.
*/
-import { ErrorApi, ErrorContext, Observable } from '@backstage/core-plugin-api';
+import { ErrorApi, ErrorContext } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
-type Options = {
+/**
+ * Constructor arguments for {@link MockErrorApi}
+ * @public
+ */
+export type MockErrorApiOptions = {
+ // Need to be true if getErrors is used in testing.
collect?: boolean;
};
-type ErrorWithContext = {
+/**
+ * ErrorWithContext contains error and ErrorContext
+ * @public
+ */
+export type ErrorWithContext = {
error: Error;
context?: ErrorContext;
};
@@ -38,11 +48,16 @@ const nullObservable = {
},
};
+/**
+ * Mock implementation of the {@link core-plugin-api#ErrorApi} to be used in tests.
+ * Incudes withForError and getErrors methods for error testing.
+ * @public
+ */
export class MockErrorApi implements ErrorApi {
private readonly errors = new Array();
private readonly waiters = new Set();
- constructor(private readonly options: Options = {}) {}
+ constructor(private readonly options: MockErrorApiOptions = {}) {}
post(error: Error, context?: ErrorContext) {
if (this.options.collect) {
diff --git a/packages/test-utils/src/testUtils/apis/ErrorApi/index.ts b/packages/test-utils/src/testUtils/apis/ErrorApi/index.ts
index 8a6b5031e5..1273a91531 100644
--- a/packages/test-utils/src/testUtils/apis/ErrorApi/index.ts
+++ b/packages/test-utils/src/testUtils/apis/ErrorApi/index.ts
@@ -15,3 +15,4 @@
*/
export { MockErrorApi } from './MockErrorApi';
+export type { MockErrorApiOptions, ErrorWithContext } from './MockErrorApi';
diff --git a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts
index ab5d26afc5..6378a77474 100644
--- a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts
+++ b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts
@@ -14,15 +14,20 @@
* limitations under the License.
*/
-import {
- Observable,
- StorageApi,
- StorageValueChange,
-} from '@backstage/core-plugin-api';
+import { StorageApi, StorageValueChange } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import ObservableImpl from 'zen-observable';
+/**
+ * Type for map holding data in {@link MockStorageApi}
+ * @public
+ */
export type MockStorageBucket = { [key: string]: any };
+/**
+ * Mock implementation of the {@link core-plugin-api#StorageApi} to be used in tests
+ * @public
+ */
export class MockStorageApi implements StorageApi {
private readonly namespace: string;
private readonly data: MockStorageBucket;
diff --git a/packages/test-utils/src/testUtils/appWrappers.test.tsx b/packages/test-utils/src/testUtils/appWrappers.test.tsx
index a5a60130f2..25809912ec 100644
--- a/packages/test-utils/src/testUtils/appWrappers.test.tsx
+++ b/packages/test-utils/src/testUtils/appWrappers.test.tsx
@@ -23,7 +23,7 @@ import {
useApi,
useRouteRef,
} from '@backstage/core-plugin-api';
-import { withLogCollector } from '@backstage/test-utils-core';
+import { withLogCollector } from './logCollector';
import { render } from '@testing-library/react';
import React, { useEffect } from 'react';
import { Route, Routes } from 'react-router';
diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx
index 09c9e5d0bf..6f55cc6190 100644
--- a/packages/test-utils/src/testUtils/appWrappers.tsx
+++ b/packages/test-utils/src/testUtils/appWrappers.tsx
@@ -27,7 +27,7 @@ import {
createRouteRef,
} from '@backstage/core-plugin-api';
import { RenderResult } from '@testing-library/react';
-import { renderWithEffects } from '@backstage/test-utils-core';
+import { renderWithEffects } from './testingLibrary';
import { mockApis } from './mockApis';
const ErrorBoundaryFallback = ({ error }: { error: Error }) => {
@@ -43,8 +43,9 @@ const Progress = () => ;
/**
* Options to customize the behavior of the test app wrapper.
+ * @public
*/
-type TestAppOptions = {
+export type TestAppOptions = {
/**
* Initial route entries to pass along as `initialEntries` to the router.
*/
@@ -56,11 +57,11 @@ type TestAppOptions = {
* used by `useRouteRef` in the rendered elements.
*
* @example
- * wrapInTestApp(, {
- * mountedRoutes: {
+ * wrapInTestApp(, \{
+ * mountedRoutes: \{
* '/my-path': myRouteRef,
- * }
- * })
+ * \}
+ * \})
* // ...
* const link = useRouteRef(myRouteRef)
*/
@@ -80,6 +81,7 @@ function isExternalRouteRef(
*
* @param Component - A component or react node to render inside the test app.
* @param options - Additional options for the rendering.
+ * @public
*/
export function wrapInTestApp(
Component: ComponentType | ReactNode,
@@ -171,6 +173,7 @@ export function wrapInTestApp(
*
* @param Component - A component or react node to render inside the test app.
* @param options - Additional options for the rendering.
+ * @public
*/
export async function renderInTestApp(
Component: ComponentType | ReactNode,
diff --git a/packages/test-utils/src/testUtils/index.tsx b/packages/test-utils/src/testUtils/index.tsx
index 859315313d..7d93d606cc 100644
--- a/packages/test-utils/src/testUtils/index.tsx
+++ b/packages/test-utils/src/testUtils/index.tsx
@@ -17,4 +17,8 @@
export * from './apis';
export { default as mockBreakpoint } from './mockBreakpoint';
export { wrapInTestApp, renderInTestApp } from './appWrappers';
+export type { TestAppOptions } from './appWrappers';
export * from './msw';
+export * from './Keyboard';
+export * from './logCollector';
+export * from './testingLibrary';
diff --git a/packages/test-utils-core/src/testUtils/logCollector.test.ts b/packages/test-utils/src/testUtils/logCollector.test.ts
similarity index 100%
rename from packages/test-utils-core/src/testUtils/logCollector.test.ts
rename to packages/test-utils/src/testUtils/logCollector.test.ts
diff --git a/packages/test-utils-core/src/testUtils/logCollector.ts b/packages/test-utils/src/testUtils/logCollector.ts
similarity index 76%
rename from packages/test-utils-core/src/testUtils/logCollector.ts
rename to packages/test-utils/src/testUtils/logCollector.ts
index 3180010529..25629f2c13 100644
--- a/packages/test-utils-core/src/testUtils/logCollector.ts
+++ b/packages/test-utils/src/testUtils/logCollector.ts
@@ -16,36 +16,65 @@
/* eslint-disable no-console */
+/**
+ * Severity levels of {@link CollectedLogs}
+ * @public */
export type LogFuncs = 'log' | 'warn' | 'error';
+/**
+ * AsyncLogCollector type used in {@link (withLogCollector:1)} callback function.
+ * @public */
export type AsyncLogCollector = () => Promise;
+/**
+ * SyncLogCollector type used in {@link (withLogCollector:2)} callback function.
+ * @public */
export type SyncLogCollector = () => void;
+/**
+ * Union type used in {@link (withLogCollector:3)} callback function.
+ * @public */
export type LogCollector = AsyncLogCollector | SyncLogCollector;
+/**
+ * Map of severity level and corresponding log lines.
+ * @public */
export type CollectedLogs = { [key in T]: string[] };
const allCategories = ['log', 'warn', 'error'];
-// Asynchronous log collector with that collects all categories
+/**
+ * Asynchronous log collector with that collects all categories
+ * @public */
export function withLogCollector(
callback: AsyncLogCollector,
): Promise>;
-// Synchronous log collector with that collects all categories
+/**
+ * Synchronous log collector with that collects all categories
+ * @public */
export function withLogCollector(
callback: SyncLogCollector,
): CollectedLogs;
-// Asynchronous log collector with that only collects selected categories
+/**
+ * Asynchronous log collector with that only collects selected categories
+ * @public
+ */
export function withLogCollector(
logsToCollect: T[],
callback: AsyncLogCollector,
): Promise>;
-// Synchronous log collector with that only collects selected categories
+/**
+ * Synchronous log collector with that only collects selected categories
+ * @public */
export function withLogCollector(
logsToCollect: T[],
callback: SyncLogCollector,
): CollectedLogs;
+/**
+ * Log collector that collect logs either from a sync or async collector.
+ * @public
+ * @deprecated import from test-utils instead
+ * */
export function withLogCollector(
logsToCollect: LogFuncs[] | LogCollector,
callback?: LogCollector,
diff --git a/packages/test-utils/src/testUtils/mockBreakpoint.ts b/packages/test-utils/src/testUtils/mockBreakpoint.ts
index 3335284f88..3bc285a81d 100644
--- a/packages/test-utils/src/testUtils/mockBreakpoint.ts
+++ b/packages/test-utils/src/testUtils/mockBreakpoint.ts
@@ -24,7 +24,7 @@
* If there are any updates from MUI React on testing `useMediaQuery` this mock should be replaced
* https://material-ui.com/components/use-media-query/#testing
*
- * @param matchMediaOptions
+ * @public
*/
export default function mockBreakpoint({ matches = false }) {
Object.defineProperty(window, 'matchMedia', {
diff --git a/packages/test-utils/src/testUtils/msw/index.ts b/packages/test-utils/src/testUtils/msw/index.ts
index 337c2999fc..40bc47e8f0 100644
--- a/packages/test-utils/src/testUtils/msw/index.ts
+++ b/packages/test-utils/src/testUtils/msw/index.ts
@@ -14,14 +14,31 @@
* limitations under the License.
*/
+/**
+ * @deprecated use {@link setupRequestMockHandlers} instead which can be called directly with the worker.
+ * @public
+ */
export const msw = {
setupDefaultHandlers: (worker: {
listen: (t: any) => void;
close: () => void;
resetHandlers: () => void;
}) => {
- beforeAll(() => worker.listen({ onUnhandledRequest: 'error' }));
- afterAll(() => worker.close());
- afterEach(() => worker.resetHandlers());
+ setupRequestMockHandlers(worker);
},
};
+
+/**
+ * Sets up handlers for request mocking
+ * @public
+ * @param worker - service worker
+ */
+export function setupRequestMockHandlers(worker: {
+ listen: (t: any) => void;
+ close: () => void;
+ resetHandlers: () => void;
+}) {
+ beforeAll(() => worker.listen({ onUnhandledRequest: 'error' }));
+ afterAll(() => worker.close());
+ afterEach(() => worker.resetHandlers());
+}
diff --git a/packages/test-utils-core/src/testUtils/testingLibrary.ts b/packages/test-utils/src/testUtils/testingLibrary.ts
similarity index 71%
rename from packages/test-utils-core/src/testUtils/testingLibrary.ts
rename to packages/test-utils/src/testUtils/testingLibrary.ts
index 48e5132825..44ea20a4cc 100644
--- a/packages/test-utils-core/src/testUtils/testingLibrary.ts
+++ b/packages/test-utils/src/testUtils/testingLibrary.ts
@@ -18,11 +18,18 @@ import { ReactElement } from 'react';
import { act } from 'react-dom/test-utils';
import { render, RenderResult } from '@testing-library/react';
-// Components using useEffect to perform an asynchronous action (such as fetch) must be rendered within an async
-// act call to properly get the final state, even with mocked responses. This utility method makes the signature a bit
-// cleaner, since act doesn't return the result of the evaluated function.
-// https://github.com/testing-library/react-testing-library/issues/281
-// https://github.com/facebook/react/pull/14853
+/**
+ * @public
+ * Simplifies rendering of async components in by taking care of the wrapping inside act
+ *
+ * @remarks
+ *
+ * Components using useEffect to perform an asynchronous action (such as fetch) must be rendered within an async
+ * act call to properly get the final state, even with mocked responses. This utility method makes the signature a bit
+ * cleaner, since act doesn't return the result of the evaluated function.
+ * https://github.com/testing-library/react-testing-library/issues/281
+ * https://github.com/facebook/react/pull/14853
+ */
export async function renderWithEffects(
nodes: ReactElement,
): Promise {
diff --git a/packages/types/package.json b/packages/types/package.json
index 9da8cd5e95..8632422a25 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.0",
+ "version": "0.1.1",
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json
index 6047b5aeb1..3483bfa8bd 100644
--- a/plugins/app-backend/package.json
+++ b/plugins/app-backend/package.json
@@ -33,6 +33,7 @@
"@backstage/backend-common": "^0.9.7",
"@backstage/config-loader": "^0.7.0",
"@backstage/config": "^0.1.8",
+ "@backstage/types": "^0.1.1",
"@types/express": "^4.17.6",
"express": "^4.17.1",
"express-promise-router": "^4.1.0",
@@ -42,6 +43,7 @@
},
"devDependencies": {
"@backstage/cli": "^0.8.0",
+ "@backstage/types": "^0.1.1",
"@types/supertest": "^2.0.8",
"msw": "^0.29.0",
"supertest": "^6.1.3"
diff --git a/plugins/app-backend/src/lib/config.ts b/plugins/app-backend/src/lib/config.ts
index 1ebd95a5c0..c37dc34dcc 100644
--- a/plugins/app-backend/src/lib/config.ts
+++ b/plugins/app-backend/src/lib/config.ts
@@ -17,7 +17,8 @@
import fs from 'fs-extra';
import { resolve as resolvePath } from 'path';
import { Logger } from 'winston';
-import { AppConfig, Config, JsonObject } from '@backstage/config';
+import { AppConfig, Config } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { loadConfigSchema, readEnvConfig } from '@backstage/config-loader';
type InjectOptions = {
diff --git a/plugins/azure-devops-backend/api-report.md b/plugins/azure-devops-backend/api-report.md
index 4b49872161..fac6261a39 100644
--- a/plugins/azure-devops-backend/api-report.md
+++ b/plugins/azure-devops-backend/api-report.md
@@ -81,7 +81,10 @@ export type RepoBuild = {
status?: BuildStatus;
result?: BuildResult;
queueTime?: Date;
+ startTime?: Date;
+ finishTime?: Date;
source: string;
+ uniqueName?: string;
};
// Warning: (ae-missing-release-tag) "RouterOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts
index fd9aa169d5..fdd49c0440 100644
--- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts
+++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.test.ts
@@ -13,19 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { mappedPullRequest, mappedRepoBuild } from './AzureDevOpsApi';
-import { PullRequest, RepoBuild } from './types';
+
import {
Build,
BuildResult,
BuildStatus,
- DefinitionReference,
-} from 'azure-devops-node-api/interfaces/BuildInterfaces';
-import {
GitPullRequest,
+ GitRepository,
+ PullRequest,
PullRequestStatus,
-} from 'azure-devops-node-api/interfaces/GitInterfaces';
-import { GitRepository } from 'azure-devops-node-api/interfaces/TfvcInterfaces';
+ RepoBuild,
+} from './types';
+import { mappedPullRequest, mappedRepoBuild } from './AzureDevOpsApi';
+
+import { DefinitionReference } from 'azure-devops-node-api/interfaces/BuildInterfaces';
import { IdentityRef } from 'azure-devops-node-api/interfaces/common/VSSInterfaces';
describe('AzureDevOpsApi', () => {
@@ -41,16 +42,24 @@ describe('AzureDevOpsApi', () => {
},
};
+ const inputIdentityRef: IdentityRef = {
+ displayName: 'Jane Doe',
+ uniqueName: 'DOMAINjdoe',
+ };
+
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: inputBuildDefinition,
_links: inputLinks,
+ requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
@@ -60,7 +69,10 @@ describe('AzureDevOpsApi', () => {
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
source: 'refs/heads/develop (f4f78b31)',
+ uniqueName: 'DOMAINjdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
@@ -75,16 +87,24 @@ describe('AzureDevOpsApi', () => {
},
};
+ const inputIdentityRef: IdentityRef = {
+ displayName: 'Jane Doe',
+ uniqueName: 'DOMAINjdoe',
+ };
+
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
+ requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
@@ -94,7 +114,10 @@ describe('AzureDevOpsApi', () => {
status: BuildStatus.Completed,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
source: 'refs/heads/develop (f4f78b31)',
+ uniqueName: 'DOMAINjdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
@@ -109,16 +132,24 @@ describe('AzureDevOpsApi', () => {
},
};
+ const inputIdentityRef: IdentityRef = {
+ displayName: 'Jane Doe',
+ uniqueName: 'DOMAINjdoe',
+ };
+
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: undefined,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
+ requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
@@ -128,7 +159,10 @@ describe('AzureDevOpsApi', () => {
status: BuildStatus.None,
result: BuildResult.Succeeded,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
source: 'refs/heads/develop (f4f78b31)',
+ uniqueName: 'DOMAINjdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
@@ -143,16 +177,24 @@ describe('AzureDevOpsApi', () => {
},
};
+ const inputIdentityRef: IdentityRef = {
+ displayName: 'Jane Doe',
+ uniqueName: 'DOMAINjdoe',
+ };
+
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.InProgress,
result: undefined,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: inputLinks,
+ requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
@@ -162,7 +204,10 @@ describe('AzureDevOpsApi', () => {
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
source: 'refs/heads/develop (f4f78b31)',
+ uniqueName: 'DOMAINjdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
@@ -171,16 +216,24 @@ describe('AzureDevOpsApi', () => {
describe('mappedRepoBuild with undefined link', () => {
it('should return empty string for link', () => {
+ const inputIdentityRef: IdentityRef = {
+ displayName: 'Jane Doe',
+ uniqueName: 'DOMAINjdoe',
+ };
+
const inputBuild: Build = {
id: 1,
buildNumber: 'Build-1',
status: BuildStatus.InProgress,
result: undefined,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
sourceBranch: 'refs/heads/develop',
sourceVersion: 'f4f78b3100b2923982bdf60c89c57ce6fd2d9a1c',
definition: undefined,
_links: undefined,
+ requestedFor: inputIdentityRef,
};
const outputRepoBuild: RepoBuild = {
@@ -190,7 +243,10 @@ describe('AzureDevOpsApi', () => {
status: BuildStatus.InProgress,
result: BuildResult.None,
queueTime: new Date('2020-09-12T06:10:23.9325232Z'),
+ startTime: new Date('2020-09-12T06:15:23.9325232Z'),
+ finishTime: new Date('2020-09-12T06:20:23.9325232Z'),
source: 'refs/heads/develop (f4f78b31)',
+ uniqueName: 'DOMAINjdoe',
};
expect(mappedRepoBuild(inputBuild)).toEqual(outputRepoBuild);
diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts
index 1a80a3d42b..26378496ea 100644
--- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts
+++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts
@@ -111,7 +111,7 @@ export class AzureDevOpsApi {
options: PullRequestOptions,
): Promise {
this.logger?.debug(
- `Calling Azure DevOps REST API, getting up to ${top} Pull Requests for Repository ${repoName} for Project ${projectName}`,
+ `Calling Azure DevOps REST API, getting up to ${options.top} Pull Requests for Repository ${repoName} for Project ${projectName}`,
);
const gitRepository = await this.getGitRepository(projectName, repoName);
@@ -144,11 +144,14 @@ export function mappedRepoBuild(build: Build): RepoBuild {
title: [build.definition?.name, build.buildNumber]
.filter(Boolean)
.join(' - '),
- link: build._links?.web.href ? build._links?.web.href : '',
- status: build.status ? build.status : BuildStatus.None,
- result: build.result ? build.result : BuildResult.None,
+ link: build._links?.web.href ?? '',
+ status: build.status ?? BuildStatus.None,
+ result: build.result ?? BuildResult.None,
queueTime: build.queueTime,
+ startTime: build.startTime,
+ finishTime: build.finishTime,
source: `${build.sourceBranch} (${build.sourceVersion?.substr(0, 8)})`,
+ uniqueName: build.requestedFor?.uniqueName ?? 'N/A',
};
}
@@ -160,8 +163,8 @@ export function mappedPullRequest(
pullRequestId: pullRequest.pullRequestId,
repoName: pullRequest.repository?.name,
title: pullRequest.title,
- uniqueName: pullRequest.createdBy?.uniqueName,
- createdBy: pullRequest.createdBy?.displayName,
+ uniqueName: pullRequest.createdBy?.uniqueName ?? 'N/A',
+ createdBy: pullRequest.createdBy?.displayName ?? 'N/A',
creationDate: pullRequest.creationDate,
sourceRefName: pullRequest.sourceRefName,
targetRefName: pullRequest.targetRefName,
diff --git a/plugins/azure-devops-backend/src/api/types.ts b/plugins/azure-devops-backend/src/api/types.ts
index 0fd30243af..fab61d26c9 100644
--- a/plugins/azure-devops-backend/src/api/types.ts
+++ b/plugins/azure-devops-backend/src/api/types.ts
@@ -41,7 +41,10 @@ export type RepoBuild = {
status?: BuildStatus;
result?: BuildResult;
queueTime?: Date;
+ startTime?: Date;
+ finishTime?: Date;
source: string;
+ uniqueName?: string;
};
export type PullRequest = {
diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md
index edf26ecfb3..47a0c4debd 100644
--- a/plugins/azure-devops/README.md
+++ b/plugins/azure-devops/README.md
@@ -2,74 +2,50 @@
Website: [https://dev.azure.com/](https://dev.azure.com/)
-
+## Features
+
+### Azure Pipelines
+
+Lists the top _n_ builds for a given repository where _n_ is a configurable value
+
+
## Setup
The following sections will help you get the Azure DevOps plugin setup and running
-### Configuration
+### Azure DevOps Backend
-The Azure DevOps plugin requires the following YAML to be added to your app-config.yaml:
+You need to setup the [Azure DevOps backend plugin](https://github.com/backstage/backstage/tree/master/plugins/azure-devops-backend) before you move forward with any of these steps if you haven't already
+
+### Entity Annotation
+
+To be able to use the Azure DevOps plugin you need to add the following annotation to any entities you want to use it with:
```yaml
-azureDevOps:
- host: dev.azure.com
- token: ${AZURE_TOKEN}
- organization: my-company
+dev.azure.com/project-repo: /
```
-Configuration Details:
+Let's break this down a little: `` will be the name of your Team Project and `` will be the name of your repository which needs to be part of the Team Project you entered for ``.
-- `host` and `token` can be the same as the ones used for the `integration` section
-- `AZURE_TOKEN` environment variable must be set to a [Personal Access Token](https://docs.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page) with read access to both Code and Build
-- `organization` is your Azure DevOps Organization name or for Azure DevOps Server (on-premise) this will be your Collection name
+Here's what that will look like in action:
-### Backend
+```yaml
+# Example catalog-info.yaml entity definition file
+apiVersion: backstage.io/v1alpha1
+kind: Component
+metadata:
+ # ...
+ annotations:
+ dev.azure.com/project-repo: my-project/my-repo
+spec:
+ type: service
+ # ...
+```
-Here's how to get the backend up and running:
+### Azure Pipelines Component
-1. First we need to add the `@backstage/plugin-azure-devops-backend` package to your backend:
-
- ```sh
- # From the Backstage root directory
- cd packages/backend
- yarn add @backstage/plugin-azure-devops-backend
- ```
-
-2. Then we will create a new file named `packages/backend/src/plugins/azure-devops.ts`, and add the
- following to it:
-
- ```ts
- import { createRouter } from '@backstage/plugin-azure-devops-backend';
- import { Router } from 'express';
- import type { PluginEnvironment } from '../types';
-
- export default function createPlugin({
- logger,
- config,
- }: PluginEnvironment): Promise {
- return createRouter({ logger, config });
- }
- ```
-
-3. Next we wire this into the overall backend router, edit `packages/backend/src/index.ts`:
-
- ```ts
- import azureDevOps from './plugins/azuredevops';
- // ...
- async function main() {
- // ...
- const azureDevOpsEnv = useHotMemoize(module, () => createEnv('azure-devops'));
- apiRouter.use('/azure-devops', await azureDevOps(azureDevOpsEnv));
- ```
-
-4. Now run `yarn start-backend` from the repo root
-5. Finally open `http://localhost:7000/api/azure-devops/health` in a browser and it should return `{"status":"ok"}`
-
-### Frontend
-
-To get the frontend working you'll need to do the following two steps:
+To get the Azure Pipelines component working you'll need to do the following two steps:
1. First we need to add the @backstage/plugin-azure-devops package to your frontend app:
@@ -101,35 +77,7 @@ To get the frontend working you'll need to do the following two steps:
```
-### Entity Annotation
-
-You need to add the following annotation to any entities you want to be able to use the Azure Devops plugin with:
-
-```yaml
-dev.azure.com/project-repo: /
-```
-
-Let's break this down a little: `` will be the name of your Team Project and `` will be the name of your repository which needs to be part of the Team Project you entered for ``.
-
-Here's what that will look like in action:
-
-```yaml
-# Example catalog-info.yaml entity definition file
-apiVersion: backstage.io/v1alpha1
-kind: Component
-metadata:
- # ...
- annotations:
- dev.azure.com/project-repo: my-project/my-repo
-spec:
- type: service
- # ...
-```
-
-## Features
-
-- Lists the top _n_ builds for a given repository where _n_ is the value configured for `top`
-
## Limitations
-- Currently multiple organizations is not supported
+- Currently multiple organizations are not supported
+- Mixing Azure DevOps Services (cloud) and Azure DevOps Server (on-premise) is not supported
diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.test.ts b/plugins/azure-devops/src/components/BuildTable/BuildTable.test.ts
new file mode 100644
index 0000000000..42408f37b3
--- /dev/null
+++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.test.ts
@@ -0,0 +1,241 @@
+/*
+ * 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 {
+ BuildResult,
+ BuildStatus,
+} from 'azure-devops-node-api/interfaces/BuildInterfaces';
+import { getBuildResultComponent, getBuildStateComponent } from './BuildTable';
+
+import { renderInTestApp } from '@backstage/test-utils';
+
+describe('getBuildResultComponent', () => {
+ describe('getBuildResultComponent with Succeeded result', () => {
+ it('should return Status ok Succeeded', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildResultComponent(BuildResult.Succeeded),
+ );
+
+ expect(getByLabelText('Status ok')).toBeInTheDocument();
+ expect(getByText('Succeeded')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildResultComponent with Partially Succeeded result', () => {
+ it('should return Status warning Partially Succeeded', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildResultComponent(BuildResult.PartiallySucceeded),
+ );
+
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+ expect(getByText('Partially Succeeded')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildResultComponent with Failed result', () => {
+ it('should return Status error Failed', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildResultComponent(BuildResult.Failed),
+ );
+
+ expect(getByLabelText('Status error')).toBeInTheDocument();
+ expect(getByText('Failed')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildResultComponent with Canceled result', () => {
+ it('should return Status aborted Canceled', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildResultComponent(BuildResult.Canceled),
+ );
+
+ expect(getByLabelText('Status aborted')).toBeInTheDocument();
+ expect(getByText('Canceled')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildResultComponent with None result', () => {
+ it('should return Status warning Unknown', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildResultComponent(BuildResult.None),
+ );
+
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+ expect(getByText('Unknown')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildResultComponent with undefined result', () => {
+ it('should return Status warning Unknown', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildResultComponent(undefined),
+ );
+
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+ expect(getByText('Unknown')).toBeInTheDocument();
+ });
+ });
+});
+
+describe('getBuildStateComponent', () => {
+ describe('getBuildStateComponent with In Progress status and undefined result', () => {
+ it('should return Status running In Progress', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.InProgress, undefined),
+ );
+
+ expect(getByLabelText('Status running')).toBeInTheDocument();
+ expect(getByText('In Progress')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with Completed status and Succeeded result', () => {
+ it('should return Status ok Succeeded', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.Completed, BuildResult.Succeeded),
+ );
+
+ expect(getByLabelText('Status ok')).toBeInTheDocument();
+ expect(getByText('Succeeded')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with Completed status and Partially Succeeded result', () => {
+ it('should return Status warning Partially Succeeded', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(
+ BuildStatus.Completed,
+ BuildResult.PartiallySucceeded,
+ ),
+ );
+
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+ expect(getByText('Partially Succeeded')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with Completed status and Failed result', () => {
+ it('should return Status error Failed', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.Completed, BuildResult.Failed),
+ );
+
+ expect(getByLabelText('Status error')).toBeInTheDocument();
+ expect(getByText('Failed')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with Completed status and Canceled result', () => {
+ it('should return Status aborted Canceled', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.Completed, BuildResult.Canceled),
+ );
+
+ expect(getByLabelText('Status aborted')).toBeInTheDocument();
+ expect(getByText('Canceled')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with Completed status and None result', () => {
+ it('should return Status warning Unknown', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.Completed, BuildResult.None),
+ );
+
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+ expect(getByText('Unknown')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with Completed status and undefined result', () => {
+ it('should return Status warning Unknown', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.Completed, undefined),
+ );
+
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+ expect(getByText('Unknown')).toBeInTheDocument();
+ });
+ });
+
+ // TODO: Add remaining Completed iterations
+
+ describe('getBuildStateComponent with Cancelling status and undefined result', () => {
+ it('should return Status aborted Cancelling', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.Cancelling, undefined),
+ );
+
+ expect(getByLabelText('Status aborted')).toBeInTheDocument();
+ expect(getByText('Cancelling')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with Postponed status and undefined result', () => {
+ it('should return Status pending Postponed', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.Postponed, undefined),
+ );
+
+ expect(getByLabelText('Status pending')).toBeInTheDocument();
+ expect(getByText('Postponed')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with Not Started status and undefined result', () => {
+ it('should return Status aborted Not Started', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.NotStarted, undefined),
+ );
+
+ expect(getByLabelText('Status aborted')).toBeInTheDocument();
+ expect(getByText('Not Started')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with None status and undefined result', () => {
+ it('should return Status warning Unknown', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(BuildStatus.None, undefined),
+ );
+
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+ expect(getByText('Unknown')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with undefined and undefined result', () => {
+ it('should return Status warning Unknown', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(undefined, undefined),
+ );
+
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+ expect(getByText('Unknown')).toBeInTheDocument();
+ });
+ });
+
+ describe('getBuildStateComponent with undefined and any valid BuildResult', () => {
+ it('should return Status warning Unknown', async () => {
+ const { getByLabelText, getByText } = await renderInTestApp(
+ getBuildStateComponent(undefined, BuildResult.Succeeded),
+ );
+
+ expect(getByLabelText('Status warning')).toBeInTheDocument();
+ expect(getByText('Unknown')).toBeInTheDocument();
+ });
+ });
+});
diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
index 7516ae52a4..a308afb81b 100644
--- a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
+++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
@@ -36,7 +36,7 @@ import { DateTime } from 'luxon';
import React from 'react';
import { RepoBuild } from '../../api/types';
-const getBuildResultComponent = (result: number | undefined) => {
+export const getBuildResultComponent = (result: number | undefined) => {
switch (result) {
case BuildResult.Succeeded:
return (
@@ -72,7 +72,7 @@ const getBuildResultComponent = (result: number | undefined) => {
}
};
-const getBuildStateComponent = (
+export const getBuildStateComponent = (
status: number | undefined,
result: number | undefined,
) => {
diff --git a/plugins/azure-devops/src/components/Router.tsx b/plugins/azure-devops/src/components/Router.tsx
deleted file mode 100644
index 3000deb5cc..0000000000
--- a/plugins/azure-devops/src/components/Router.tsx
+++ /dev/null
@@ -1,43 +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 { Routes, Route } from 'react-router';
-import { azureDevOpsRouteRef } from '../routes';
-import { EntityPageAzurePipelines } from './EntityPageAzurePipelines';
-import { AZURE_DEVOPS_ANNOTATION } from '../constants';
-import { Entity } from '@backstage/catalog-model';
-import { useEntity } from '@backstage/plugin-catalog-react';
-import { MissingAnnotationEmptyState } from '@backstage/core-components';
-
-export const isAzureDevOpsAvailable = (entity: Entity) =>
- Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_ANNOTATION]);
-
-export const Router = ({ defaultLimit }: { defaultLimit?: number }) => {
- const { entity } = useEntity();
-
- if (!isAzureDevOpsAvailable(entity)) {
- return ;
- }
-
- return (
-
- }
- />
-
- );
-};
diff --git a/plugins/azure-devops/src/index.ts b/plugins/azure-devops/src/index.ts
index 9a90d6ed6a..71d3b654fe 100644
--- a/plugins/azure-devops/src/index.ts
+++ b/plugins/azure-devops/src/index.ts
@@ -13,5 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { azureDevOpsPlugin, EntityAzurePipelinesContent } from './plugin';
-export { isAzureDevOpsAvailable } from './components/Router';
+export {
+ azureDevOpsPlugin,
+ EntityAzurePipelinesContent,
+ isAzureDevOpsAvailable,
+} from './plugin';
diff --git a/plugins/azure-devops/src/plugin.ts b/plugins/azure-devops/src/plugin.ts
index 1cf6e680d0..ab6d816460 100644
--- a/plugins/azure-devops/src/plugin.ts
+++ b/plugins/azure-devops/src/plugin.ts
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import { azureDevOpsApiRef } from './api/AzureDevOpsApi';
-import { AzureDevOpsClient } from './api/AzureDevOpsClient';
import {
createApiFactory,
createPlugin,
@@ -23,8 +21,16 @@ import {
discoveryApiRef,
identityApiRef,
} from '@backstage/core-plugin-api';
+
+import { AZURE_DEVOPS_ANNOTATION } from './constants';
+import { AzureDevOpsClient } from './api/AzureDevOpsClient';
+import { Entity } from '@backstage/catalog-model';
+import { azureDevOpsApiRef } from './api/AzureDevOpsApi';
import { azureDevOpsRouteRef } from './routes';
+export const isAzureDevOpsAvailable = (entity: Entity) =>
+ Boolean(entity.metadata.annotations?.[AZURE_DEVOPS_ANNOTATION]);
+
export const azureDevOpsPlugin = createPlugin({
id: 'azureDevOps',
apis: [
@@ -43,7 +49,10 @@ export const azureDevOpsPlugin = createPlugin({
export const EntityAzurePipelinesContent = azureDevOpsPlugin.provide(
createRoutableExtension({
name: 'EntityAzurePipelinesContent',
- component: () => import('./components/Router').then(m => m.Router),
+ component: () =>
+ import('./components/EntityPageAzurePipelines').then(
+ m => m.EntityPageAzurePipelines,
+ ),
mountPoint: azureDevOpsRouteRef,
}),
);
diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md
index ae4a085649..bbf92944b9 100644
--- a/plugins/catalog-backend-module-ldap/api-report.md
+++ b/plugins/catalog-backend-module-ldap/api-report.md
@@ -10,7 +10,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 { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { LocationSpec } from '@backstage/catalog-model';
import { Logger as Logger_2 } from 'winston';
import { SearchEntry } from 'ldapjs';
diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json
index 7236e780c4..51c8780a9d 100644
--- a/plugins/catalog-backend-module-ldap/package.json
+++ b/plugins/catalog-backend-module-ldap/package.json
@@ -33,6 +33,7 @@
"@backstage/config": "^0.1.10",
"@backstage/errors": "^0.1.3",
"@backstage/plugin-catalog-backend": "^0.17.1",
+ "@backstage/types": "^0.1.1",
"@types/ldapjs": "^2.2.0",
"ldapjs": "^2.2.0",
"lodash": "^4.17.21",
diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts
index 6dad7e087f..dc1ec4910d 100644
--- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts
+++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { Config, JsonValue } from '@backstage/config';
+import { Config } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { SearchOptions } from 'ldapjs';
import mergeWith from 'lodash/mergeWith';
import { RecursivePartial } from '@backstage/plugin-catalog-backend';
diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md
index d3c73afc91..cbef4c90a1 100644
--- a/plugins/catalog-backend/api-report.md
+++ b/plugins/catalog-backend/api-report.md
@@ -18,8 +18,8 @@ import { EntityRelationSpec } from '@backstage/catalog-model';
import express from 'express';
import { GitHubIntegrationConfig } from '@backstage/integration';
import { IndexableDocument } from '@backstage/search-common';
-import { JsonObject } from '@backstage/config';
-import { JsonValue } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
+import { JsonValue } from '@backstage/types';
import { Knex } from 'knex';
import { Location as Location_2 } from '@backstage/catalog-model';
import { LocationSpec } from '@backstage/catalog-model';
diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json
index 896bbde592..9a44258958 100644
--- a/plugins/catalog-backend/package.json
+++ b/plugins/catalog-backend/package.json
@@ -37,6 +37,7 @@
"@backstage/errors": "^0.1.3",
"@backstage/integration": "^0.6.8",
"@backstage/search-common": "^0.2.0",
+ "@backstage/types": "^0.1.1",
"@octokit/graphql": "^4.5.8",
"@types/express": "^4.17.6",
"aws-sdk": "^2.840.0",
diff --git a/plugins/catalog-backend/src/database/types.ts b/plugins/catalog-backend/src/database/types.ts
index 9d28387e2c..ac2ff07498 100644
--- a/plugins/catalog-backend/src/database/types.ts
+++ b/plugins/catalog-backend/src/database/types.ts
@@ -15,7 +15,7 @@
*/
import { Entity, EntityRelationSpec } from '@backstage/catalog-model';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { DateTime } from 'luxon';
import { DeferredEntity } from '../processing/types';
diff --git a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts
index 9ddef1ab3b..65f65a0479 100644
--- a/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/PlaceholderProcessor.ts
@@ -16,7 +16,7 @@
import { UrlReader } from '@backstage/backend-common';
import { Entity, LocationSpec } from '@backstage/catalog-model';
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { ScmIntegrationRegistry } from '@backstage/integration';
import yaml from 'yaml';
import { CatalogProcessor } from './types';
diff --git a/plugins/catalog-backend/src/ingestion/processors/types.ts b/plugins/catalog-backend/src/ingestion/processors/types.ts
index dccc60d42c..3e4e24b0fe 100644
--- a/plugins/catalog-backend/src/ingestion/processors/types.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/types.ts
@@ -19,7 +19,7 @@ import {
EntityRelationSpec,
LocationSpec,
} from '@backstage/catalog-model';
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
export type CatalogProcessor = {
/**
diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts
index d3ec162216..b42c8839cf 100644
--- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts
+++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingOrchestrator.ts
@@ -29,7 +29,7 @@ import {
InputError,
NotAllowedError,
} from '@backstage/errors';
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { ScmIntegrationRegistry } from '@backstage/integration';
import path from 'path';
import { Logger } from 'winston';
diff --git a/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts b/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts
index 378974e33e..472d6453ce 100644
--- a/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts
+++ b/plugins/catalog-backend/src/processing/ProcessorCacheManager.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue } from '@backstage/types';
import { CatalogProcessor } from '../ingestion/processors';
import { CatalogProcessorCache } from '../ingestion/processors/types';
import { isObject } from './util';
diff --git a/plugins/catalog-backend/src/processing/types.ts b/plugins/catalog-backend/src/processing/types.ts
index 4b16ccef32..7fb1157d3b 100644
--- a/plugins/catalog-backend/src/processing/types.ts
+++ b/plugins/catalog-backend/src/processing/types.ts
@@ -15,7 +15,7 @@
*/
import { Entity, EntityRelationSpec } from '@backstage/catalog-model';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
export type EntityProcessingRequest = {
entity: Entity;
diff --git a/plugins/catalog-backend/src/processing/util.ts b/plugins/catalog-backend/src/processing/util.ts
index ad8fe8be60..9f5f23ed09 100644
--- a/plugins/catalog-backend/src/processing/util.ts
+++ b/plugins/catalog-backend/src/processing/util.ts
@@ -24,7 +24,7 @@ import {
ORIGIN_LOCATION_ANNOTATION,
stringifyEntityRef,
} from '@backstage/catalog-model';
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue } from '@backstage/types';
import { InputError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import path from 'path';
diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json
index 80ad7f9a16..f5705dcc76 100644
--- a/plugins/catalog-graphql/package.json
+++ b/plugins/catalog-graphql/package.json
@@ -33,6 +33,7 @@
"dependencies": {
"@backstage/catalog-model": "^0.9.5",
"@backstage/config": "^0.1.8",
+ "@backstage/types": "^0.1.1",
"@graphql-modules/core": "^0.7.17",
"apollo-server": "^2.16.1",
"cross-fetch": "^3.0.6",
diff --git a/plugins/catalog-graphql/src/service/client.ts b/plugins/catalog-graphql/src/service/client.ts
index 487762d5d7..4328a5bd5a 100644
--- a/plugins/catalog-graphql/src/service/client.ts
+++ b/plugins/catalog-graphql/src/service/client.ts
@@ -16,7 +16,7 @@
import { Entity, EntityMeta } from '@backstage/catalog-model';
import fetch from 'cross-fetch';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
export interface ReaderEntityMeta extends EntityMeta {
uid: string;
diff --git a/plugins/catalog-react/api-report.md b/plugins/catalog-react/api-report.md
index e28b8b0d32..06dccf4b7f 100644
--- a/plugins/catalog-react/api-report.md
+++ b/plugins/catalog-react/api-report.md
@@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model';
import { EntityName } from '@backstage/catalog-model';
import { IconButton } from '@material-ui/core';
import { LinkProps } from '@backstage/core-components';
-import { Observable } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { PropsWithChildren } from 'react';
import { default as React_2 } from 'react';
import { ReactNode } from 'react';
diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json
index 3373641d16..404ba6893f 100644
--- a/plugins/catalog-react/package.json
+++ b/plugins/catalog-react/package.json
@@ -36,6 +36,7 @@
"@backstage/core-plugin-api": "^0.1.11",
"@backstage/errors": "^0.1.3",
"@backstage/integration": "^0.6.8",
+ "@backstage/types": "^0.1.1",
"@backstage/version-bridge": "^0.1.0",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
diff --git a/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.ts
index 87d99c8904..46a8e63084 100644
--- a/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.ts
+++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/DefaultStarredEntitiesApi.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { Observable, StorageApi } from '@backstage/core-plugin-api';
+import { StorageApi } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import ObservableImpl from 'zen-observable';
import { performMigrationToTheNewBucket } from './migration';
import { StarredEntitiesApi } from './StarredEntitiesApi';
diff --git a/plugins/catalog-react/src/apis/StarredEntitiesApi/StarredEntitiesApi.ts b/plugins/catalog-react/src/apis/StarredEntitiesApi/StarredEntitiesApi.ts
index 4c4c68d66a..56c7664bfb 100644
--- a/plugins/catalog-react/src/apis/StarredEntitiesApi/StarredEntitiesApi.ts
+++ b/plugins/catalog-react/src/apis/StarredEntitiesApi/StarredEntitiesApi.ts
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-import { ApiRef, createApiRef, Observable } from '@backstage/core-plugin-api';
+import { ApiRef, createApiRef } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
/**
* An API to store starred entities
diff --git a/plugins/config-schema/api-report.md b/plugins/config-schema/api-report.md
index 15c00acc18..efeb1646eb 100644
--- a/plugins/config-schema/api-report.md
+++ b/plugins/config-schema/api-report.md
@@ -7,7 +7,7 @@
import { ApiRef } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
-import { Observable } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import { RouteRef } from '@backstage/core-plugin-api';
import { Schema } from 'jsonschema';
diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json
index 1b2214de8b..1a6b39f0cb 100644
--- a/plugins/config-schema/package.json
+++ b/plugins/config-schema/package.json
@@ -26,6 +26,7 @@
"@backstage/core-plugin-api": "^0.1.11",
"@backstage/errors": "^0.1.3",
"@backstage/theme": "^0.2.11",
+ "@backstage/types": "^0.1.1",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
diff --git a/plugins/config-schema/src/api/StaticSchemaLoader.ts b/plugins/config-schema/src/api/StaticSchemaLoader.ts
index 8958f4cb5b..48a125f38e 100644
--- a/plugins/config-schema/src/api/StaticSchemaLoader.ts
+++ b/plugins/config-schema/src/api/StaticSchemaLoader.ts
@@ -18,7 +18,7 @@ import ObservableImpl from 'zen-observable';
import { ResponseError } from '@backstage/errors';
import { Schema } from 'jsonschema';
import { ConfigSchemaApi, ConfigSchemaResult } from './types';
-import { Observable } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
const DEFAULT_URL = 'config-schema.json';
diff --git a/plugins/config-schema/src/api/types.ts b/plugins/config-schema/src/api/types.ts
index 464ef970fb..13cd4f602b 100644
--- a/plugins/config-schema/src/api/types.ts
+++ b/plugins/config-schema/src/api/types.ts
@@ -15,7 +15,8 @@
*/
import { Schema } from 'jsonschema';
-import { createApiRef, Observable } from '@backstage/core-plugin-api';
+import { createApiRef } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
export interface ConfigSchemaResult {
schema?: Schema;
diff --git a/plugins/config-schema/src/components/SchemaView/ChildView.tsx b/plugins/config-schema/src/components/SchemaView/ChildView.tsx
index 03cb1201a0..dcb446db5a 100644
--- a/plugins/config-schema/src/components/SchemaView/ChildView.tsx
+++ b/plugins/config-schema/src/components/SchemaView/ChildView.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { Box, Chip, Divider, makeStyles, Typography } from '@material-ui/core';
import { Schema } from 'jsonschema';
import React, { useEffect, useRef } from 'react';
diff --git a/plugins/config-schema/src/components/SchemaView/MetadataView.tsx b/plugins/config-schema/src/components/SchemaView/MetadataView.tsx
index c7113950cc..157890ae6a 100644
--- a/plugins/config-schema/src/components/SchemaView/MetadataView.tsx
+++ b/plugins/config-schema/src/components/SchemaView/MetadataView.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import {
Paper,
Table,
diff --git a/plugins/kubernetes/src/components/Cluster/Cluster.test.tsx b/plugins/kubernetes/src/components/Cluster/Cluster.test.tsx
new file mode 100644
index 0000000000..ddadd95272
--- /dev/null
+++ b/plugins/kubernetes/src/components/Cluster/Cluster.test.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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 { render } from '@testing-library/react';
+import { wrapInTestApp } from '@backstage/test-utils';
+import { Cluster } from './Cluster';
+
+jest.mock('../../hooks');
+import * as oneDeployment from '../../__fixtures__/1-deployments.json';
+
+describe('Cluster', () => {
+ it('render 1 cluster', async () => {
+ const { getByText } = render(
+ wrapInTestApp(
+ (),
+ } as any)}
+ />,
+ ),
+ );
+
+ expect(getByText('cluster-1')).toBeInTheDocument();
+ expect(getByText('10 pods')).toBeInTheDocument();
+ });
+});
diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx b/plugins/kubernetes/src/components/Cluster/Cluster.tsx
similarity index 52%
rename from plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx
rename to plugins/kubernetes/src/components/Cluster/Cluster.tsx
index 260c77ed9e..d238396f53 100644
--- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.tsx
+++ b/plugins/kubernetes/src/components/Cluster/Cluster.tsx
@@ -23,32 +23,20 @@ import {
Grid,
Typography,
} from '@material-ui/core';
-import { Entity } from '@backstage/catalog-model';
import { ClusterObjects } from '@backstage/plugin-kubernetes-common';
-import { ErrorPanel } from './ErrorPanel';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { DeploymentsAccordions } from '../DeploymentsAccordions';
-import { ErrorReporting } from '../ErrorReporting';
import { groupResponses } from '../../utils/response';
-import { DetectedError, detectErrors } from '../../error-detection';
import { IngressesAccordions } from '../IngressesAccordions';
import { ServicesAccordions } from '../ServicesAccordions';
import { CustomResources } from '../CustomResources';
-import EmptyStateImage from '../../assets/emptystate.svg';
import {
ClusterContext,
GroupedResponsesContext,
PodNamesWithErrorsContext,
- useKubernetesObjects,
} from '../../hooks';
-import {
- Content,
- Page,
- Progress,
- StatusError,
- StatusOK,
-} from '@backstage/core-components';
+import { StatusError, StatusOK } from '@backstage/core-components';
type ClusterSummaryProps = {
clusterName: string;
@@ -117,7 +105,7 @@ type ClusterProps = {
children?: React.ReactNode;
};
-const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => {
+export const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => {
const groupedResponses = groupResponses(clusterObjects.resources);
return (
@@ -153,107 +141,3 @@ const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => {
);
};
-
-type KubernetesContentProps = { entity: Entity; children?: React.ReactNode };
-
-export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
- const { kubernetesObjects, error } = useKubernetesObjects(entity);
-
- const clustersWithErrors =
- kubernetesObjects?.items.filter(r => r.errors.length > 0) ?? [];
-
- const detectedErrors =
- kubernetesObjects !== undefined
- ? detectErrors(kubernetesObjects)
- : new Map();
-
- return (
-
-
- {kubernetesObjects === undefined && error === undefined && }
-
- {/* errors retrieved from the kubernetes clusters */}
- {clustersWithErrors.length > 0 && (
-
-
-
-
-
- )}
-
- {/* other errors */}
- {error !== undefined && (
-
-
-
-
-
- )}
-
- {kubernetesObjects && (
-
-
-
-
-
-
-
-
- Your Clusters
-
-
- {kubernetesObjects?.items.length <= 0 && (
-
-
-
- No resources on any known clusters for{' '}
- {entity.metadata.name}
-
-
-
-
-
-
- )}
- {kubernetesObjects?.items.length > 0 &&
- kubernetesObjects?.items.map((item, i) => {
- const podsWithErrors = new Set(
- detectedErrors
- .get(item.cluster.name)
- ?.filter(de => de.kind === 'Pod')
- .map(de => de.names)
- .flat() ?? [],
- );
-
- return (
-
-
-
- );
- })}
-
-
- )}
-
-
- );
-};
diff --git a/packages/test-utils-core/src/setupTests.ts b/plugins/kubernetes/src/components/Cluster/index.ts
similarity index 94%
rename from packages/test-utils-core/src/setupTests.ts
rename to plugins/kubernetes/src/components/Cluster/index.ts
index 963c0f188b..dc9df63d42 100644
--- a/packages/test-utils-core/src/setupTests.ts
+++ b/plugins/kubernetes/src/components/Cluster/index.ts
@@ -13,5 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-import '@testing-library/jest-dom';
+export { Cluster } from './Cluster';
diff --git a/plugins/kubernetes/src/components/KubernetesContent/ErrorPanel.test.tsx b/plugins/kubernetes/src/components/ErrorPanel/ErrorPanel.test.tsx
similarity index 100%
rename from plugins/kubernetes/src/components/KubernetesContent/ErrorPanel.test.tsx
rename to plugins/kubernetes/src/components/ErrorPanel/ErrorPanel.test.tsx
diff --git a/plugins/kubernetes/src/components/KubernetesContent/ErrorPanel.tsx b/plugins/kubernetes/src/components/ErrorPanel/ErrorPanel.tsx
similarity index 100%
rename from plugins/kubernetes/src/components/KubernetesContent/ErrorPanel.tsx
rename to plugins/kubernetes/src/components/ErrorPanel/ErrorPanel.tsx
diff --git a/plugins/kubernetes/src/components/KubernetesContent/index.ts b/plugins/kubernetes/src/components/ErrorPanel/index.ts
similarity index 91%
rename from plugins/kubernetes/src/components/KubernetesContent/index.ts
rename to plugins/kubernetes/src/components/ErrorPanel/index.ts
index 1a77bd40b2..2d3778e2c2 100644
--- a/plugins/kubernetes/src/components/KubernetesContent/index.ts
+++ b/plugins/kubernetes/src/components/ErrorPanel/index.ts
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { KubernetesContent } from './KubernetesContent';
+export { ErrorPanel } from './ErrorPanel';
diff --git a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.test.tsx b/plugins/kubernetes/src/components/KubernetesContent.test.tsx
similarity index 95%
rename from plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.test.tsx
rename to plugins/kubernetes/src/components/KubernetesContent.test.tsx
index cd4b902f5f..db8cd06782 100644
--- a/plugins/kubernetes/src/components/KubernetesContent/KubernetesContent.test.tsx
+++ b/plugins/kubernetes/src/components/KubernetesContent.test.tsx
@@ -18,11 +18,11 @@ import React from 'react';
import { render } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
import { KubernetesContent } from './KubernetesContent';
-import { useKubernetesObjects } from '../../hooks';
+import { useKubernetesObjects } from '../hooks';
-jest.mock('../../hooks');
-import * as oneDeployment from '../../__fixtures__/1-deployments.json';
-import * as twoDeployments from '../../__fixtures__/2-deployments.json';
+jest.mock('../hooks');
+import * as oneDeployment from '../__fixtures__/1-deployments.json';
+import * as twoDeployments from '../__fixtures__/2-deployments.json';
describe('KubernetesContent', () => {
it('render empty response', async () => {
diff --git a/plugins/kubernetes/src/components/KubernetesContent.tsx b/plugins/kubernetes/src/components/KubernetesContent.tsx
new file mode 100644
index 0000000000..036bcdb608
--- /dev/null
+++ b/plugins/kubernetes/src/components/KubernetesContent.tsx
@@ -0,0 +1,130 @@
+/*
+ * 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 { Divider, Grid, Typography } from '@material-ui/core';
+import { Entity } from '@backstage/catalog-model';
+import { ErrorPanel } from './ErrorPanel';
+import { ErrorReporting } from './ErrorReporting';
+import { DetectedError, detectErrors } from '../error-detection';
+import { Cluster } from './Cluster';
+import EmptyStateImage from '../assets/emptystate.svg';
+import { useKubernetesObjects } from '../hooks';
+import { Content, Page, Progress } from '@backstage/core-components';
+
+type KubernetesContentProps = { entity: Entity; children?: React.ReactNode };
+
+export const KubernetesContent = ({ entity }: KubernetesContentProps) => {
+ const { kubernetesObjects, error } = useKubernetesObjects(entity);
+
+ const clustersWithErrors =
+ kubernetesObjects?.items.filter(r => r.errors.length > 0) ?? [];
+
+ const detectedErrors =
+ kubernetesObjects !== undefined
+ ? detectErrors(kubernetesObjects)
+ : new Map();
+
+ return (
+
+
+ {kubernetesObjects === undefined && error === undefined && }
+
+ {/* errors retrieved from the kubernetes clusters */}
+ {clustersWithErrors.length > 0 && (
+
+
+
+
+
+ )}
+
+ {/* other errors */}
+ {error !== undefined && (
+
+
+
+
+
+ )}
+
+ {kubernetesObjects && (
+
+
+
+
+
+
+
+
+ Your Clusters
+
+
+ {kubernetesObjects?.items.length <= 0 && (
+
+
+
+ No resources on any known clusters for{' '}
+ {entity.metadata.name}
+
+
+
+
+
+
+ )}
+ {kubernetesObjects?.items.length > 0 &&
+ kubernetesObjects?.items.map((item, i) => {
+ const podsWithErrors = new Set(
+ detectedErrors
+ .get(item.cluster.name)
+ ?.filter(de => de.kind === 'Pod')
+ .map(de => de.names)
+ .flat() ?? [],
+ );
+
+ return (
+
+
+
+ );
+ })}
+
+
+ )}
+
+
+ );
+};
diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts
index 1d46eb14ea..0daeb93d73 100644
--- a/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts
+++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.test.ts
@@ -71,10 +71,10 @@ describe('useKubernetesObjects', () => {
const mockGetObjectsByEntity = jest.fn();
const mockDecorateRequestBodyForAuth = jest.fn();
- const expectMocksCalledCorrectly = () => {
- expect(mockGetClusters).toBeCalledTimes(1);
+ const expectMocksCalledCorrectly = (numOfCalls: number = 1) => {
+ expect(mockGetClusters).toBeCalledTimes(numOfCalls);
expect(mockGetClusters).toHaveBeenLastCalledWith();
- expect(mockDecorateRequestBodyForAuth).toBeCalledTimes(2);
+ expect(mockDecorateRequestBodyForAuth).toBeCalledTimes(numOfCalls * 2);
expect(mockDecorateRequestBodyForAuth).toHaveBeenCalledWith('google', {
entity,
});
@@ -82,7 +82,7 @@ describe('useKubernetesObjects', () => {
'authprovider2',
entityWithAuthToken,
);
- expect(mockGetObjectsByEntity).toBeCalledTimes(1);
+ expect(mockGetObjectsByEntity).toBeCalledTimes(numOfCalls);
expect(mockGetObjectsByEntity).toHaveBeenLastCalledWith(
entityWithAuthToken,
);
@@ -110,6 +110,26 @@ describe('useKubernetesObjects', () => {
expectMocksCalledCorrectly();
});
+ it('should update on an interval', async () => {
+ (useApi as any).mockReturnValue({
+ getClusters: mockGetClusters.mockResolvedValue(getClustersResponse),
+ getObjectsByEntity:
+ mockGetObjectsByEntity.mockResolvedValue(mockResponse),
+ decorateRequestBodyForAuth:
+ mockDecorateRequestBodyForAuth.mockResolvedValue(entityWithAuthToken),
+ });
+ const { result, waitForNextUpdate } = renderHook(() =>
+ useKubernetesObjects(entity, 100),
+ );
+
+ await waitForNextUpdate();
+ await waitForNextUpdate();
+
+ expect(result.current.error).toBeUndefined();
+ expect(result.current.kubernetesObjects).toStrictEqual(mockResponse);
+
+ expectMocksCalledCorrectly(2);
+ });
it('should return error when getObjectsByEntity throws', async () => {
(useApi as any).mockReturnValue({
getClusters: mockGetClusters.mockResolvedValue(getClustersResponse),
diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts
index 4f19816296..8a8ec260fa 100644
--- a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts
+++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts
@@ -18,6 +18,7 @@ import { Entity } from '@backstage/catalog-model';
import { kubernetesApiRef } from '../api/types';
import { kubernetesAuthProvidersApiRef } from '../kubernetes-auth-provider/types';
import { useEffect, useState } from 'react';
+import { useInterval } from 'react-use';
import {
KubernetesRequestBody,
ObjectsByEntityResponse,
@@ -29,7 +30,10 @@ export interface KubernetesObjects {
error: string | undefined;
}
-export const useKubernetesObjects = (entity: Entity): KubernetesObjects => {
+export const useKubernetesObjects = (
+ entity: Entity,
+ intervalMs: number = 10000,
+): KubernetesObjects => {
const kubernetesApi = useApi(kubernetesApiRef);
const kubernetesAuthProvidersApi = useApi(kubernetesAuthProvidersApiRef);
const [kubernetesObjects, setKubernetesObjects] = useState<
@@ -38,51 +42,55 @@ export const useKubernetesObjects = (entity: Entity): KubernetesObjects => {
const [error, setError] = useState(undefined);
+ const getObjects = async () => {
+ let clusters = [];
+
+ try {
+ clusters = await kubernetesApi.getClusters();
+ } catch (e) {
+ setError(e.message);
+ return;
+ }
+
+ const authProviders: string[] = [
+ ...new Set(clusters.map(c => c.authProvider)),
+ ];
+ // For each auth type, invoke decorateRequestBodyForAuth on corresponding KubernetesAuthProvider
+ let requestBody: KubernetesRequestBody = {
+ entity,
+ };
+ for (const authProviderStr of authProviders) {
+ // Multiple asyncs done sequentially instead of all at once to prevent same requestBody from being modified simultaneously
+ try {
+ requestBody =
+ await kubernetesAuthProvidersApi.decorateRequestBodyForAuth(
+ authProviderStr,
+ requestBody,
+ );
+ } catch (e) {
+ setError(e.message);
+ return;
+ }
+ }
+
+ try {
+ setKubernetesObjects(await kubernetesApi.getObjectsByEntity(requestBody));
+ } catch (e) {
+ setError(e.message);
+ return;
+ }
+ };
+
useEffect(() => {
- (async () => {
- let clusters = [];
-
- try {
- clusters = await kubernetesApi.getClusters();
- } catch (e) {
- setError(e.message);
- return;
- }
-
- const authProviders: string[] = [
- ...new Set(clusters.map(c => c.authProvider)),
- ];
- // For each auth type, invoke decorateRequestBodyForAuth on corresponding KubernetesAuthProvider
- let requestBody: KubernetesRequestBody = {
- entity,
- };
- for (const authProviderStr of authProviders) {
- // Multiple asyncs done sequentially instead of all at once to prevent same requestBody from being modified simultaneously
- try {
- requestBody =
- await kubernetesAuthProvidersApi.decorateRequestBodyForAuth(
- authProviderStr,
- requestBody,
- );
- } catch (e) {
- setError(e.message);
- return;
- }
- }
-
- try {
- setKubernetesObjects(
- await kubernetesApi.getObjectsByEntity(requestBody),
- );
- } catch (e) {
- setError(e.message);
- return;
- }
- })();
+ getObjects();
/* eslint-disable react-hooks/exhaustive-deps */
}, [entity.metadata.name, kubernetesApi, kubernetesAuthProvidersApi]);
/* eslint-enable react-hooks/exhaustive-deps */
+ useInterval(() => {
+ getObjects();
+ }, intervalMs);
+
return {
kubernetesObjects,
error,
diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json
index 7db57dbe6b..1f004b8804 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/package.json
+++ b/plugins/scaffolder-backend-module-cookiecutter/package.json
@@ -25,6 +25,7 @@
"@backstage/integration": "^0.6.2",
"@backstage/plugin-scaffolder-backend": "^0.15.10",
"@backstage/config": "^0.1.8",
+ "@backstage/types": "^0.1.1",
"command-exists": "^1.2.9",
"fs-extra": "10.0.0",
"winston": "^3.2.1",
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 bc3fe3eca2..bba51e3f01 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
@@ -29,7 +29,8 @@ import {
UrlReader,
ContainerRunner,
} from '@backstage/backend-common';
-import { ConfigReader, JsonObject } from '@backstage/config';
+import { ConfigReader } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { ScmIntegrations } from '@backstage/integration';
import mockFs from 'mock-fs';
import os from 'os';
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 1f6fbdb7d8..5120cd3b27 100644
--- a/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts
+++ b/plugins/scaffolder-backend-module-cookiecutter/src/actions/fetch/cookiecutter.ts
@@ -19,7 +19,7 @@ import {
UrlReader,
resolveSafeChildPath,
} from '@backstage/backend-common';
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue } from '@backstage/types';
import { InputError } from '@backstage/errors';
import { ScmIntegrations } from '@backstage/integration';
import commandExists from 'command-exists';
diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json
index c88e6d0f9f..5e1c9d0107 100644
--- a/plugins/scaffolder-backend-module-rails/package.json
+++ b/plugins/scaffolder-backend-module-rails/package.json
@@ -26,6 +26,7 @@
"@backstage/config": "^0.1.8",
"@backstage/errors": "^0.1.3",
"@backstage/integration": "^0.6.2",
+ "@backstage/types": "^0.1.1",
"command-exists": "^1.2.9",
"fs-extra": "^9.0.0"
},
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 e2dc8fd95a..5ce7d46689 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
@@ -15,7 +15,7 @@
*/
import { ContainerRunner, UrlReader } from '@backstage/backend-common';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { InputError } from '@backstage/errors';
import { ScmIntegrations } from '@backstage/integration';
import fs from 'fs-extra';
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 a828800e84..27ce32dde2 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
@@ -23,7 +23,7 @@ import {
railsArgumentResolver,
RailsRunOptions,
} from './railsArgumentResolver';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { Writable } from 'stream';
export class RailsNewRunner {
diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json
index eb339b90d5..a1f1635808 100644
--- a/plugins/scaffolder-backend-module-yeoman/package.json
+++ b/plugins/scaffolder-backend-module-yeoman/package.json
@@ -22,6 +22,7 @@
"dependencies": {
"@backstage/config": "^0.1.8",
"@backstage/plugin-scaffolder-backend": "^0.15.2",
+ "@backstage/types": "^0.1.1",
"winston": "^3.2.1",
"yeoman-environment": "^3.6.0"
},
diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.test.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.test.ts
index 6e61ae77c4..ee5e8449f7 100644
--- a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.test.ts
+++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.test.ts
@@ -23,7 +23,7 @@ import os from 'os';
import { PassThrough } from 'stream';
import { createRunYeomanAction } from './yeoman';
import type { ActionContext } from '@backstage/plugin-scaffolder-backend';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
describe('run:yeoman', () => {
const mockTmpDir = os.tmpdir();
diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts
index c8bd49f3d3..f6a29037a3 100644
--- a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts
+++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeoman.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { createTemplateAction } from '@backstage/plugin-scaffolder-backend';
import { yeomanRun } from './yeomanRun';
diff --git a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeomanRun.ts b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeomanRun.ts
index ff11634f81..a18ea38c46 100644
--- a/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeomanRun.ts
+++ b/plugins/scaffolder-backend-module-yeoman/src/actions/run/yeomanRun.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
/*
* This module should use '@types/yeoman-environment' eventually as soon as '@types/yeoman-environment' supports
diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md
index a52c57bb2a..b40270f317 100644
--- a/plugins/scaffolder-backend/api-report.md
+++ b/plugins/scaffolder-backend/api-report.md
@@ -189,7 +189,7 @@ export function createRouter(options: RouterOptions): Promise;
// @public (undocumented)
export const createTemplateAction: <
Input extends Partial<{
- [name: string]: JsonValue_2 | Partial | undefined;
+ [name: string]: JsonValue | Partial | undefined;
}>,
>(
templateAction: TemplateAction,
diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json
index ea29866485..cfea66284d 100644
--- a/plugins/scaffolder-backend/package.json
+++ b/plugins/scaffolder-backend/package.json
@@ -39,6 +39,7 @@
"@backstage/plugin-catalog-backend": "^0.17.1",
"@backstage/plugin-scaffolder-common": "^0.1.0",
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.2",
+ "@backstage/types": "^0.1.1",
"@gitbeaker/core": "^30.2.0",
"@gitbeaker/node": "^30.2.0",
"@octokit/rest": "^18.5.3",
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 5625989417..3946110d42 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/helpers.ts
@@ -15,7 +15,7 @@
*/
import { resolveSafeChildPath, UrlReader } from '@backstage/backend-common';
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
import { InputError } from '@backstage/errors';
import { ScmIntegrations } from '@backstage/integration';
import fs from 'fs-extra';
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 aaadd5df57..4bdba96750 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts
@@ -17,7 +17,7 @@ import { createTemplateAction } from '../../createTemplateAction';
import { resolveSafeChildPath } from '@backstage/backend-common';
import { InputError } from '@backstage/errors';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import fs from 'fs-extra';
interface FilesToRename extends JsonObject {
diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
index 3783359b95..ca3459d29c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts
@@ -16,7 +16,7 @@
import { Logger } from 'winston';
import { Writable } from 'stream';
-import { JsonValue, JsonObject } from '@backstage/config';
+import { JsonValue, JsonObject } from '@backstage/types';
import { Schema } from 'jsonschema';
type PartialJsonObject = Partial;
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts
index 6783da674e..813d8d0f5b 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DatabaseTaskStore.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { resolvePackagePath } from '@backstage/backend-common';
import { ConflictError, NotFoundError } from '@backstage/errors';
import { Knex } from 'knex';
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.ts
index 145ded6bc0..e56befd939 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.ts
@@ -26,7 +26,7 @@ import * as winston from 'winston';
import nunjucks from 'nunjucks';
import fs from 'fs-extra';
import path from 'path';
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue } from '@backstage/types';
import { InputError } from '@backstage/errors';
import { PassThrough } from 'stream';
import { isTruthy } from './helper';
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.ts
index 97e3a4dec4..6d43ae978c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/LegacyWorkflowRunner.ts
@@ -31,7 +31,7 @@ import { Logger } from 'winston';
import path from 'path';
import fs from 'fs-extra';
import { validate as validateJsonSchema } from 'jsonschema';
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue } from '@backstage/types';
import { InputError } from '@backstage/errors';
type Options = {
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts
index 529cfed95e..2f4387ca4a 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.ts
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { assertError } from '@backstage/errors';
import { Logger } from 'winston';
import {
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
index e30d156736..cd4d291719 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonValue, JsonObject } from '@backstage/config';
+import { JsonValue, JsonObject } from '@backstage/types';
/**
* Status
diff --git a/plugins/scaffolder-common/api-report.md b/plugins/scaffolder-common/api-report.md
index 722e746967..8485e224b2 100644
--- a/plugins/scaffolder-common/api-report.md
+++ b/plugins/scaffolder-common/api-report.md
@@ -4,7 +4,7 @@
```ts
import { Entity } from '@backstage/catalog-model';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { JSONSchema } from '@backstage/catalog-model';
// @public (undocumented)
diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json
index b0c8de30c7..3c1c444a78 100644
--- a/plugins/scaffolder-common/package.json
+++ b/plugins/scaffolder-common/package.json
@@ -37,7 +37,7 @@
},
"dependencies": {
"@backstage/catalog-model": "^0.9.5",
- "@backstage/config": "^0.1.10"
+ "@backstage/types": "^0.1.1"
},
"devDependencies": {
"@backstage/cli": "^0.8.0"
diff --git a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts
index 6471fe0ca6..2aefa5fd6b 100644
--- a/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts
+++ b/plugins/scaffolder-common/src/TemplateEntityV1beta3.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { Entity } from '@backstage/catalog-model';
/** @public */
diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md
index 949f19a058..554fccabb7 100644
--- a/plugins/scaffolder/api-report.md
+++ b/plugins/scaffolder/api-report.md
@@ -19,10 +19,10 @@ import { FieldProps } from '@rjsf/core';
import { FieldValidation } from '@rjsf/core';
import { IconButton } from '@material-ui/core';
import { IdentityApi } from '@backstage/core-plugin-api';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { JSONSchema } from '@backstage/catalog-model';
-import { JsonValue } from '@backstage/config';
-import { Observable } from '@backstage/core-plugin-api';
+import { JsonValue } from '@backstage/types';
+import { Observable } from '@backstage/types';
import { default as React_2 } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
import { ScmIntegrationRegistry } from '@backstage/integration';
@@ -240,6 +240,29 @@ const scaffolderPlugin: BackstagePlugin<
export { scaffolderPlugin as plugin };
export { scaffolderPlugin };
+// 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)
+export const TemplateList: ({
+ TemplateCardComponent,
+}: TemplateListProps) => JSX.Element;
+
+// 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)
+export type TemplateListProps = {
+ TemplateCardComponent?:
+ | ComponentType<{
+ template: TemplateEntityV1beta2;
+ }>
+ | undefined;
+};
+
+// 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)
diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json
index 10483f4807..aa4412204a 100644
--- a/plugins/scaffolder/package.json
+++ b/plugins/scaffolder/package.json
@@ -41,6 +41,7 @@
"@backstage/integration-react": "^0.1.12",
"@backstage/plugin-catalog-react": "^0.6.1",
"@backstage/theme": "^0.2.11",
+ "@backstage/types": "^0.1.1",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts
index 3e0ac3eccb..1b531b652d 100644
--- a/plugins/scaffolder/src/api.ts
+++ b/plugins/scaffolder/src/api.ts
@@ -15,7 +15,7 @@
*/
import { EntityName } from '@backstage/catalog-model';
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue, Observable } from '@backstage/types';
import { ResponseError } from '@backstage/errors';
import { ScmIntegrationRegistry } from '@backstage/integration';
import { Field, FieldValidation } from '@rjsf/core';
@@ -25,7 +25,6 @@ import {
createApiRef,
DiscoveryApi,
IdentityApi,
- Observable,
} from '@backstage/core-plugin-api';
export const scaffolderApiRef = createApiRef({
diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx
index 62c2881ab0..df5be2de56 100644
--- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx
+++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import {
Box,
Button,
diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/schema.ts b/plugins/scaffolder/src/components/MultistepJsonForm/schema.ts
index dd86784275..39592d314d 100644
--- a/plugins/scaffolder/src/components/MultistepJsonForm/schema.ts
+++ b/plugins/scaffolder/src/components/MultistepJsonForm/schema.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { FormProps } from '@rjsf/core';
function isObject(value: unknown): value is JsonObject {
diff --git a/plugins/scaffolder/src/components/TemplateList/index.ts b/plugins/scaffolder/src/components/TemplateList/index.ts
index b9ec700d74..0b62f9cbb1 100644
--- a/plugins/scaffolder/src/components/TemplateList/index.ts
+++ b/plugins/scaffolder/src/components/TemplateList/index.ts
@@ -14,3 +14,4 @@
* limitations under the License.
*/
export { TemplateList } from './TemplateList';
+export type { TemplateListProps } from './TemplateList';
diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx
index 05533376db..0ea53212c2 100644
--- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx
+++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { JsonObject, JsonValue } from '@backstage/config';
+import { JsonObject, JsonValue } from '@backstage/types';
import { LinearProgress } from '@material-ui/core';
import { FormValidation, IChangeEvent } from '@rjsf/core';
import React, { useCallback, useState } from 'react';
diff --git a/plugins/scaffolder/src/components/hooks/useEventStream.ts b/plugins/scaffolder/src/components/hooks/useEventStream.ts
index 9eb1666db4..7414a35189 100644
--- a/plugins/scaffolder/src/components/hooks/useEventStream.ts
+++ b/plugins/scaffolder/src/components/hooks/useEventStream.ts
@@ -17,7 +17,8 @@ import { useImmerReducer } from 'use-immer';
import { useEffect } from 'react';
import { scaffolderApiRef, LogEvent } from '../../api';
import { ScaffolderTask, Status, TaskOutput } from '../../types';
-import { Subscription, useApi } from '@backstage/core-plugin-api';
+import { useApi } from '@backstage/core-plugin-api';
+import { Subscription } from '@backstage/types';
type Step = {
id: string;
diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts
index 4de701bf7a..0180c24b90 100644
--- a/plugins/scaffolder/src/index.ts
+++ b/plugins/scaffolder/src/index.ts
@@ -44,3 +44,6 @@ export {
TextValuePicker,
} from './components/fields';
export { FavouriteTemplate } from './components/FavouriteTemplate';
+export { TemplateList } from './components/TemplateList';
+export type { TemplateListProps } from './components/TemplateList';
+export { TemplateTypePicker } from './components/TemplateTypePicker';
diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts
index dc71d44f71..5597082f22 100644
--- a/plugins/scaffolder/src/types.ts
+++ b/plugins/scaffolder/src/types.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { JSONSchema } from '@backstage/catalog-model';
-import { JsonValue } from '@backstage/config';
+import { JsonValue } from '@backstage/types';
export type Status = 'open' | 'processing' | 'failed' | 'completed' | 'skipped';
export type JobStatus = 'PENDING' | 'STARTED' | 'COMPLETED' | 'FAILED';
diff --git a/plugins/search/api-report.md b/plugins/search/api-report.md
index b29149805c..c638fb4af6 100644
--- a/plugins/search/api-report.md
+++ b/plugins/search/api-report.md
@@ -9,7 +9,7 @@ import { ApiRef } from '@backstage/core-plugin-api';
import { AsyncState } from 'react-use/lib/useAsync';
import { BackstagePlugin } from '@backstage/core-plugin-api';
import { IndexableDocument } from '@backstage/search-common';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { default as React_2 } from 'react';
import { ReactElement } from 'react';
import { RouteRef } from '@backstage/core-plugin-api';
diff --git a/plugins/search/package.json b/plugins/search/package.json
index 9830d26e5c..ee8a2fb726 100644
--- a/plugins/search/package.json
+++ b/plugins/search/package.json
@@ -38,6 +38,7 @@
"@backstage/plugin-catalog-react": "^0.6.1",
"@backstage/search-common": "^0.2.0",
"@backstage/theme": "^0.2.11",
+ "@backstage/types": "^0.1.1",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
diff --git a/plugins/search/src/components/SearchContext/SearchContext.tsx b/plugins/search/src/components/SearchContext/SearchContext.tsx
index 16d1371964..11d6a2786e 100644
--- a/plugins/search/src/components/SearchContext/SearchContext.tsx
+++ b/plugins/search/src/components/SearchContext/SearchContext.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { useApi } from '@backstage/core-plugin-api';
import { SearchResultSet } from '@backstage/search-common';
import React, {
diff --git a/plugins/search/src/components/SearchPage/SearchPage.tsx b/plugins/search/src/components/SearchPage/SearchPage.tsx
index fb18833da6..a0738b3466 100644
--- a/plugins/search/src/components/SearchPage/SearchPage.tsx
+++ b/plugins/search/src/components/SearchPage/SearchPage.tsx
@@ -19,7 +19,7 @@ import { usePrevious } from 'react-use';
import qs from 'qs';
import { useLocation, useOutlet } from 'react-router';
import { SearchContextProvider, useSearch } from '../SearchContext';
-import { JsonObject } from '@backstage/config';
+import { JsonObject } from '@backstage/types';
import { LegacySearchPage } from '../LegacySearchPage';
export const UrlUpdater = () => {
diff --git a/plugins/sentry/api-report.md b/plugins/sentry/api-report.md
index 7143272284..a23fa112c1 100644
--- a/plugins/sentry/api-report.md
+++ b/plugins/sentry/api-report.md
@@ -36,7 +36,11 @@ export class MockSentryApi implements SentryApi {
export class ProductionSentryApi implements SentryApi {
constructor(discoveryApi: DiscoveryApi, organization: string);
// (undocumented)
- fetchIssues(project: string, statsFor: string): Promise;
+ fetchIssues(
+ project: string,
+ statsFor: string,
+ query?: string,
+ ): Promise;
}
// Warning: (ae-missing-release-tag) "Router" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -49,7 +53,11 @@ export const Router: ({ entity }: { entity: Entity }) => JSX.Element;
// @public (undocumented)
export interface SentryApi {
// (undocumented)
- fetchIssues(project: string, statsFor: string): Promise;
+ fetchIssues(
+ project: string,
+ statsFor: string,
+ query?: string,
+ ): Promise;
}
// Warning: (ae-missing-release-tag) "sentryApiRef" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
@@ -67,7 +75,7 @@ export type SentryIssue = {
userCount: number;
stats: {
'24h'?: EventPoint[];
- '12h'?: EventPoint[];
+ '14d'?: EventPoint[];
};
culprit: string;
title: string;
@@ -100,10 +108,12 @@ export const SentryIssuesWidget: ({
entity,
statsFor,
variant,
+ query,
}: {
entity: Entity;
- statsFor?: '12h' | '24h' | undefined;
+ statsFor?: '' | '14d' | '24h' | undefined;
variant?: InfoCardVariants | undefined;
+ query?: string | undefined;
}) => JSX.Element;
// Warning: (ae-missing-release-tag) "sentryPlugin" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
diff --git a/plugins/sentry/src/api/mock/mock-api.ts b/plugins/sentry/src/api/mock/mock-api.ts
index 5f0c68c1d8..af64d8656a 100644
--- a/plugins/sentry/src/api/mock/mock-api.ts
+++ b/plugins/sentry/src/api/mock/mock-api.ts
@@ -20,7 +20,7 @@ import mockData from './sentry-issue-mock.json';
function getMockIssue(): SentryIssue {
const randomizedStats = {
- '12h': new Array(12)
+ '24h': new Array(24)
.fill(0)
.map(() => [0, Math.floor(Math.random() * 100)]),
};
diff --git a/plugins/sentry/src/api/mock/sentry-issue-mock.json b/plugins/sentry/src/api/mock/sentry-issue-mock.json
index 115be77b19..34201d56ee 100644
--- a/plugins/sentry/src/api/mock/sentry-issue-mock.json
+++ b/plugins/sentry/src/api/mock/sentry-issue-mock.json
@@ -4,7 +4,7 @@
"numComments": 0,
"userCount": 0,
"stats": {
- "12h": [
+ "24h": [
[1589450400, 7],
[1589454000, 2],
[1589457600, 6],
diff --git a/plugins/sentry/src/api/production-api.ts b/plugins/sentry/src/api/production-api.ts
index e634650f96..5968d20011 100644
--- a/plugins/sentry/src/api/production-api.ts
+++ b/plugins/sentry/src/api/production-api.ts
@@ -24,15 +24,21 @@ export class ProductionSentryApi implements SentryApi {
private readonly organization: string,
) {}
- async fetchIssues(project: string, statsFor: string): Promise {
+ async fetchIssues(
+ project: string,
+ statsFor: string,
+ query?: string,
+ ): Promise {
if (!project) {
return [];
}
const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sentry/api`;
+ const queryPart = query ? `&query=${query}` : '';
+
const response = await fetch(
- `${apiUrl}/0/projects/${this.organization}/${project}/issues/?statsPeriod=${statsFor}`,
+ `${apiUrl}/0/projects/${this.organization}/${project}/issues/?statsPeriod=${statsFor}${queryPart}`,
);
if (response.status >= 400 && response.status < 600) {
diff --git a/plugins/sentry/src/api/sentry-api.ts b/plugins/sentry/src/api/sentry-api.ts
index a755ca0941..d2177cde2a 100644
--- a/plugins/sentry/src/api/sentry-api.ts
+++ b/plugins/sentry/src/api/sentry-api.ts
@@ -23,5 +23,9 @@ export const sentryApiRef = createApiRef({
});
export interface SentryApi {
- fetchIssues(project: string, statsFor: string): Promise;
+ fetchIssues(
+ project: string,
+ statsFor: string,
+ query?: string,
+ ): Promise;
}
diff --git a/plugins/sentry/src/api/sentry-issue.ts b/plugins/sentry/src/api/sentry-issue.ts
index 4364edb502..ef0e99adb8 100644
--- a/plugins/sentry/src/api/sentry-issue.ts
+++ b/plugins/sentry/src/api/sentry-issue.ts
@@ -39,7 +39,7 @@ export type SentryIssue = {
userCount: number;
stats: {
'24h'?: EventPoint[];
- '12h'?: EventPoint[];
+ '14d'?: EventPoint[];
};
culprit: string;
title: string;
diff --git a/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx b/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx
index 001b42f9f3..0448b7cfea 100644
--- a/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx
+++ b/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx
@@ -20,8 +20,8 @@ import { Sparklines, SparklinesBars } from 'react-sparklines';
export const ErrorGraph = ({ sentryIssue }: { sentryIssue: SentryIssue }) => {
const data =
- '12h' in sentryIssue.stats
- ? sentryIssue.stats['12h']
+ '14d' in sentryIssue.stats
+ ? sentryIssue.stats['14d']
: sentryIssue.stats['24h'];
return (
diff --git a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx
index 637f2c4355..b6b7848dfa 100644
--- a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx
+++ b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx
@@ -59,7 +59,7 @@ const columns: TableColumn[] = [
type SentryIssuesTableProps = {
sentryIssues: SentryIssue[];
- statsFor?: '24h' | '12h';
+ statsFor?: '24h' | '14d' | '';
};
const SentryIssuesTable = ({
diff --git a/plugins/sentry/src/components/SentryIssuesWidget/SentryIssuesWidget.tsx b/plugins/sentry/src/components/SentryIssuesWidget/SentryIssuesWidget.tsx
index 46198e4b93..00f7ad0b0b 100644
--- a/plugins/sentry/src/components/SentryIssuesWidget/SentryIssuesWidget.tsx
+++ b/plugins/sentry/src/components/SentryIssuesWidget/SentryIssuesWidget.tsx
@@ -38,10 +38,12 @@ export const SentryIssuesWidget = ({
entity,
statsFor = '24h',
variant = 'gridItem',
+ query = '',
}: {
entity: Entity;
- statsFor?: '24h' | '12h';
+ statsFor?: '24h' | '14d' | '';
variant?: InfoCardVariants;
+ query?: string;
}) => {
const errorApi = useApi(errorApiRef);
const sentryApi = useApi(sentryApiRef);
@@ -49,8 +51,8 @@ export const SentryIssuesWidget = ({
const projectId = useProjectSlug(entity);
const { loading, value, error } = useAsync(
- () => sentryApi.fetchIssues(projectId, statsFor),
- [sentryApi, statsFor, projectId],
+ () => sentryApi.fetchIssues(projectId, statsFor, query),
+ [sentryApi, statsFor, projectId, query],
);
useEffect(() => {
diff --git a/plugins/shortcuts/api-report.md b/plugins/shortcuts/api-report.md
index 0e1442b53f..d85d427c99 100644
--- a/plugins/shortcuts/api-report.md
+++ b/plugins/shortcuts/api-report.md
@@ -7,7 +7,7 @@
import { ApiRef } from '@backstage/core-plugin-api';
import { BackstagePlugin } from '@backstage/core-plugin-api';
-import { Observable } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
import ObservableImpl from 'zen-observable';
import { StorageApi } from '@backstage/core-plugin-api';
diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json
index c9afe49c5a..14dfd16896 100644
--- a/plugins/shortcuts/package.json
+++ b/plugins/shortcuts/package.json
@@ -24,6 +24,7 @@
"@backstage/core-components": "^0.7.1",
"@backstage/core-plugin-api": "^0.1.11",
"@backstage/theme": "^0.2.11",
+ "@backstage/types": "^0.1.1",
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
diff --git a/plugins/shortcuts/src/api/ShortcutApi.ts b/plugins/shortcuts/src/api/ShortcutApi.ts
index c7802a0385..dcecc9945b 100644
--- a/plugins/shortcuts/src/api/ShortcutApi.ts
+++ b/plugins/shortcuts/src/api/ShortcutApi.ts
@@ -15,7 +15,8 @@
*/
import { Shortcut } from '../types';
-import { createApiRef, Observable } from '@backstage/core-plugin-api';
+import { createApiRef } from '@backstage/core-plugin-api';
+import { Observable } from '@backstage/types';
export const shortcutsApiRef = createApiRef({
id: 'plugin.shortcuts.api',
diff --git a/plugins/techdocs/src/reader/components/Reader.tsx b/plugins/techdocs/src/reader/components/Reader.tsx
index 7307961772..9bb0a7bbad 100644
--- a/plugins/techdocs/src/reader/components/Reader.tsx
+++ b/plugins/techdocs/src/reader/components/Reader.tsx
@@ -321,6 +321,7 @@ export const useTechDocsReaderDom = (): Element | null => {
baseUrl: window.location.origin,
onClick: (_: MouseEvent, url: string) => {
const parsedUrl = new URL(url);
+ // hash exists when anchor is clicked on secondary sidebar
if (parsedUrl.hash) {
navigate(`${parsedUrl.pathname}${parsedUrl.hash}`);
// Scroll to hash if it's on the current page
@@ -329,6 +330,10 @@ export const useTechDocsReaderDom = (): Element | null => {
?.scrollIntoView();
} else {
navigate(parsedUrl.pathname);
+ // Scroll to top of reader if primary sidebar link is clicked
+ transformedElement
+ ?.querySelector('.md-content__inner')
+ ?.scrollIntoView();
}
},
}),