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). +

+ Screenshot of the wizard asking for a name for the app, and a selection menu for the database. +

-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 +``` + +

+ Screenshot of the command output, with the message web pack compiled successfully. +

+ +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. + +

+ Screenshot of the Backstage portal. +

+ +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/) -![Azure DevOps Builds Example](./docs/azure-devops-builds.png) +## Features + +### Azure Pipelines + +Lists the top _n_ builds for a given repository where _n_ is a configurable value + +![Azure Pipelines Builds Example](./docs/azure-devops-builds.png) ## 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} - - - - EmptyState - - - )} - {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} + + + + EmptyState + + + )} + {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(); } }, }),