Merge branch 'master' into scaffolder-improvement
This commit is contained in:
@@ -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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-sentry': patch
|
||||
---
|
||||
|
||||
feature: sentry-plugin allows passing search query for listing issues
|
||||
@@ -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()`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-techdocs': patch
|
||||
---
|
||||
|
||||
Reader will now scroll to the top of the page when navigating between pages
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-scaffolder': patch
|
||||
---
|
||||
|
||||
Surfacing all components of the ScaffolderPage outside of the plugin so you can customize the page
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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`
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/techdocs-common': patch
|
||||
---
|
||||
|
||||
Support optional bucketRootPath configuration parameter in S3 and GCS publishers
|
||||
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-sentry': patch
|
||||
---
|
||||
|
||||
fix: sentry-plugin correction for allowed period values
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -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
|
||||
|
||||
@@ -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).
|
||||
<p align='center'>
|
||||
<img src='../assets/getting-started/wizard.png' alt='Screenshot of the wizard asking for a name for the app, and a selection menu for the database.'>
|
||||
</p>
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
<p align='center'>
|
||||
<img src='../assets/getting-started/startup.png' alt='Screenshot of the command output, with the message web pack compiled successfully.'>
|
||||
</p>
|
||||
|
||||
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.
|
||||
|
||||
<p align='center'>
|
||||
<img src='../assets/getting-started/portal.png' alt='Screenshot of the Backstage portal.'>
|
||||
</p>
|
||||
|
||||
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)
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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",
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { YarnInfoInspectData } from '../../lib/versioning';
|
||||
import { JsonObject } from '@backstage/config';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
|
||||
/**
|
||||
* TODO: possible types
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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_';
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { TransformFunc, EnvFunc } from './types';
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { JsonValue } from '@backstage/config';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
|
||||
export type EnvFunc = (name: string) => Promise<string | undefined>;
|
||||
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -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<ResultType> = {
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
|
||||
@@ -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<string, WebStorage>();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
+2
-1
@@ -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';
|
||||
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
@@ -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<SessionState>(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
//
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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<void> {
|
||||
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<TechDocsMetadata>(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<boolean> {
|
||||
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,
|
||||
|
||||
@@ -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`,
|
||||
|
||||
@@ -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<void> {
|
||||
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<TechDocsMetadata> {
|
||||
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<any> = [];
|
||||
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<boolean> {
|
||||
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`)
|
||||
|
||||
@@ -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', () => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<void>;
|
||||
|
||||
// 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<T extends LogFuncs> = {
|
||||
[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<void>;
|
||||
// (undocumented)
|
||||
debug: boolean;
|
||||
// (undocumented)
|
||||
document: any;
|
||||
// (undocumented)
|
||||
enter(value: any): Promise<void>;
|
||||
// (undocumented)
|
||||
escape(): Promise<void>;
|
||||
// (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<void>;
|
||||
// (undocumented)
|
||||
_sendKey(key: any, charCode: any, action: any): Promise<void>;
|
||||
// (undocumented)
|
||||
tab(): Promise<void>;
|
||||
// (undocumented)
|
||||
static toReadableInput(chars: any): any;
|
||||
// (undocumented)
|
||||
toString(): string;
|
||||
// (undocumented)
|
||||
static type(target: any, input: any): Promise<void>;
|
||||
// (undocumented)
|
||||
type(input: any): Promise<void>;
|
||||
// (undocumented)
|
||||
static typeDebug(target: any, input: any): Promise<void>;
|
||||
}
|
||||
|
||||
// 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<RenderResult>;
|
||||
|
||||
// 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<CollectedLogs<LogFuncs>>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function withLogCollector(
|
||||
callback: SyncLogCollector,
|
||||
): CollectedLogs<LogFuncs>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function withLogCollector<T extends LogFuncs>(
|
||||
logsToCollect: T[],
|
||||
callback: AsyncLogCollector,
|
||||
): Promise<CollectedLogs<T>>;
|
||||
|
||||
// @public (undocumented)
|
||||
export function withLogCollector<T extends LogFuncs>(
|
||||
logsToCollect: T[],
|
||||
callback: SyncLogCollector,
|
||||
): CollectedLogs<T>;
|
||||
// (No @packageDocumentation comment for this package)
|
||||
```
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
|
||||
@@ -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';
|
||||
@@ -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<void>;
|
||||
|
||||
// @public
|
||||
export type CollectedLogs<T extends LogFuncs> = {
|
||||
[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<void>;
|
||||
// (undocumented)
|
||||
debug: boolean;
|
||||
// (undocumented)
|
||||
document: any;
|
||||
// (undocumented)
|
||||
enter(value: any): Promise<void>;
|
||||
// (undocumented)
|
||||
escape(): Promise<void>;
|
||||
// (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<void>;
|
||||
// (undocumented)
|
||||
_sendKey(key: any, charCode: any, action: any): Promise<void>;
|
||||
// (undocumented)
|
||||
tab(): Promise<void>;
|
||||
// (undocumented)
|
||||
static toReadableInput(chars: any): any;
|
||||
// (undocumented)
|
||||
toString(): string;
|
||||
// (undocumented)
|
||||
static type(target: any, input: any): Promise<void>;
|
||||
// (undocumented)
|
||||
type(input: any): Promise<void>;
|
||||
// (undocumented)
|
||||
static typeDebug(target: any, input: any): Promise<void>;
|
||||
}
|
||||
|
||||
// @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<ErrorWithContext>;
|
||||
}
|
||||
|
||||
// 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<T>(key: string, data: T): Promise<void>;
|
||||
}
|
||||
|
||||
// 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<RenderResult>;
|
||||
|
||||
// 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<RenderResult>;
|
||||
|
||||
// @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<CollectedLogs<LogFuncs>>;
|
||||
|
||||
// @public
|
||||
export function withLogCollector(
|
||||
callback: SyncLogCollector,
|
||||
): CollectedLogs<LogFuncs>;
|
||||
|
||||
// @public
|
||||
export function withLogCollector<T extends LogFuncs>(
|
||||
logsToCollect: T[],
|
||||
callback: AsyncLogCollector,
|
||||
): Promise<CollectedLogs<T>>;
|
||||
|
||||
// @public
|
||||
export function withLogCollector<T extends LogFuncs>(
|
||||
logsToCollect: T[],
|
||||
callback: SyncLogCollector,
|
||||
): CollectedLogs<T>;
|
||||
|
||||
// @public
|
||||
export function wrapInTestApp(
|
||||
Component: ComponentType | ReactNode,
|
||||
options?: TestAppOptions,
|
||||
): ReactElement;
|
||||
|
||||
export * from '@backstage/test-utils-core';
|
||||
```
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -21,4 +21,3 @@
|
||||
*/
|
||||
|
||||
export * from './testUtils';
|
||||
export * from '@backstage/test-utils-core';
|
||||
|
||||
+4
@@ -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);
|
||||
@@ -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[] = [];
|
||||
|
||||
|
||||
@@ -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<ErrorWithContext>();
|
||||
private readonly waiters = new Set<Waiter>();
|
||||
|
||||
constructor(private readonly options: Options = {}) {}
|
||||
constructor(private readonly options: MockErrorApiOptions = {}) {}
|
||||
|
||||
post(error: Error, context?: ErrorContext) {
|
||||
if (this.options.collect) {
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
export { MockErrorApi } from './MockErrorApi';
|
||||
export type { MockErrorApiOptions, ErrorWithContext } from './MockErrorApi';
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 = () => <div data-testid="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(<MyComponent />, {
|
||||
* mountedRoutes: {
|
||||
* wrapInTestApp(<MyComponent />, \{
|
||||
* 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,
|
||||
|
||||
@@ -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';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user