Merge remote-tracking branch 'origin/master' into natasha/b2b-auth

Signed-off-by: Mike Lewis <mtlewis@users.noreply.github.com>
This commit is contained in:
Mike Lewis
2021-11-22 18:22:57 +00:00
201 changed files with 1464 additions and 610 deletions
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/errors': patch
---
Deprecate `parseErrorResponse` in favour of `parseErrorResponseBody`. Deprecate `data` field inside `ErrorResponse` in favour of `body`.
Rename the error name for unknown errors from `unknown` to `error`.
+2 -2
View File
@@ -23,13 +23,13 @@ const {
async function getDependencyReleaseLine(changesets, dependenciesUpdated) {
if (dependenciesUpdated.length === 0) return '';
const updatedDepenenciesList = dependenciesUpdated.map(
const updatedDependenciesList = dependenciesUpdated.map(
dependency => ` - ${dependency.name}@${dependency.newVersion}`,
);
// Return one `Updated dependencies` bullet instead of repeating for each changeset; this
// sacrifices the commit shas for brevity.
return ['- Updated dependencies', ...updatedDepenenciesList].join('\n');
return ['- Updated dependencies', ...updatedDependenciesList].join('\n');
}
module.exports = {
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-tech-insights-backend': patch
'@backstage/plugin-tech-insights-backend-module-jsonfc': patch
---
Update README docs to use correct function/parameter names
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-auth-backend': patch
---
Switched to using the standardized JSON error responses for all provider endpoints.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Switch the default test coverage provider from the jest default one to `'v8'`, which provides much better coverage information when using the default Backstage test setup. This is considered a bug fix as the current coverage information is often very inaccurate.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Disable ES transforms in tests transformed by the `jestSucraseTransform.js`. This is not considered a breaking change since all code is already transpiled this way in the development setup.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
DefaultTechDocsCollator is now included in the search backend, and the Search Page updated with the SearchType component that includes the techdocs type
+34
View File
@@ -0,0 +1,34 @@
---
'@backstage/backend-common': patch
'@backstage/cli': patch
'@backstage/core-app-api': patch
'@backstage/create-app': patch
'@backstage/techdocs-common': patch
'@backstage/plugin-auth-backend': patch
'@backstage/plugin-azure-devops-backend': patch
'@backstage/plugin-badges-backend': patch
'@backstage/plugin-bazaar-backend': patch
'@backstage/plugin-catalog-backend': patch
'@backstage/plugin-code-coverage-backend': patch
'@backstage/plugin-github-actions': patch
'@backstage/plugin-jenkins-backend': patch
'@backstage/plugin-proxy-backend': patch
'@backstage/plugin-rollbar-backend': patch
'@backstage/plugin-search-backend': patch
'@backstage/plugin-techdocs': patch
'@backstage/plugin-techdocs-backend': patch
---
Change default port of backend from 7000 to 7007.
This is due to the AirPlay Receiver process occupying port 7000 and preventing local Backstage instances on MacOS to start.
You can change the port back to 7000 or any other value by providing an `app-config.yaml` with the following values:
```
backend:
listen: 0.0.0.0:7123
baseUrl: http://localhost:7123
```
More information can be found here: https://backstage.io/docs/conf/writing
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-permission-common': minor
---
Accept configApi rather than enabled flag in PermissionClient constructor.
+42
View File
@@ -0,0 +1,42 @@
---
'@backstage/plugin-catalog-backend': minor
---
**BREAKING** EntitiesSearchFilter fields have changed.
EntitiesSearchFilter now has only two fields: `key` and `value`. The `matchValueIn` and `matchValueExists` fields are no longer are supported. Previous filters written using the `matchValueIn` and `matchValueExists` fields can be rewritten as follows:
Filtering by existence of key only:
```diff
filter: {
{
key: 'abc',
- matchValueExists: true,
},
}
```
Filtering by key and values:
```diff
filter: {
{
key: 'abc',
- matchValueExists: true,
- matchValueIn: ['xyz'],
+ values: ['xyz'],
},
}
```
Negation of filters can now be achieved through a `not` object:
```
filter: {
not: {
key: 'abc',
values: ['xyz'],
},
}
```
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': patch
---
Index User entities by displayName to be able to search by full name. Added displayName (if present) to the 'text' field in the indexed document.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Update the default routes to use id instead of title
+63
View File
@@ -0,0 +1,63 @@
---
'@backstage/core-app-api': patch
'@backstage/test-utils': patch
---
The `ApiRegistry` from `@backstage/core-app-api` class has been deprecated and will be removed in a future release. To replace it, we have introduced two new helpers that are exported from `@backstage/test-utils`, namely `TestApiProvider` and `TestApiRegistry`.
These two new helpers are more tailored for writing tests and development setups, as they allow for partial implementations of each of the APIs.
When migrating existing code it is typically best to prefer usage of `TestApiProvider` when possible, so for example the following code:
```tsx
render(
<ApiProvider
apis={ApiRegistry.from([
[identityApiRef, mockIdentityApi as unknown as IdentityApi]
])}
>
{...}
</ApiProvider>
)
```
Would be migrated to this:
```tsx
render(
<TestApiProvider apis={[[identityApiRef, mockIdentityApi]]}>
{...}
</TestApiProvider>
)
```
In cases where the `ApiProvider` is used in a more standalone way, for example to reuse a set of APIs across multiple tests, the `TestApiRegistry` can be used instead. Note that the `TestApiRegistry` only has a single static factory method, `.from()`, and it is slightly different from the existing `.from()` method on `ApiRegistry` in that it doesn't require the API pairs to be wrapped in an outer array.
Usage that looks like this:
```ts
const apis = ApiRegistry.with(
identityApiRef,
mockIdentityApi as unknown as IdentityApi,
).with(configApiRef, new ConfigReader({}));
```
OR like this:
```ts
const apis = ApiRegistry.from([
[identityApiRef, mockIdentityApi as unknown as IdentityApi],
[configApiRef, new ConfigReader({})],
]);
```
Would be migrated to this:
```ts
const apis = TestApiRegistry.from(
[identityApiRef, mockIdentityApi],
[configApiRef, new ConfigReader({})],
);
```
If your app is still using the `ApiRegistry` to construct the `apis` for `createApp`, we recommend that you move over to use the new method of supplying API factories instead, using `createApiFactory`.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/core-components': patch
---
Pin sidebar by default for easier navigation
@@ -6,6 +6,8 @@ on:
jobs:
sync:
if: github.repository == 'backstage/backstage' # prevent running on forks
runs-on: ubuntu-latest
strategy:
+2 -2
View File
@@ -1,7 +1,7 @@
services:
backstage:
image: tugboatqa/node:lts
expose: 7000
expose: 7007
default: true
commands:
init:
@@ -14,4 +14,4 @@ services:
- yarn workspace example-app build
start:
# wget the endpoint. Will retry every 2 seconds. 30 retries = 1m for service to come up. Plenty.
- wget -O /dev/null -o /dev/null --tries=30 --timeout=5 --retry-connrefused http://localhost:7000
- wget -O /dev/null -o /dev/null --tries=30 --timeout=5 --retry-connrefused http://localhost:7007
+2
View File
@@ -69,3 +69,5 @@
| [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. |
| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services throught templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. |
| [Epic Games](https://www.epicgames.com) | [Brian Jung](https://github.com/brian-at-epic), [Jeff Goldian](https://github.com/jeffgoldian-Epic) | Developer Portal: Service Catalog, Documentation, Software Templates and more making our internal teams' lives easier! |
| [Globo](https://globo.com) | [Carlos Gusmão](https://github.com/caeugusmao), [Guilherme Vierno](https://github.com/vierno), [Denis Aoki](https://github.com/dnsaoki2), [Maycon Dionisio](https://github.com/MayconDionisio), | Reduce the friction of accessing the information engineers need about Globo's digital services through a coherent and centralized experience. |
| [QBE](https://www.qbe.com/) | [Daniel Steel](https://github.com/danielsteelqbe), [Pete Jespers](https://github.com/petejespersqbe) | Developer portal allowing our global teams to explore and create applications, documentation and cloud infrastructure easily and quickly 🚀 |
+3 -1
View File
@@ -122,7 +122,9 @@ We use [changesets](https://github.com/atlassian/changesets) to help us prepare
Any time a patch, minor, or major change aligning to [Semantic Versioning](https://semver.org) is made to any published package in `packages/` or `plugins/`, a changeset should be used. It helps to align your change to the [Backstage stability index](https://backstage.io/docs/overview/stability-index) for the package you are changing, for example, when to provide additional clarity on deprecation or impacting changes which will then be included into CHANGELOGs.
In general, changesets are not needed for the documentation, build utilities, contributed samples in `contrib/`, or the [example `packages/app`](packages/app).
In general, changesets are only needed for changes to packages within `packages/` or `plugins/` directories, and only for the packages that are not marked as `private`. Changesets are also not needed for changes that do not affect the published version of each package, for example changes to tests or in-line source code comments.
Changesets **are** needed for new packages, as that is what triggers the package to be part of the next release.
### How to create a changeset
+2 -2
View File
@@ -27,9 +27,9 @@ backend:
# See authenticate-api-requests.md in the contrib docs for information on the format
# authorization:
# secret: ${BACKEND_SECRET}
baseUrl: http://localhost:7000
baseUrl: http://localhost:7007
listen:
port: 7000
port: 7007
database:
client: sqlite3
connection: ':memory:'
@@ -1,7 +1,7 @@
backend:
lighthouseHostname: {{ include "lighthouse.serviceName" . | quote }}
listen:
port: {{ .Values.appConfig.backend.listen.port | default 7000 }}
port: {{ .Values.appConfig.backend.listen.port | default 7007 }}
database:
client: {{ .Values.appConfig.backend.database.client | quote }}
connection:
+2 -2
View File
@@ -26,7 +26,7 @@ backend:
repository: martinaif/backstage-k8s-demo-backend
tag: 20210423T1550
pullPolicy: IfNotPresent
containerPort: 7000
containerPort: 7007
serviceType: ClusterIP
postgresCertMountEnabled: true
resources:
@@ -96,7 +96,7 @@ appConfig:
backend:
baseUrl: https://demo.example.com
listen:
port: 7000
port: 7007
cors:
origin: https://demo.example.com
database:
+5 -5
View File
@@ -9,8 +9,8 @@ docker_name_prefix := backstage-make
# to the host computer
frontend_port := 3000
frontend_host_port := 3000
backend_port := 7000
backend_host_port := 7000
backend_port := 7007
backend_host_port := 7007
# path to this "makefile"
my_dir_path = $(dir $(CURDIR)/$(firstword $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)))
@@ -191,10 +191,10 @@ test: check-tests
check: check-code check-docs check-type-dependencies check-styles
# run development instance
# BUG: the frontend seems to run on "$(backend_port)" (7000 default).
# BUG: the frontend seems to run on "$(backend_port)" (7007 default).
# The documentation states "This is going to start two things,
# the frontend (:3000) and the backend (:7000)."
# However, the frontend seems to end up running on 7000.
# the frontend (:3000) and the backend (:7007)."
# However, the frontend seems to end up running on 7007.
.PHONY: dev
dev: build
@docker run --rm -it \
@@ -22,6 +22,6 @@ spec:
image: spotify/backstage-backend:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 7000
- containerPort: 7007
name: backend
protocol: TCP
@@ -63,7 +63,7 @@ backend:
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 7000
port: 7007
ingress:
enabled: false
annotations:
@@ -30,6 +30,6 @@ spec:
component: backend
ports:
- name: backend
port: 7000
port: 7007
protocol: TCP
targetPort: backend
+1 -1
View File
@@ -1,5 +1,5 @@
{
"baseUrl": "http://localhost:7000",
"baseUrl": "http://localhost:7007",
"integrationFolder": "./src/integration",
"supportFile": "./src/support",
"fixturesFolder": "./src/fixtures",
@@ -1,6 +1,6 @@
---
id: adrs-adr012
title: ADR000: Use Luxon.toLocaleString and date/time presets
title: ADR012: Use Luxon.toLocaleString and date/time presets
description: Architecture Decision Record (ADR) for using Luxon's toLocaleString method and date/time presets for displaying dates and times
---
+1 -1
View File
@@ -229,7 +229,7 @@ name.
### Test the new provider
You can `curl -i localhost:7000/api/auth/providerA/start` and which should
You can `curl -i localhost:7007/api/auth/providerA/start` and which should
provide a `302` redirect with a `Location` header. Paste the url from that
header into a web browser and you should be able to trigger the authorization
flow.
+1 -1
View File
@@ -28,7 +28,7 @@ Name your integration and click on the `Create` button.
Settings for local development:
- Callback URL: `http://localhost:7000/api/auth/atlassian`
- Callback URL: `http://localhost:7007/api/auth/atlassian`
- Use rotating refresh tokens
- For permissions, you **must** enable `View user profile` for the currently
logged-in user, under `User identity API`
+1 -1
View File
@@ -17,7 +17,7 @@ provider that can authenticate users using OAuth.
- Application type: Single Page Web Application
4. Click on the Settings tab
5. Add under `Application URIs` > `Allowed Callback URLs`:
`http://localhost:7000/api/auth/auth0/handler/frame`
`http://localhost:7007/api/auth/auth0/handler/frame`
6. Click `Save Changes`
## Configuration
+1 -1
View File
@@ -20,7 +20,7 @@ Click Add Consumer.
Settings for local development:
- Application name: Backstage (or your custom app name)
- Callback URL: `http://localhost:7000/api/auth/bitbucket`
- Callback URL: `http://localhost:7007/api/auth/bitbucket`
- Other are optional
- (IMPORTANT) **Permissions: Account - Read, Workspace membership - Read**
+1 -1
View File
@@ -24,7 +24,7 @@ Settings for local development:
- Application name: Backstage (or your custom app name)
- Homepage URL: `http://localhost:3000`
- Authorization callback URL: `http://localhost:7000/api/auth/github`
- Authorization callback URL: `http://localhost:7007/api/auth/github`
## Configuration
+1 -1
View File
@@ -17,7 +17,7 @@ should point to your Backstage backend auth handler.
Settings for local development:
- Name: Backstage (or your custom app name)
- Redirect URI: `http://localhost:7000/api/auth/gitlab/handler/frame`
- Redirect URI: `http://localhost:7007/api/auth/gitlab/handler/frame`
- Scopes: read_user
## Configuration
+1 -1
View File
@@ -26,7 +26,7 @@ To support Google authentication, you must create OAuth credentials:
- `Name`: Backstage (or your custom app name)
- `Authorized JavaScript origins`: http://localhost:3000
- `Authorized Redirect URIs`:
http://localhost:7000/api/auth/google/handler/frame
http://localhost:7007/api/auth/google/handler/frame
7. Click Create
## Configuration
+1 -1
View File
@@ -21,7 +21,7 @@ To support Azure authentication, you must create an App Registration:
4. Register an application
- Name: Backstage (or your custom app name)
- Redirect URI: Web >
`http://localhost:7000/api/auth/microsoft/handler/frame`
`http://localhost:7007/api/auth/microsoft/handler/frame`
5. Navigate to **Certificates & secrets > New client secret** to create a secret
## Configuration
+2 -2
View File
@@ -22,8 +22,8 @@ To add Okta authentication, you must create an Application from Okta:
- `App integration name`: `Backstage` (or your custom app name)
- `Grant type`: `Authorization Code` & `Refresh Token`
- `Sign-in redirect URIs`:
`http://localhost:7000/api/auth/okta/handler/frame`
- `Sign-out redirect URIs`: `http://localhost:7000`
`http://localhost:7007/api/auth/okta/handler/frame`
- `Sign-out redirect URIs`: `http://localhost:7007`
- `Controlled access`: (select as appropriate)
- Click Save
+1 -1
View File
@@ -18,7 +18,7 @@ To support OneLogin authentication, you must create an Application:
3. Click Save
4. Go to the Configuration tab for the Application and set:
- `Login Url`: `http://localhost:3000`
- `Redirect URIs`: `http://localhost:7000/api/auth/onelogin/handler/frame`
- `Redirect URIs`: `http://localhost:7007/api/auth/onelogin/handler/frame`
5. Click Save
6. Go to the SSO tab for the Application and set:
- `Token Endpoint` > `Authentication Method`: `POST`
+2 -2
View File
@@ -16,8 +16,8 @@ app:
baseUrl: http://localhost:3000
backend:
listen: 0.0.0.0:7000
baseUrl: http://localhost:7000
listen: 0.0.0.0:7007
baseUrl: http://localhost:7007
organization:
name: CNCF
+4 -4
View File
@@ -105,11 +105,11 @@ docker image build . -f packages/backend/Dockerfile --tag backstage
To try out the image locally you can run the following:
```sh
docker run -it -p 7000:7000 backstage
docker run -it -p 7007:7007 backstage
```
You should then start to get logs in your terminal, and then you can open your
browser at `http://localhost:7000`
browser at `http://localhost:7007`
## Multi-stage Build
@@ -208,11 +208,11 @@ docker image build -t backstage .
To try out the image locally you can run the following:
```sh
docker run -it -p 7000:7000 backstage
docker run -it -p 7007:7007 backstage
```
You should then start to get logs in your terminal, and then you can open your
browser at `http://localhost:7000`
browser at `http://localhost:7007`
## Separate Frontend
+7 -7
View File
@@ -351,7 +351,7 @@ spec:
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 7000
containerPort: 7007
envFrom:
- secretRef:
name: postgres-secrets
@@ -361,11 +361,11 @@ spec:
# https://backstage.io/docs/plugins/observability#health-checks
# readinessProbe:
# httpGet:
# port: 7000
# port: 7007
# path: /healthcheck
# livenessProbe:
# httpGet:
# port: 7000
# port: 7007
# path: /healthcheck
```
@@ -449,7 +449,7 @@ spec:
```
The `selector` here is telling the Service which pods to target, and the port
mapping translates normal HTTP port 80 to the backend http port (7000) on the
mapping translates normal HTTP port 80 to the backend http port (7007) on the
pod.
Apply this Service to the Kubernetes cluster:
@@ -464,10 +464,10 @@ reveal**_, you can forward a local port to the service:
```shell
$ sudo kubectl port-forward --namespace=backstage svc/backstage 80:80
Forwarding from 127.0.0.1:80 -> 7000
Forwarding from 127.0.0.1:80 -> 7007
```
This shows port 7000 since `port-forward` doesn't _really_ support services, so
This shows port 7007 since `port-forward` doesn't _really_ support services, so
it cheats by looking up the first pod for a service and connecting to the mapped
pod port.
@@ -486,7 +486,7 @@ organization:
backend:
baseUrl: http://localhost
listen:
port: 7000
port: 7007
cors:
origin: http://localhost
```
+2 -2
View File
@@ -138,11 +138,11 @@ techdocs:
# (Optional and Legacy) TechDocs makes API calls to techdocs-backend using this URL. e.g. get docs of an entity, get metadata, etc.
# You don't have to specify this anymore.
requestUrl: http://localhost:7000/api/techdocs
requestUrl: http://localhost:7007/api/techdocs
# (Optional and Legacy) Just another route in techdocs-backend where TechDocs requests the static files from. This URL uses an HTTP middleware
# to serve files from either a local directory or an External storage provider.
# You don't have to specify this anymore.
storageUrl: http://localhost:7000/api/techdocs/static/docs
storageUrl: http://localhost:7007/api/techdocs/static/docs
```
+1 -1
View File
@@ -43,7 +43,7 @@ the project root. Make sure you have run the above mentioned commands first.
$ yarn dev
```
This is going to start two things, the frontend (:3000) and the backend (:7000).
This is going to start two things, the frontend (:3000) and the backend (:7007).
This should open a local instance of Backstage in your browser, otherwise open
one of the URLs printed in the terminal.
+1 -1
View File
@@ -37,7 +37,7 @@ guide to do a repository-based installation.
- `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
ports need to be opened: 3000, 7007
### Create your Backstage App
@@ -70,7 +70,7 @@ cd packages/backend
yarn start
```
That starts up a backend instance on port 7000.
That starts up a backend instance on port 7007.
In the other window, we will then launch the frontend. This command is run from
the project root, not inside the backend directory.
+1 -1
View File
@@ -21,7 +21,7 @@ externalDocs:
description: Backstage official documentation
url: https://github.com/backstage/backstage/blob/master/docs/README.md
servers:
- url: http://localhost:7000/api/auth/
- url: http://localhost:7007/api/auth/
tags:
- name: provider
description: List of endpoints per provider
+3 -3
View File
@@ -44,11 +44,11 @@ cd plugins/carmen-backend
yarn start
```
This will think for a bit, and then say `Listening on :7000`. In a different
This will think for a bit, and then say `Listening on :7007`. In a different
terminal window, now run
```sh
curl localhost:7000/carmen/health
curl localhost:7007/carmen/health
```
This should return `{"status":"ok"}`. Success! Press `Ctrl + c` to kill it
@@ -107,7 +107,7 @@ root), you should be able to fetch data from it.
```sh
# Note the extra /api here
curl localhost:7000/api/carmen/health
curl localhost:7007/api/carmen/health
```
This should return `{"status":"ok"}` like before. Success!
+1 -1
View File
@@ -2,5 +2,5 @@
"packages": ["packages/*", "plugins/*"],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "0.1.0"
"version": "0.0.0"
}
@@ -1,5 +1,5 @@
---
title: Starting Phase 2: The Service Catalog
title: 'Starting Phase 2: The Service Catalog'
author: Stefan Ålund, Spotify
authorURL: http://twitter.com/stalund
authorImageURL: https://pbs.twimg.com/profile_images/121166861/6919c047c0d0edaace78c3009b28e917-user-full-200-130.generated_400x400.jpg
@@ -1,5 +1,5 @@
---
title: Announcing TechDocs: Spotifys docs-like-code plugin for Backstage
title: 'Announcing TechDocs: Spotifys docs-like-code plugin for Backstage'
author: Gary Niemen, Spotify
authorURL: https://github.com/garyniemen
---
@@ -1,5 +1,5 @@
---
title: New Cost Insights plugin: The engineers solution to taming cloud costs
title: 'New Cost Insights plugin: The engineers solution to taming cloud costs'
author: Janisa Anandamohan, Spotify
authorURL: https://twitter.com/janisa_a
---
@@ -1,5 +1,5 @@
---
title: New Backstage feature: Kubernetes for Service owners
title: 'New Backstage feature: Kubernetes for Service owners'
author: Matthew Clarke, Spotify
authorURL: https://github.com/mclarke47
---
@@ -1,5 +1,5 @@
---
title: Announcing the Backstage Search platform: a customizable search tool built just for you
title: 'Announcing the Backstage Search platform: a customizable search tool built just for you'
author: Emma Indal, Spotify
authorURL: https://www.linkedin.com/in/emma-indal
---
@@ -14,7 +14,7 @@
* limitations under the License.
*/
const API_ENDPOINT = 'http://localhost:7000/api/search/query';
const API_ENDPOINT = 'http://localhost:7007/api/search/query';
describe('SearchPage', () => {
describe('Given a search context with a term, results, and filter values', () => {
+3 -3
View File
@@ -27,14 +27,14 @@ describe('App', () => {
data: {
app: {
title: 'Test',
support: { url: 'http://localhost:7000/support' },
support: { url: 'http://localhost:7007/support' },
},
backend: { baseUrl: 'http://localhost:7000' },
backend: { baseUrl: 'http://localhost:7007' },
lighthouse: {
baseUrl: 'http://localhost:3003',
},
techdocs: {
storageUrl: 'http://localhost:7000/api/techdocs/static/docs',
storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
},
},
context: 'test',
@@ -37,7 +37,7 @@ export class SingleHostDiscovery implements PluginEndpointDiscovery {
* for the internal one.
*
* The basePath defaults to `/api`, meaning the default full internal
* path for the `catalog` plugin will be `http://localhost:7000/api/catalog`.
* path for the `catalog` plugin will be `http://localhost:7007/api/catalog`.
*/
static fromConfig(config: Config, options?: { basePath?: string }) {
const basePath = options?.basePath ?? '/api';
@@ -40,7 +40,7 @@ import {
} from './config';
import { createHttpServer, createHttpsServer } from './hostFactory';
export const DEFAULT_PORT = 7000;
export const DEFAULT_PORT = 7007;
// '' is express default, which listens to all interfaces
const DEFAULT_HOST = '';
// taken from the helmet source code - don't seem to be exported
@@ -63,8 +63,8 @@ type CustomOrigin = (
* @example
* ```json
* {
* baseUrl: "http://localhost:7000",
* listen: "0.0.0.0:7000"
* baseUrl: "http://localhost:7007",
* listen: "0.0.0.0:7007"
* }
* ```
*/
+1 -1
View File
@@ -34,7 +34,7 @@ export type ServiceBuilder = {
*
* If no port is specified, the service will first look for an environment
* variable named PORT and use that if present, otherwise it picks a default
* port (7000).
* port (7007).
*
* @param port - The port to listen on
*/
+1 -1
View File
@@ -37,7 +37,7 @@ Substitute `x` for actual values, or leave them as
dummy values just to try out the backend without using the auth or sentry features.
You can also, instead of using dummy values for a huge number of environment variables, remove those config directly from app-config.yaml file located in the root folder.
The backend starts up on port 7000 per default.
The backend starts up on port 7007 per default.
### Debugging
+1
View File
@@ -95,6 +95,7 @@ async function getProjectConfig(targetPath, displayName) {
...(displayName && { displayName }),
rootDir: path.resolve(targetPath, 'src'),
coverageDirectory: path.resolve(targetPath, 'coverage'),
coverageProvider: 'v8',
collectCoverageFrom: ['**/*.{js,jsx,ts,tsx}', '!**/*.d.ts'],
moduleNameMapper: {
'\\.(css|less|scss|sss|styl)$': require.resolve('jest-css-modules'),
+5 -1
View File
@@ -46,7 +46,11 @@ function process(source, filePath) {
}
if (transforms) {
return transform(source, { transforms, filePath }).code;
return transform(source, {
transforms,
filePath,
disableESTransforms: true,
}).code;
}
return source;
@@ -18,7 +18,7 @@ import { getRootLogger } from '@backstage/backend-common';
import yn from 'yn';
import { startStandaloneServer } from './service/standaloneServer';
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000;
const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007;
const enableCors = yn(process.env.PLUGIN_CORS, { default: false });
const logger = getRootLogger();
@@ -1,5 +1,5 @@
import { createRouteRef } from '@backstage/core-plugin-api';
export const rootRouteRef = createRouteRef({
title: '{{ id }}',
id: '{{ id }}',
});
+6
View File
@@ -1,5 +1,11 @@
# @backstage/core-app-api
## 0.1.22
### Patch Changes
- Reverted the `createApp` TypeScript type to match the one before version `0.1.21`, as it was an accidental breaking change.
## 0.1.21
### Patch Changes
+5 -3
View File
@@ -27,6 +27,7 @@ import { BackstagePlugin } from '@backstage/core-plugin-api';
import { bitbucketAuthApiRef } from '@backstage/core-plugin-api';
import { ComponentType } from 'react';
import { ConfigReader } from '@backstage/config';
import { createApp as createApp_2 } from '@backstage/app-defaults';
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { ErrorApi } from '@backstage/core-plugin-api';
import { ErrorApiError } from '@backstage/core-plugin-api';
@@ -45,7 +46,6 @@ 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';
import { OptionalAppOptions } from '@backstage/app-defaults';
import { PendingAuthRequest } from '@backstage/core-plugin-api';
import { PluginOutput } from '@backstage/core-plugin-api';
import { ProfileInfo } from '@backstage/core-plugin-api';
@@ -126,7 +126,7 @@ export type ApiProviderProps = {
children: ReactNode;
};
// @public
// @public @deprecated
export class ApiRegistry implements ApiHolder {
constructor(apis: Map<string, unknown>);
// Warning: (ae-forgotten-export) The symbol "ApiRegistryBuilder" needs to be exported by the entry point index.d.ts
@@ -332,7 +332,9 @@ export type BootErrorPageProps = {
export { ConfigReader };
// @public @deprecated
export function createApp(options?: OptionalAppOptions): BackstageApp;
export function createApp(
options?: Parameters<typeof createApp_2>[0],
): BackstageApp & AppContext;
// @public
export function createSpecializedApp(options: AppOptions): BackstageApp;
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "@backstage/core-app-api",
"description": "Core app API used by Backstage apps",
"version": "0.1.21",
"version": "0.1.22",
"private": false,
"publishConfig": {
"access": "public",
@@ -26,10 +26,10 @@ describe('UrlPatternDiscovery', () => {
it('should use a plain pattern', async () => {
const discoveryApi = UrlPatternDiscovery.compile(
'http://localhost:7000/{{ pluginId }}',
'http://localhost:7007/{{ pluginId }}',
);
await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe(
'http://localhost:7000/my-plugin',
'http://localhost:7007/my-plugin',
);
});
@@ -30,7 +30,7 @@ export class UrlPatternDiscovery implements DiscoveryApi {
* interpolation done for the template is to replace instances of `{{pluginId}}`
* with the ID of the plugin being requested.
*
* Example pattern: `http://localhost:7000/api/{{ pluginId }}`
* Example pattern: `http://localhost:7007/api/{{ pluginId }}`
*/
static compile(pattern: string): UrlPatternDiscovery {
const parts = pattern.split(/\{\{\s*pluginId\s*\}\}/);
@@ -36,6 +36,7 @@ class ApiRegistryBuilder {
* A registry for utility APIs.
*
* @public
* @deprecated Will be removed, use {@link @backstage/test-utils#TestApiProvider} or {@link @backstage/test-utils#TestApiRegistry} instead.
*/
export class ApiRegistry implements ApiHolder {
static builder() {
+6 -6
View File
@@ -14,10 +14,8 @@
* limitations under the License.
*/
import {
createApp as createDefaultApp,
OptionalAppOptions,
} from '@backstage/app-defaults';
import { createApp as createDefaultApp } from '@backstage/app-defaults';
import { AppContext, BackstageApp } from './types';
/**
* Creates a new Backstage App.
@@ -26,7 +24,9 @@ import {
* @param options - A set of options for creating the app
* @public
*/
export function createApp(options?: OptionalAppOptions) {
export function createApp(
options?: Parameters<typeof createDefaultApp>[0],
): BackstageApp & AppContext {
// eslint-disable-next-line no-console
console.warn(
'DEPRECATION WARNING: The createApp function from @backstage/core-app-api will soon be removed, ' +
@@ -34,5 +34,5 @@ export function createApp(options?: OptionalAppOptions) {
'If you do not wish to use a standard app configuration but instead supply all options yourself ' +
' you can use createSpecializedApp from @backstage/core-app-api instead.',
);
return createDefaultApp(options);
return createDefaultApp(options) as BackstageApp & AppContext;
}
@@ -16,14 +16,15 @@
import React from 'react';
import { FeatureFlagged } from './FeatureFlagged';
import { render } from '@testing-library/react';
import { ApiProvider, ApiRegistry, LocalStorageFeatureFlags } from '../apis';
import { LocalStorageFeatureFlags } from '../apis';
import { TestApiProvider } from '@backstage/test-utils';
import { featureFlagsApiRef } from '@backstage/core-plugin-api';
const mockFeatureFlagsApi = new LocalStorageFeatureFlags();
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider apis={ApiRegistry.with(featureFlagsApiRef, mockFeatureFlagsApi)}>
<TestApiProvider apis={[[featureFlagsApiRef, mockFeatureFlagsApi]]}>
{children}
</ApiProvider>
</TestApiProvider>
);
describe('FeatureFlagged', () => {
@@ -17,17 +17,18 @@
import { render, RenderResult } from '@testing-library/react';
import React, { ReactNode } from 'react';
import { MemoryRouter, Route, Routes, useOutlet } from 'react-router-dom';
import { ApiProvider, ApiRegistry, LocalStorageFeatureFlags } from '../apis';
import { LocalStorageFeatureFlags } from '../apis';
import { featureFlagsApiRef } from '@backstage/core-plugin-api';
import { AppContext } from '../app';
import { AppContextProvider } from '../app/AppContext';
import { FlatRoutes } from './FlatRoutes';
import { TestApiProvider } from '@backstage/test-utils';
const mockFeatureFlagsApi = new LocalStorageFeatureFlags();
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider apis={ApiRegistry.with(featureFlagsApiRef, mockFeatureFlagsApi)}>
<TestApiProvider apis={[[featureFlagsApiRef, mockFeatureFlagsApi]]}>
{children}
</ApiProvider>
</TestApiProvider>
);
function makeRouteRenderer(node: ReactNode) {
@@ -17,78 +17,66 @@
import React from 'react';
import { AlertDisplay } from './AlertDisplay';
import { alertApiRef } from '@backstage/core-plugin-api';
import {
ApiProvider,
ApiRegistry,
AlertApiForwarder,
} from '@backstage/core-app-api';
import { AlertApiForwarder } from '@backstage/core-app-api';
import Observable from 'zen-observable';
import { renderInTestApp } from '@backstage/test-utils';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
const TEST_MESSAGE = 'TEST_MESSAGE';
describe('<AlertDisplay />', () => {
it('renders without exploding', async () => {
const apiRegistry = ApiRegistry.from([
[alertApiRef, new AlertApiForwarder()],
]);
const { queryByText } = await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<TestApiProvider apis={[[alertApiRef, new AlertApiForwarder()]]}>
<AlertDisplay />
</ApiProvider>,
</TestApiProvider>,
);
expect(queryByText(TEST_MESSAGE)).not.toBeInTheDocument();
});
it('renders with message', async () => {
const apiRegistry = ApiRegistry.from([
[
alertApiRef,
{
post() {},
alert$() {
return Observable.of({ message: TEST_MESSAGE });
},
},
],
]);
const { queryByText } = await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<TestApiProvider
apis={[
[
alertApiRef,
{
post() {},
alert$() {
return Observable.of({ message: TEST_MESSAGE });
},
},
],
]}
>
<AlertDisplay />
</ApiProvider>,
</TestApiProvider>,
);
expect(queryByText(TEST_MESSAGE)).toBeInTheDocument();
});
describe('with multiple messages', () => {
let apiRegistry: ApiRegistry;
beforeEach(() => {
apiRegistry = ApiRegistry.from([
[
alertApiRef,
{
post() {},
alert$() {
return Observable.of(
{ message: 'message one' },
{ message: 'message two' },
{ message: 'message three' },
);
},
const apis = [
[
alertApiRef,
{
post() {},
alert$() {
return Observable.of(
{ message: 'message one' },
{ message: 'message two' },
{ message: 'message three' },
);
},
],
]);
});
},
] as const,
] as const;
it('renders first message', async () => {
const { queryByText } = await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<TestApiProvider apis={apis}>
<AlertDisplay />
</ApiProvider>,
</TestApiProvider>,
);
expect(queryByText('message one')).toBeInTheDocument();
@@ -96,9 +84,9 @@ describe('<AlertDisplay />', () => {
it('renders a count of remaining messages', async () => {
const { queryByText } = await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<TestApiProvider apis={apis}>
<AlertDisplay />
</ApiProvider>,
</TestApiProvider>,
);
expect(queryByText('(2 older messages)')).toBeInTheDocument();
@@ -17,10 +17,9 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
import { act } from 'react-dom/test-utils';
import { renderInTestApp } from '@backstage/test-utils';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { CopyTextButton } from './CopyTextButton';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { errorApiRef, ErrorApi } from '@backstage/core-plugin-api';
import { errorApiRef } from '@backstage/core-plugin-api';
import { useCopyToClipboard } from 'react-use';
jest.mock('popper.js', () => {
@@ -51,22 +50,18 @@ const props = {
tooltipText: 'mockTooltip',
};
const apiRegistry = ApiRegistry.from([
[
errorApiRef,
{
post: jest.fn(),
error$: jest.fn(),
} as ErrorApi,
],
]);
const mockErrorApi = {
post: jest.fn(),
error$: jest.fn(),
};
const apis = [[errorApiRef, mockErrorApi] as const] as const;
describe('<CopyTextButton />', () => {
it('renders without exploding', async () => {
const { getByTitle, queryByText } = await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<TestApiProvider apis={apis}>
<CopyTextButton {...props} />
</ApiProvider>,
</TestApiProvider>,
);
expect(getByTitle('mockTooltip')).toBeInTheDocument();
expect(queryByText('mockTooltip')).not.toBeInTheDocument();
@@ -80,9 +75,9 @@ describe('<CopyTextButton />', () => {
spy.mockReturnValue([{}, copy]);
const rendered = await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<TestApiProvider apis={apis}>
<CopyTextButton {...props} />
</ApiProvider>,
</TestApiProvider>,
);
const button = rendered.getByTitle('mockTooltip');
fireEvent.click(button);
@@ -101,10 +96,10 @@ describe('<CopyTextButton />', () => {
spy.mockReturnValue([{ error }, jest.fn()]);
await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<TestApiProvider apis={apis}>
<CopyTextButton {...props} />
</ApiProvider>,
</TestApiProvider>,
);
expect(apiRegistry.get(errorApiRef)?.post).toHaveBeenCalledWith(error);
expect(mockErrorApi.post).toHaveBeenCalledWith(error);
});
});
@@ -18,12 +18,13 @@ import React from 'react';
import { DismissableBanner } from './DismissableBanner';
import Link from '@material-ui/core/Link';
import Typography from '@material-ui/core/Typography';
import { ApiProvider, ApiRegistry, WebStorage } from '@backstage/core-app-api';
import { WebStorage } from '@backstage/core-app-api';
import {
ErrorApi,
storageApiRef,
StorageApi,
} from '@backstage/core-plugin-api';
import { TestApiProvider } from '@backstage/test-utils';
export default {
title: 'Feedback/DismissableBanner',
@@ -37,47 +38,47 @@ const createWebStorage = (): StorageApi => {
return WebStorage.create({ errorApi });
};
const apis = ApiRegistry.from([[storageApiRef, createWebStorage()]]);
const apis = [[storageApiRef, createWebStorage()] as const];
export const Default = () => (
<div style={containerStyle}>
<ApiProvider apis={apis}>
<TestApiProvider apis={apis}>
<DismissableBanner
message="This is a dismissable banner"
variant="info"
id="default_dismissable"
/>
</ApiProvider>
</TestApiProvider>
</div>
);
export const Error = () => (
<div style={containerStyle}>
<ApiProvider apis={apis}>
<TestApiProvider apis={apis}>
<DismissableBanner
message="This is a dismissable banner with an error message"
variant="error"
id="error_dismissable"
/>
</ApiProvider>
</TestApiProvider>
</div>
);
export const EmojisIncluded = () => (
<div style={containerStyle}>
<ApiProvider apis={apis}>
<TestApiProvider apis={apis}>
<DismissableBanner
message="This is a dismissable banner with emojis: 🚀 💚 😆 "
variant="info"
id="emojis_dismissable"
/>
</ApiProvider>
</TestApiProvider>
</div>
);
export const WithLink = () => (
<div style={containerStyle}>
<ApiProvider apis={apis}>
<TestApiProvider apis={apis}>
<DismissableBanner
message={
<Typography>
@@ -90,29 +91,29 @@ export const WithLink = () => (
variant="info"
id="linked_dismissable"
/>
</ApiProvider>
</TestApiProvider>
</div>
);
export const Fixed = () => (
<div style={containerStyle}>
<ApiProvider apis={apis}>
<TestApiProvider apis={apis}>
<DismissableBanner
message="This is a dismissable banner with a fixed position fixed at the bottom of the page"
variant="info"
id="fixed_dismissable"
fixed
/>
</ApiProvider>
</TestApiProvider>
</div>
);
export const Warning = () => (
<div style={containerStyle}>
<ApiProvider apis={apis}>
<TestApiProvider apis={apis}>
<DismissableBanner
message="This is a dismissable banner with a warning message"
variant="warning"
id="warning_dismissable"
/>
</ApiProvider>
</TestApiProvider>
</div>
);
@@ -16,13 +16,17 @@
import React from 'react';
import { fireEvent } from '@testing-library/react';
import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
import {
renderWithEffects,
TestApiRegistry,
wrapInTestApp,
} from '@backstage/test-utils';
import { DismissableBanner } from './DismissableBanner';
import { ApiRegistry, ApiProvider, WebStorage } from '@backstage/core-app-api';
import { ApiProvider, WebStorage } from '@backstage/core-app-api';
import { storageApiRef, StorageApi } from '@backstage/core-plugin-api';
describe('<DismissableBanner />', () => {
let apis: ApiRegistry;
let apis: TestApiRegistry;
const mockErrorApi = { post: jest.fn(), error$: jest.fn() };
const createWebStorage = (): StorageApi => {
return WebStorage.create({
@@ -31,7 +35,7 @@ describe('<DismissableBanner />', () => {
};
beforeEach(() => {
apis = ApiRegistry.from([[storageApiRef, createWebStorage()]]);
apis = TestApiRegistry.from([storageApiRef, createWebStorage()]);
});
it('renders the message and the popover', async () => {
@@ -16,8 +16,11 @@
import React from 'react';
import { render, fireEvent, waitFor } from '@testing-library/react';
import { MockAnalyticsApi, wrapInTestApp } from '@backstage/test-utils';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import {
MockAnalyticsApi,
TestApiProvider,
wrapInTestApp,
} from '@backstage/test-utils';
import { analyticsApiRef } from '@backstage/core-plugin-api';
import { isExternalUri, Link } from './Link';
import { Route, Routes } from 'react-router';
@@ -48,11 +51,11 @@ describe('<Link />', () => {
const { getByText } = render(
wrapInTestApp(
<ApiProvider apis={ApiRegistry.from([[analyticsApiRef, analyticsApi]])}>
<TestApiProvider apis={[[analyticsApiRef, analyticsApi]]}>
<Link to="/test" onClick={customOnClick}>
{linkText}
</Link>
</ApiProvider>,
</TestApiProvider>,
),
);
@@ -20,9 +20,9 @@ import { ErrorBoundary } from './ErrorBoundary';
import {
MockErrorApi,
renderInTestApp,
TestApiProvider,
withLogCollector,
} from '@backstage/test-utils';
import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { errorApiRef } from '@backstage/core-plugin-api';
type BombProps = {
@@ -41,25 +41,25 @@ const Bomb = ({ shouldThrow }: BombProps) => {
describe('<ErrorBoundary/>', () => {
it('should render error boundary with and without error', async () => {
const { error } = await withLogCollector(['error'], async () => {
const apis = ApiRegistry.with(errorApiRef, new MockErrorApi());
const errorApi = new MockErrorApi();
const { rerender, queryByRole, getByRole, getByText } =
await renderInTestApp(
<ApiProvider apis={apis}>
<TestApiProvider apis={[[errorApiRef, errorApi]]}>
<ErrorBoundary>
<Bomb />
</ErrorBoundary>
</ApiProvider>,
</TestApiProvider>,
);
expect(queryByRole('alert')).not.toBeInTheDocument();
expect(getByText(/working component/i)).toBeInTheDocument();
rerender(
<ApiProvider apis={apis}>
<TestApiProvider apis={[[errorApiRef, errorApi]]}>
<ErrorBoundary>
<Bomb shouldThrow />
</ErrorBoundary>
</ApiProvider>,
</TestApiProvider>,
);
expect(getByRole('alert')).toBeInTheDocument();
@@ -15,13 +15,9 @@
*/
import React from 'react';
import { renderInTestApp } from '@backstage/test-utils';
import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { Header } from './Header';
import {
ApiRegistry,
ConfigReader,
ApiProvider,
} from '@backstage/core-app-api';
import { ConfigReader } from '@backstage/core-app-api';
import { configApiRef } from '@backstage/core-plugin-api';
jest.mock('react-helmet', () => {
@@ -72,14 +68,12 @@ describe('<Header/>', () => {
});
it('should use app.title', async () => {
const apiRegistry = ApiRegistry.with(
configApiRef,
new ConfigReader({ app: { title: 'Blah' } }),
);
const rendered = await renderInTestApp(
<ApiProvider apis={apiRegistry}>
<TestApiProvider
apis={[[configApiRef, new ConfigReader({ app: { title: 'Blah' } })]]}
>
<Header title="Title" type="tool" typeLink="/tool" />,
</ApiProvider>,
</TestApiProvider>,
);
rendered.getAllByText(/Title | Blah/);
});
@@ -14,16 +14,12 @@
* limitations under the License.
*/
import { renderWithEffects } from '@backstage/test-utils';
import { renderWithEffects, TestApiProvider } from '@backstage/test-utils';
import { HomepageTimer } from './HomepageTimer';
import React from 'react';
import { lightTheme } from '@backstage/theme';
import { ThemeProvider } from '@material-ui/core/styles';
import {
ApiProvider,
ApiRegistry,
ConfigReader,
} from '@backstage/core-app-api';
import { ConfigReader } from '@backstage/core-app-api';
import { ConfigApi, configApiRef } from '@backstage/core-plugin-api';
it('changes default timezone to GMT', async () => {
@@ -41,9 +37,9 @@ it('changes default timezone to GMT', async () => {
const rendered = await renderWithEffects(
<ThemeProvider theme={lightTheme}>
<ApiProvider apis={ApiRegistry.from([[configApiRef, configApi]])}>
<TestApiProvider apis={[[configApiRef, configApi]]}>
<HomepageTimer />
</ApiProvider>
</TestApiProvider>
</ThemeProvider>,
);
@@ -49,7 +49,7 @@ export type SidebarPinStateContextType = {
export const SidebarPinStateContext = createContext<SidebarPinStateContextType>(
{
isPinned: false,
isPinned: true,
toggleSidebarPinState: () => {},
},
);
@@ -24,10 +24,10 @@ export const LocalStorage = {
try {
value = JSON.parse(
window.localStorage.getItem(LocalStorageKeys.SIDEBAR_PIN_STATE) ||
'false',
'true',
);
} catch {
return false;
return true;
}
return !!value;
},
@@ -18,11 +18,8 @@ import { useElementFilter } from './useElementFilter';
import { renderHook } from '@testing-library/react-hooks';
import { attachComponentData } from './componentData';
import { featureFlagsApiRef } from '../apis';
import {
ApiProvider,
ApiRegistry,
LocalStorageFeatureFlags,
} from '@backstage/core-app-api';
import { LocalStorageFeatureFlags } from '@backstage/core-app-api';
import { TestApiProvider } from '@backstage/test-utils';
const WRAPPING_COMPONENT_KEY = 'core.blob.testing';
const INNER_COMPONENT_KEY = 'core.blob2.testing';
@@ -45,9 +42,9 @@ const FeatureFlagComponent = (_props: {
attachComponentData(FeatureFlagComponent, 'core.featureFlagged', true);
const mockFeatureFlagsApi = new LocalStorageFeatureFlags();
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
<ApiProvider apis={ApiRegistry.with(featureFlagsApiRef, mockFeatureFlagsApi)}>
<TestApiProvider apis={[[featureFlagsApiRef, mockFeatureFlagsApi]]}>
{children}
</ApiProvider>
</TestApiProvider>
);
describe('useElementFilter', () => {
@@ -1,8 +1,8 @@
app:
# Should be the same as backend.baseUrl when using the `app-backend` plugin
baseUrl: http://localhost:7000
baseUrl: http://localhost:7007
backend:
baseUrl: http://localhost:7000
baseUrl: http://localhost:7007
listen:
port: 7000
port: 7007
@@ -10,9 +10,9 @@ backend:
# See authenticate-api-requests.md in the contrib docs for information on the format
# authorization:
# secret: ${BACKEND_SECRET}
baseUrl: http://localhost:7000
baseUrl: http://localhost:7007
listen:
port: 7000
port: 7007
csp:
connect-src: ["'self'", 'http:', 'https:']
# Content-Security-Policy directives follow the Helmet format: https://helmetjs.github.io/#reference
@@ -10,9 +10,9 @@ describe('App', () => {
{
data: {
app: { title: 'Test' },
backend: { baseUrl: 'http://localhost:7000' },
backend: { baseUrl: 'http://localhost:7007' },
techdocs: {
storageUrl: 'http://localhost:7000/api/techdocs/static/docs',
storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
},
},
context: 'test',
@@ -2,10 +2,13 @@ import React from 'react';
import { makeStyles, Theme, Grid, List, Paper } from '@material-ui/core';
import { CatalogResultListItem } from '@backstage/plugin-catalog';
import { DocsResultListItem } from '@backstage/plugin-techdocs';
import {
SearchBar,
SearchFilter,
SearchResult,
SearchType,
DefaultResultListItem,
} from '@backstage/plugin-search';
import { Content, Header, Page } from '@backstage/core-components';
@@ -39,6 +42,11 @@ const SearchPage = () => {
</Grid>
<Grid item xs={3}>
<Paper className={classes.filters}>
<SearchType
values={['techdocs', 'software-catalog']}
name="type"
defaultValue="software-catalog"
/>
<SearchFilter.Select
className={classes.filter}
name="kind"
@@ -64,6 +72,13 @@ const SearchPage = () => {
result={document}
/>
);
case 'techdocs':
return (
<DocsResultListItem
key={document.location}
result={document}
/>
);
default:
return (
<DefaultResultListItem
@@ -36,7 +36,7 @@ yarn start
Substitute `x` for actual values, or leave them as dummy values just to try out
the backend without using the auth or sentry features.
The backend starts up on port 7000 per default.
The backend starts up on port 7007 per default.
## Populating The Catalog
@@ -6,6 +6,7 @@ import {
} from '@backstage/plugin-search-backend-node';
import { PluginEnvironment } from '../types';
import { DefaultCatalogCollator } from '@backstage/plugin-catalog-backend';
import { DefaultTechDocsCollator } from '@backstage/plugin-techdocs-backend';
export default async function createPlugin({
logger,
@@ -18,7 +19,7 @@ export default async function createPlugin({
const indexBuilder = new IndexBuilder({ logger, searchEngine });
// Collators are responsible for gathering documents known to plugins. This
// particular collator gathers entities from the software catalog.
// collator gathers entities from the software catalog.
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: DefaultCatalogCollator.fromConfig(config, {
@@ -27,6 +28,12 @@ export default async function createPlugin({
}),
});
// collator gathers entities from techdocs.
indexBuilder.addCollator({
defaultRefreshIntervalSeconds: 600,
collator: DefaultTechDocsCollator.fromConfig(config, { discovery, logger }),
});
// The scheduler controls when documents are gathered from collators and sent
// to the search engine for indexing.
const { scheduler } = await indexBuilder.build();
+1 -1
View File
@@ -471,7 +471,7 @@ async function testBackendStart(appDir: string, isPostgres: boolean) {
print('Try to fetch entities from the backend');
// Try fetch entities, should be ok
await fetch('http://localhost:7000/api/catalog/entities').then(res =>
await fetch('http://localhost:7007/api/catalog/entities').then(res =>
res.json(),
);
print('Entities fetched successfully');
@@ -5,8 +5,8 @@ app:
baseUrl: http://localhost:3000
backend:
baseUrl: http://localhost:7000
baseUrl: http://localhost:7007
techdocs:
builder: 'external'
requestUrl: http://localhost:7000/api
requestUrl: http://localhost:7007/api
@@ -26,9 +26,9 @@ describe('App', () => {
{
data: {
app: { title: 'Test' },
backend: { baseUrl: 'http://localhost:7000' },
backend: { baseUrl: 'http://localhost:7007' },
techdocs: {
storageUrl: 'http://localhost:7000/api/techdocs/static/docs',
storageUrl: 'http://localhost:7007/api/techdocs/static/docs',
},
},
context: 'test',
+15 -4
View File
@@ -33,8 +33,11 @@ export type ErrorLike = {
[unknownKeys: string]: unknown;
};
// @public @deprecated
export type ErrorResponse = ErrorResponseBody;
// @public
export type ErrorResponse = {
export type ErrorResponseBody = {
error: SerializedError;
request?: {
method: string;
@@ -65,19 +68,27 @@ export class NotFoundError extends CustomErrorBase {}
// @public
export class NotModifiedError extends CustomErrorBase {}
// @public
// @public @deprecated
export function parseErrorResponse(response: Response): Promise<ErrorResponse>;
// @public
export function parseErrorResponseBody(
response: Response,
): Promise<ErrorResponseBody>;
// @public
export class ResponseError extends Error {
// @deprecated
constructor(props: {
message: string;
response: Response;
data: ErrorResponse;
data: ErrorResponseBody;
cause: Error;
});
readonly body: ErrorResponseBody;
readonly cause: Error;
readonly data: ErrorResponse;
// @deprecated
get data(): ErrorResponseBody;
static fromResponse(response: Response): Promise<ResponseError>;
readonly response: Response;
}
+21 -9
View File
@@ -14,16 +14,16 @@
* limitations under the License.
*/
import { deserializeError } from '../serialization';
import {
parseErrorResponse,
ErrorResponse,
deserializeError,
} from '../serialization';
ErrorResponseBody,
parseErrorResponseBody,
} from '../serialization/response';
/**
* An error thrown as the result of a failed server request.
*
* The server is expected to respond on the ErrorResponse format.
* The server is expected to respond on the ErrorResponseBody format.
*
* @public
*/
@@ -39,7 +39,7 @@ export class ResponseError extends Error {
/**
* The parsed JSON error body, as sent by the server.
*/
readonly data: ErrorResponse;
readonly body: ErrorResponseBody;
/**
* The Error cause, as seen by the remote server. This is parsed out of the
@@ -60,7 +60,7 @@ export class ResponseError extends Error {
* been consumed before.
*/
static async fromResponse(response: Response): Promise<ResponseError> {
const data = await parseErrorResponse(response);
const data = await parseErrorResponseBody(response);
const status = data.response.statusCode || response.status;
const statusText = data.error.name || response.statusText;
@@ -75,16 +75,28 @@ export class ResponseError extends Error {
});
}
/**
* @deprecated will be removed.
**/
constructor(props: {
message: string;
response: Response;
data: ErrorResponse;
data: ErrorResponseBody;
cause: Error;
}) {
super(props.message);
this.name = 'ResponseError';
this.response = props.response;
this.data = props.data;
this.body = props.data;
this.cause = props.cause;
}
/**
* The parsed JSON error body, as sent by the server.
* @deprecated use body instead.
*/
get data(): ErrorResponseBody {
// eslint-disable-next-line no-console
console.warn('ErrorResponse.data is deprecated, use .body instead.');
return this.body;
}
}
+2 -2
View File
@@ -16,5 +16,5 @@
export { deserializeError, serializeError, stringifyError } from './error';
export type { SerializedError } from './error';
export { parseErrorResponse } from './response';
export type { ErrorResponse } from './response';
export { parseErrorResponse, parseErrorResponseBody } from './response';
export type { ErrorResponse, ErrorResponseBody } from './response';
@@ -14,11 +14,11 @@
* limitations under the License.
*/
import { parseErrorResponse, ErrorResponse } from './response';
import { parseErrorResponseBody, ErrorResponseBody } from './response';
describe('parseErrorResponse', () => {
describe('parseErrorResponseBody', () => {
it('handles the happy path', async () => {
const body: ErrorResponse = {
const body: ErrorResponseBody = {
error: { name: 'Fours', message: 'Expected fives', stack: 'lines' },
request: { method: 'GET', url: '/' },
response: { statusCode: 444 },
@@ -31,13 +31,13 @@ describe('parseErrorResponse', () => {
headers: new Headers({ 'Content-Type': 'application/json' }),
};
await expect(parseErrorResponse(response as Response)).resolves.toEqual(
await expect(parseErrorResponseBody(response as Response)).resolves.toEqual(
body,
);
});
it('uses request header and text body when wrong content type, even if parsable', async () => {
const body: ErrorResponse = {
const body: ErrorResponseBody = {
error: { name: 'Threes', message: 'Expected twos' },
request: { method: 'GET', url: '/' },
response: { statusCode: 333 },
@@ -50,19 +50,21 @@ describe('parseErrorResponse', () => {
headers: new Headers({ 'Content-Type': 'not-application/not-json' }),
};
await expect(parseErrorResponse(response as Response)).resolves.toEqual({
error: {
name: 'Unknown',
message: `Request failed with status 444 Fours, ${JSON.stringify(
body,
)}`,
await expect(parseErrorResponseBody(response as Response)).resolves.toEqual(
{
error: {
name: 'Error',
message: `Request failed with status 444 Fours, ${JSON.stringify(
body,
)}`,
},
response: { statusCode: 444 },
},
response: { statusCode: 444 },
});
);
});
it('uses request header and text body when not parsable', async () => {
const body: ErrorResponse = {
const body: ErrorResponseBody = {
error: { name: 'Threes', message: 'Expected twos' },
request: { method: 'GET', url: '/' },
response: { statusCode: 333 },
@@ -75,15 +77,17 @@ describe('parseErrorResponse', () => {
headers: new Headers({ 'Content-Type': 'application/json' }),
};
await expect(parseErrorResponse(response as Response)).resolves.toEqual({
error: {
name: 'Unknown',
message: `Request failed with status 444 Fours, ${JSON.stringify(
body,
).substring(1)}`,
await expect(parseErrorResponseBody(response as Response)).resolves.toEqual(
{
error: {
name: 'Error',
message: `Request failed with status 444 Fours, ${JSON.stringify(
body,
).substring(1)}`,
},
response: { statusCode: 444 },
},
response: { statusCode: 444 },
});
);
});
it('uses request header when failing to get body', async () => {
@@ -96,12 +100,14 @@ describe('parseErrorResponse', () => {
headers: new Headers({ 'Content-Type': 'application/json' }),
};
await expect(parseErrorResponse(response as Response)).resolves.toEqual({
error: {
name: 'Unknown',
message: `Request failed with status 444 Fours`,
await expect(parseErrorResponseBody(response as Response)).resolves.toEqual(
{
error: {
name: 'Error',
message: `Request failed with status 444 Fours`,
},
response: { statusCode: 444 },
},
response: { statusCode: 444 },
});
);
});
});
+30 -3
View File
@@ -20,8 +20,16 @@ import { SerializedError } from './error';
* A standard shape of JSON data returned as the body of backend errors.
*
* @public
* @deprecated - Use {@link ErrorResponseBody} instead.
*/
export type ErrorResponse = {
export type ErrorResponse = ErrorResponseBody;
/**
* A standard shape of JSON data returned as the body of backend errors.
*
* @public
*/
export type ErrorResponseBody = {
/** Details of the error that was caught */
error: SerializedError;
@@ -51,10 +59,29 @@ export type ErrorResponse = {
*
* @public
* @param response - The response of a failed request
* @deprecated - Use {@link parseErrorResponseBody} instead.
*/
export async function parseErrorResponse(
response: Response,
): Promise<ErrorResponse> {
return parseErrorResponseBody(response);
}
/**
* Attempts to construct an ErrorResponseBody out of a failed server request.
* Assumes that the response has already been checked to be not ok. This
* function consumes the body of the response, and assumes that it hasn't
* been consumed before.
*
* The code is forgiving, and constructs a useful synthetic body as best it can
* if the response body wasn't on the expected form.
*
* @public
* @param response - The response of a failed request
*/
export async function parseErrorResponseBody(
response: Response,
): Promise<ErrorResponseBody> {
try {
const text = await response.text();
if (text) {
@@ -73,7 +100,7 @@ export async function parseErrorResponse(
return {
error: {
name: 'Unknown',
name: 'Error',
message: `Request failed with status ${response.status} ${response.statusText}, ${text}`,
},
response: {
@@ -87,7 +114,7 @@ export async function parseErrorResponse(
return {
error: {
name: 'Unknown',
name: 'Error',
message: `Request failed with status ${response.status} ${response.statusText}`,
},
response: {
+6 -6
View File
@@ -50,7 +50,7 @@ const oauthRequestApi = builder.add(
builder.add(
googleAuthApiRef,
GoogleAuth.create({
apiOrigin: 'http://localhost:7000',
apiOrigin: 'http://localhost:7007',
basePath: '/auth/',
oauthRequestApi,
}),
@@ -59,7 +59,7 @@ builder.add(
builder.add(
githubAuthApiRef,
GithubAuth.create({
apiOrigin: 'http://localhost:7000',
apiOrigin: 'http://localhost:7007',
basePath: '/auth/',
oauthRequestApi,
}),
@@ -68,7 +68,7 @@ builder.add(
builder.add(
gitlabAuthApiRef,
GitlabAuth.create({
apiOrigin: 'http://localhost:7000',
apiOrigin: 'http://localhost:7007',
basePath: '/auth/',
oauthRequestApi,
}),
@@ -77,7 +77,7 @@ builder.add(
builder.add(
oktaAuthApiRef,
OktaAuth.create({
apiOrigin: 'http://localhost:7000',
apiOrigin: 'http://localhost:7007',
basePath: '/auth/',
oauthRequestApi,
}),
@@ -86,7 +86,7 @@ builder.add(
builder.add(
auth0AuthApiRef,
Auth0Auth.create({
apiOrigin: 'http://localhost:7000',
apiOrigin: 'http://localhost:7007',
basePath: '/auth/',
oauthRequestApi,
}),
@@ -95,7 +95,7 @@ builder.add(
builder.add(
oauth2ApiRef,
OAuth2.create({
apiOrigin: 'http://localhost:7000',
apiOrigin: 'http://localhost:7007',
basePath: '/auth/',
oauthRequestApi,
}),
@@ -36,7 +36,7 @@ export default async function serve(cmd: Command) {
// a backstage app, we define app.baseUrl in the app-config.yaml.
// Hence, it is complicated to make this configurable.
const backstagePort = 3000;
const backstageBackendPort = 7000;
const backstageBackendPort = 7007;
const mkdocsDockerAddr = `http://0.0.0.0:${cmd.mkdocsPort}`;
const mkdocsLocalAddr = `http://127.0.0.1:${cmd.mkdocsPort}`;
@@ -55,9 +55,9 @@ export class PublisherConfig {
return new ConfigReader({
// This backend config is not used at all. Just something needed a create a mock discovery instance.
backend: {
baseUrl: 'http://localhost:7000',
baseUrl: 'http://localhost:7007',
listen: {
port: 7000,
port: 7007,
},
},
techdocs: {

Some files were not shown because too many files have changed in this diff Show More