diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000000..59847fd65f
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,18 @@
+version: 2
+updates:
+ - package-ecosystem: npm
+ directory: '/'
+ schedule:
+ interval: daily
+ time: '04:00'
+ open-pull-requests-limit: 5
+ labels:
+ - dependencies
+ - package-ecosystem: npm
+ directory: '/microsite/'
+ schedule:
+ interval: daily
+ time: '04:00'
+ open-pull-requests-limit: 2
+ labels:
+ - dependencies
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2246a1dc09..0a094a10b3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -76,7 +76,7 @@ jobs:
run: yarn lerna -- run lint --since origin/master
- name: type checking and declarations
- run: yarn tsc --incremental false
+ run: yarn tsc:full
- name: build changed packages
if: ${{ steps.yarn-lock.outcome == 'success' }}
diff --git a/.github/workflows/master-win.yml b/.github/workflows/master-win.yml
index 5951721f68..3f65cf9aec 100644
--- a/.github/workflows/master-win.yml
+++ b/.github/workflows/master-win.yml
@@ -43,7 +43,7 @@ jobs:
run: yarn lerna -- run lint
- name: type checking and declarations
- run: yarn tsc --incremental false
+ run: yarn tsc:full
- name: verify type dependencies
run: yarn lint:type-deps
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
index b5c4fdf370..469ef20cda 100644
--- a/.github/workflows/master.yml
+++ b/.github/workflows/master.yml
@@ -51,7 +51,7 @@ jobs:
run: yarn lerna -- run lint
- name: type checking and declarations
- run: yarn tsc --incremental false
+ run: yarn tsc:full
- name: build
run: yarn build
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 20ae9a414d..9f9235172b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,11 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re
> Collect changes for the next release below
+- Material-UI: Bumped to 4.11.0, which is the version that create-app will
+ resolve to, because we wanted to get the renaming of ExpansionPanel to
+ Accordion into place. This gets rid of a lot of console deprecation warnings
+ in newly scaffolded apps.
+
- The backend plugin
[service builder](https://github.com/spotify/backstage/blob/master/packages/backend-common/src/service/lib/ServiceBuilderImpl.ts)
no longer adds `express.json()` automatically to all routes. While convenient
diff --git a/app-config.yaml b/app-config.yaml
index da2f5f2d11..2d7f31e72d 100644
--- a/app-config.yaml
+++ b/app-config.yaml
@@ -12,7 +12,16 @@ backend:
# See README.md in the proxy-backend plugin for information on the configuration format
proxy:
- '/circleci/api': https://circleci.com/api/v1.1
+ '/circleci/api':
+ target: https://circleci.com/api/v1.1
+ changeOrigin: true
+ pathRewrite:
+ '^/proxy/circleci/api/': '/'
+ headers:
+ Circle-Token:
+ $secret:
+ env: CIRCLECI_AUTH_TOKEN
+
'/jenkins/api':
target: http://localhost:8080
headers:
@@ -25,6 +34,7 @@ organization:
techdocs:
storageUrl: http://localhost:7000/techdocs/static/docs
+ requestUrl: http://localhost:7000/techdocs/docs
sentry:
organization: spotify
diff --git a/docs/api/utility-apis.md b/docs/api/utility-apis.md
index 0e09da3f83..37cde87262 100644
--- a/docs/api/utility-apis.md
+++ b/docs/api/utility-apis.md
@@ -17,7 +17,7 @@ Utility APIs. While the `createPlugin` API is focused on the initialization
plugins and the app, the Utility APIs provide ways for plugins to communicate
during their entire life cycle.
-## Usage
+## Consuming APIs
Each Utility API is tied to an `ApiRef` instance, which is a global singleton
object without any additional state or functionality, its only purpose is to
@@ -55,51 +55,112 @@ from any component inside Backstage, including the ones in `@backstage/core`.
The only requirement is that they are beneath the `AppProvider` in the react
tree.
-## Registering Utility API Implementations
+## Supplying APIs
-The Backstage App is responsible for providing implementations for all Utility
-APIs required by plugins. The example app in this repo registers its APIs inside
-[src/apis.ts](/packages/app/src/apis.ts). Here's an example of how to wire up
-the `ErrorApi` inside an app:
+### API Factories
+
+APIs are registered in the form of `ApiFactories`, which encapsulate the process
+of instantiating an API. It is a collection of three things: the `ApiRef` of the
+API to instantiate, a list of all required dependencies, and a factory function
+that returns a new API instance.
+
+For example, this is the default `ApiFactory` for the `ErrorApi`:
```ts
-import {
- ApiRegistry,
- createApp,
- alertApiRef,
- errorApiRef,
- AlertApiForwarder,
- ErrorApiForwarder,
- ErrorAlerter,
- ConfigApi,
-} from '@backstage/core';
-
-const apis = (config: ConfigApi) => {
- const builder = ApiRegistry.builder();
-
- // The alert API is a self-contained implementation that shows alerts to the user.
- const alertApi = builder.add(alertApiRef, new AlertApiForwarder());
-
- // The error API uses the alert API to send error notifications to the user.
- builder.add(errorApiRef, new ErrorAlerter(alertApi, new ErrorApiForwarder()));
- return builder.build();
-};
-
-const app = createApp({
- apis,
- // ... other config
+createApiFactory({
+ api: errorApiRef,
+ deps: { alertApi: alertApiRef },
+ factory: ({ alertApi }) =>
+ new ErrorAlerter(alertApi, new ErrorApiForwarder()),
});
```
-The `ApiRegistry` is used to register all Utility APIs in the app and associate
-them with `ApiRef`s. It implements the `ApiHolder` interface, which enables it
-to provide an API implementation given an `ApiRef`.
+In this example the `errorApiRef` is our API, which encapsulates the `ErrorApi`
+type. The `alertApiRef` is our single dependency, which we give the name
+`alertApi`, and is then passed on to the factory function, which returns an
+implementation of the `ErrorApi`.
-Note that our `ErrorApi` implementation depends on another Utility API, the
-`AlertApi`. This is the method with which APIs can depend on other APIs, using
-manual dependency injection at the initialization of the app. In general, if you
-want to depend on another Utility API in an implementation of an API, you import
-the type for that API and make it a constructor parameter.
+The `createApiFactory` function is a thin wrapper that enables TypeScript type
+inference. You may notice that there are no type annotations in the above
+example, and that is because we're able to infer all types from the `ApiRef`s.
+TypeScript will make sure that the return value of the `factory` function
+matches the type embedded in `api`'s `ApiRef`, in this case the `ErrorApi`. It
+will also match the types between the `deps` and the parameters of the `factory`
+function, again using the type embedded within the `ApiRef`s.
+
+## Registering API Factories
+
+The responsibility for adding Utility APIs to a Backstage app lies in three
+different locations: the Backstage core library, each plugin included in the
+app, and the app itself.
+
+### Core APIs
+
+Starting with the Backstage core library, it provides implementation for all of
+the core APIs. The core APIs are the ones exported by `@backstage/core`, such as
+the `errorApiRef` and `configApiRef`. You can find a full list of them
+[here](../reference/utility-apis/README.md).
+
+The core APIs are loaded for any app created with `createApp` from
+`@backstage/core`, which means that there is no step that needs to be taken to
+include these APIs in an app.
+
+### Plugin APIs
+
+In addition to the core APIs, plugins can define and export their own APIs.
+While doing so they should usually also provide default implementations of their
+own APIs, for example, the `catalog` plugin exports `catalogApiRef`, and also
+supplies a default `ApiFactory` of that API using the `CatalogClient`. There is
+one restriction to plugin-provided API Factories: plugins may not supply
+factories for core APIs, trying to do so will cause the app to crash.
+
+Plugins supply their APIs through the `apis` option of `createPlugin`, for
+example:
+
+```ts
+export const plugin = createPlugin({
+ id: 'techdocs',
+ apis: [
+ createApiFactory({
+ api: techdocsStorageApiRef,
+ deps: { configApi: configApiRef },
+ factory({ configApi }) {
+ return new TechDocsStorageApi({
+ apiOrigin: configApi.getString('techdocs.storageUrl'),
+ });
+ },
+ }),
+ ],
+});
+```
+
+### App APIs
+
+Lastly, the app itself is the final point where APIs can be added, and what has
+the final say in what APIs will be loaded at runtime. The app may override the
+factories for any of the core or plugin APIs, with the exception of the config,
+app theme, and identity APIs. These are static APIs that are tied into the
+`createApp` implementation, and therefore not possible to override.
+
+Overriding APIs is useful for apps that want to switch out behavior to tailor it
+to their environment. In some cases plugins may also export multiple
+implementations of the same API, where they each have their own different
+requirements on for example backend storage and surrounding environment.
+
+Supplying APIs to the app works just like for plugins:
+
+```ts
+const app = createApp({
+ apis: [
+ /* ApiFactories */
+ ],
+ // ... other options
+});
+```
+
+A common pattern is to export a list of all APIs from `apis.ts`, next to
+`App.tsx`. See the [example app in this repo](../../packages/app/src/apis.ts)
+for an example.
## Custom implementations of Utility APIs
@@ -127,19 +188,33 @@ implement the `ErrorApi`, as it is checked by the type embedded in the
## Defining custom Utility APIs
-The pattern for plugins defining their own Utility APIs is not fully established
-yet. The current way is for the plugin to export its own `ApiRef` and type for
-the API, along with one or more implementations. It is then up to the app to
-import, and register those APIs. See for example the
-[lighthouse](/plugins/lighthouse/src/api.ts) or
-[graphiql](/plugins/graphiql/src/lib/api/types.ts) plugins for examples of this.
+Plugins are free to define their own Utility APIs. Simply define the TypeScript
+interface for the API, and create an `ApiRef` using `createApiRef` exported from
+`@backstage/core`. Also be sure to provide at least one implementation of the
+API, and to declare a default factory for the API in `createPlugin`.
-The goal is to make this process a bit smoother, but that requires work in other
-parts of Backstage, like configuration management. So it remains as a TODO. If
-you have more questions regarding this, or have an idea for an API that you want
-to share outside your plugin, hit us up in
-[GitHub issues](https://github.com/spotify/backstage/issues/new/choose) or the
-[Backstage Discord server](https://discord.gg/EBHEGzX).
+Custom Utility APIs can be either public or private, which it is up to the
+plugin to choose. Private APIs do not expose an external API surface, and it's
+therefore possible to make breaking changes to the API without affecting other
+users of the plugin. If an API is made public however, it opens up for other
+plugins to make use of the API, and it also makes it possible for users for your
+plugin to override the API in the app. It is however important to maintain
+backwards compatibility of public APIs, as you may otherwise break apps that are
+using your plugin.
+
+To make an API public, simply export the `ApiRef` of the API, and any associated
+types. To make an API private, just avoid exporting the `ApiRef`, but still be
+sure to supply a default factory to `createPlugin`.
+
+Private APIs are useful for plugins that want to depend on other APIs outside of
+React components, but not have to expose an entire API surface to maintain. When
+using private APIs, it is fine to use the `typeof` of an implementing class as
+the type parameter passed to `createApiRef`, while public APIs should always
+define a separate TypeScript interface type.
+
+Plugins may depend on APIs from other plugins, both in React components and as
+dependencies to API factories. Do however be sure to not cause circular
+dependencies between plugins.
## Architecture
diff --git a/docs/architecture-decisions/adr002-default-catalog-file-format.md b/docs/architecture-decisions/adr002-default-catalog-file-format.md
index c1cb719bf5..af22d33020 100644
--- a/docs/architecture-decisions/adr002-default-catalog-file-format.md
+++ b/docs/architecture-decisions/adr002-default-catalog-file-format.md
@@ -68,7 +68,7 @@ metadata:
lifecycle: production
example.com/service-discovery-name: frobsawesome
annotations:
- circleci.com/project-slug: gh/example-org/frobs-awesome
+ circleci.com/project-slug: github/example-org/frobs-awesome
spec:
type: service
```
diff --git a/docs/architecture-decisions/index.md b/docs/architecture-decisions/index.md
index 91d2c668d7..0e9ff21812 100644
--- a/docs/architecture-decisions/index.md
+++ b/docs/architecture-decisions/index.md
@@ -4,7 +4,7 @@ title: Architecture Decision Records (ADR)
sidebar_label: Overview
---
-The substantial architecture decisions made in the Backstage project lives here.
+The substantial architecture decisions made in the Backstage project live here.
For more information about ADRs, when to write them, and why, please see
[this blog post](https://engineering.atspotify.com/2020/04/14/when-should-i-write-an-architecture-decision-record/).
diff --git a/docs/assets/getting-started/create-app_output.png b/docs/assets/getting-started/create-app_output.png
index 52dcc13097..caa39ec6d2 100644
Binary files a/docs/assets/getting-started/create-app_output.png and b/docs/assets/getting-started/create-app_output.png differ
diff --git a/docs/features/software-catalog/descriptor-format.md b/docs/features/software-catalog/descriptor-format.md
index e90792c628..fa4b8c23da 100644
--- a/docs/features/software-catalog/descriptor-format.md
+++ b/docs/features/software-catalog/descriptor-format.md
@@ -34,7 +34,7 @@ software catalog API.
"annotations": {
"backstage.io/managed-by-location": "file:/tmp/component-info.yaml",
"example.com/service-discovery": "artistweb",
- "circleci.com/project-slug": "gh/example-org/artist-website"
+ "circleci.com/project-slug": "github/example-org/artist-website"
},
"description": "The place to be, for great artists",
"etag": "ZjU2MWRkZWUtMmMxZS00YTZiLWFmMWMtOTE1NGNiZDdlYzNk",
@@ -66,7 +66,7 @@ metadata:
system: public-websites
annotations:
example.com/service-discovery: artistweb
- circleci.com/project-slug: gh/example-org/artist-website
+ circleci.com/project-slug: github/example-org/artist-website
tags:
- java
spec:
diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md
index 07b22808cd..9b5de16472 100644
--- a/docs/features/software-catalog/index.md
+++ b/docs/features/software-catalog/index.md
@@ -1,7 +1,7 @@
---
id: software-catalog-overview
title: Backstage Service Catalog (alpha)
-sidebar_label: Backstage Service Catalog
+sidebar_label: Overview
---
## What is a Service Catalog?
diff --git a/docs/features/software-catalog/installation.md b/docs/features/software-catalog/installation.md
index 397abcc6b0..85598a2025 100644
--- a/docs/features/software-catalog/installation.md
+++ b/docs/features/software-catalog/installation.md
@@ -1,4 +1,7 @@
-# Installing in your Backstage App
+---
+id: installation
+title: Installing in your Backstage App
+---
The catalog plugin comes in two packages, `@backstage/plugin-catalog` and
`@backstage/plugin-catalog-backend`. Each has their own installation steps,
diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md
index b594cfce2a..8989fa35eb 100644
--- a/docs/features/software-templates/index.md
+++ b/docs/features/software-templates/index.md
@@ -1,6 +1,7 @@
---
id: software-templates-index
-title: Software Templates
+title: Backstage Software Templates
+sidebar_label: Overview
---
The Software Templates part of Backstage is a tool that can help you create
diff --git a/docs/features/software-templates/installation.md b/docs/features/software-templates/installation.md
index 73b4f20359..50c2fd303c 100644
--- a/docs/features/software-templates/installation.md
+++ b/docs/features/software-templates/installation.md
@@ -1,4 +1,7 @@
-# Installing in your Backstage App
+---
+id: installation
+title: Installing in your Backstage App
+---
The scaffolder plugin comes in two packages, `@backstage/plugin-scaffolder` and
`@backstage/plugin-scaffolder-backend`. Each has their own installation steps,
diff --git a/docs/features/techdocs/getting-started.md b/docs/features/techdocs/getting-started.md
index 414b3baae8..33edfecbe8 100644
--- a/docs/features/techdocs/getting-started.md
+++ b/docs/features/techdocs/getting-started.md
@@ -28,7 +28,7 @@ installed:
To create a new Backstage application for TechDocs, run the following command:
```bash
-npx @backstage/cli create-app
+npx @backstage/create-app
```
You will then be prompted to enter a name for your application. Once that's
@@ -74,17 +74,28 @@ export { plugin as TechDocs } from '@backstage/plugin-techdocs';
### Setting the configuration
TechDocs allows for configuration of the docs storage URL through your
-`app-config` file.
+`app-config` file. We provide two different values to be configured,
+`requestUrl` and `storageUrl`. The `requestUrl` is what the reader will request
+its data from, and `storageUrl` is where the backend can find the stored
+documentation.
-The default storage URL:
+The default storage and request URLs:
```yaml
techdocs:
storageUrl: http://localhost:7000/techdocs/static/docs
+ requestUrl: http://localhost:7000/techdocs/docs
```
-If you want to configure this to point to another storage URL, change the value
-of `storageUrl`.
+If you want `techdocs-backend` to manage building and publishing you want
+`requestUrl` to point to the default value (or wherever `techdocs-backend` is
+hosted). `storageUrl` should be where your publisher publishes your docs. Using
+the default `LocalPublish` that is the default value.
+
+If you have a setup where you are not using `techdocs-backend` for managing
+building and publishing of your documentation you want to change the
+`requestUrl` to point to your storage. In this case `storageUrl` is not
+required.
## Run Backstage locally
diff --git a/docs/getting-started/configure-app-with-plugins.md b/docs/getting-started/configure-app-with-plugins.md
index a0d2c27f89..515b52afa7 100644
--- a/docs/getting-started/configure-app-with-plugins.md
+++ b/docs/getting-started/configure-app-with-plugins.md
@@ -3,4 +3,34 @@ id: configure-app-with-plugins
title: Configuring App with plugins
---
+## Adding existing plugins to your app
+
Coming soon!
+
+### Adding a plugin page to the Sidebar
+
+In a standard Backstage app created with
+[@backstage/create-app](./create-an-app.md), the sidebar is managed inside
+`packages/app/src/sidebar.tsx`. The file exports the entire `Sidebar` element of
+your app, which you can extend with additional entries by adding new
+`SidebarItem` elements.
+
+For example, if you install the `api-docs` plugin, a matching `SidebarItem`
+could be something like this:
+
+```tsx
+// Import icon from MUI
+import ExtensionIcon from '@material-ui/icons/Extension';
+
+// ... inside the AppSidebar component
+ ;
+```
+
+You can also use your own SVGs directly as icon components. Just make sure they
+are sized according to the Material UI's
+[SvgIcon](https://material-ui.com/api/svg-icon/) default of 24x24px, and set the
+extension to `.icon.svg`. For example:
+
+```ts
+import InternalToolIcon from './internal-tool.icon.svg';
+```
diff --git a/docs/getting-started/create-an-app.md b/docs/getting-started/create-an-app.md
index fe22935002..cebf3f5de2 100644
--- a/docs/getting-started/create-an-app.md
+++ b/docs/getting-started/create-an-app.md
@@ -15,7 +15,13 @@ To create a Backstage app, you will need to have
[NodeJS](https://nodejs.org/en/download/) Active LTS Release installed
(currently v12).
-With `npx`:
+Backstage provides a utility for creating new apps. It guides you through the
+initial setup of selecting the name of the app and a database for the backend.
+The database options are either SQLite or PostgreSQL, where the latter requires
+you to set up a separate database instance. If in doubt, choose SQLite, but
+don't worry about the choice, it's easy to change later!
+
+The easiest way to run the create app package is with `npx`:
```bash
npx @backstage/create-app
@@ -25,50 +31,45 @@ This will create a new Backstage App inside the current folder. The name of the
app-folder is the name that was provided when prompted.
-
+
Inside that directory, it will generate all the files and folder structure
needed for you to run your app.
-### Folder structure
+### General folder structure
+
+Below is a simplified layout of the files and folders generated when creating an
+app.
```
app
-├── README.md
+├── app-config.yaml
├── lerna.json
├── package.json
-├── prettier.config.js
-├── tsconfig.json
-├── packages
-│ └── app
-│ ├── package.json
-│ ├── tsconfig.json
-│ ├── public
-│ │ └── ...
-│ └── src
-│ ├── App.test.tsx
-│ ├── App.tsx
-│ ├── index.tsx
-│ ├── plugins.ts
-│ └── setupTests.ts
-└── plugins
- └── welcome
- ├── README.md
- ├── package.json
- ├── tsconfig.json
- └── src
- ├── index.ts
- ├── plugin.test.ts
- ├── plugin.ts
- ├── setupTests.ts
- └── components
- ├── Timer
- │ └── ...
- └── WelcomePage
- └── ...
+└── packages
+ ├── app
+ └── backend
```
+- **app-config.yaml**: Main configuration file for the app. See
+ [Configuration](https://backstage.io/docs/conf/) for more information.
+- **lerna.json**: Contains information about workspaces and other lerna
+ configuration needed for the monorepo setup.
+- **package.json**: Root package.json for the project. _Note: Be sure that you
+ don't add any npm dependencies here as they probably should be installed in
+ the intended workspace rather than in the root._
+- **packages/**: Lerna leaf packages or "workspaces". Everything here is going
+ to be a separate package, managed by lerna.
+- **packages/app/**: An fully functioning Backstage frontend app, that acts as a
+ good starting point for you to get to know Backstage.
+- **packages/backend/**: We include a backend that helps power features such as
+ [Authentication](https://backstage.io/docs/auth/),
+ [Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview),
+ [Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index)
+ and [TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview)
+ amongst other things.
+
## Run the app
When the installation is complete you can open the app folder and start the app.
@@ -80,3 +81,13 @@ yarn start
_When `yarn start` is ready it should open up a browser window displaying your
app, if not you can navigate to `http://localhost:3000`._
+
+In most cases you will want to start the backend as well, as it is required for
+the catalog to work, along with many other plugins.
+
+To start the backend, open a separate terminal session and run the following in
+the root directory:
+
+```bash
+yarn workspace backend start
+```
diff --git a/docs/getting-started/development-environment.md b/docs/getting-started/development-environment.md
index 421ce6d2c5..9c75c8eabf 100644
--- a/docs/getting-started/development-environment.md
+++ b/docs/getting-started/development-environment.md
@@ -65,6 +65,7 @@ yarn storybook # Start local storybook, useful for working on components in @bac
yarn workspace @backstage/plugin-welcome start # Serve welcome plugin only, also supports --check
yarn tsc # Run typecheck, use --watch for watch mode
+yarn tsc:full # Run full type checking, for example without skipLibCheck, use in CI
yarn build # Build published versions of packages, depends on tsc
diff --git a/docs/reference/utility-apis/AlertApi.md b/docs/reference/utility-apis/AlertApi.md
index 3490c1900e..d000a6d6a2 100644
--- a/docs/reference/utility-apis/AlertApi.md
+++ b/docs/reference/utility-apis/AlertApi.md
@@ -1,7 +1,7 @@
# AlertApi
The AlertApi type is defined at
-[packages/core-api/src/apis/definitions/AlertApi.ts:29](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AlertApi.ts#L29).
+[packages/core-api/src/apis/definitions/AlertApi.ts:29](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AlertApi.ts#L29).
The following Utility API implements this type: [alertApiRef](./README.md#alert)
@@ -38,7 +38,7 @@ export type AlertMessage = {
Defined at
-[packages/core-api/src/apis/definitions/AlertApi.ts:19](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AlertApi.ts#L19).
+[packages/core-api/src/apis/definitions/AlertApi.ts:19](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AlertApi.ts#L19).
Referenced by: [post](#post), [alert\$](#alert).
@@ -67,7 +67,7 @@ export type Observable<T> = {
Defined at
-[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
Referenced by: [alert\$](#alert).
@@ -86,7 +86,7 @@ export type Observer<T> = {
Defined at
-[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
Referenced by: [Observable](#observable).
@@ -109,6 +109,6 @@ export type Subscription = {
Defined at
-[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
Referenced by: [Observable](#observable).
diff --git a/docs/reference/utility-apis/AppThemeApi.md b/docs/reference/utility-apis/AppThemeApi.md
index eda1bafef0..e7d5296ceb 100644
--- a/docs/reference/utility-apis/AppThemeApi.md
+++ b/docs/reference/utility-apis/AppThemeApi.md
@@ -1,7 +1,7 @@
# AppThemeApi
The AppThemeApi type is defined at
-[packages/core-api/src/apis/definitions/AppThemeApi.ts:50](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AppThemeApi.ts#L50).
+[packages/core-api/src/apis/definitions/AppThemeApi.ts:50](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AppThemeApi.ts#L50).
The following Utility API implements this type:
[appThemeApiRef](./README.md#apptheme)
@@ -76,7 +76,7 @@ export type AppTheme = {
Defined at
-[packages/core-api/src/apis/definitions/AppThemeApi.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AppThemeApi.ts#L24).
+[packages/core-api/src/apis/definitions/AppThemeApi.ts:24](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AppThemeApi.ts#L24).
Referenced by: [getInstalledThemes](#getinstalledthemes).
@@ -87,7 +87,7 @@ export type BackstagePalette = Palette & Palette
Defined at
-[packages/theme/src/types.ts:67](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/theme/src/types.ts#L67).
+[packages/theme/src/types.ts:70](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/theme/src/types.ts#L70).
Referenced by: [BackstageTheme](#backstagetheme).
@@ -100,7 +100,7 @@ export interface BackstageTheme extends Theme {
Defined at
-[packages/theme/src/types.ts:70](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/theme/src/types.ts#L70).
+[packages/theme/src/types.ts:73](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/theme/src/types.ts#L73).
Referenced by: [AppTheme](#apptheme).
@@ -129,7 +129,7 @@ export type Observable<T> = {
Defined at
-[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
Referenced by: [activeThemeId\$](#activethemeid).
@@ -148,7 +148,7 @@ export type Observer<T> = {
Defined at
-[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
Referenced by: [Observable](#observable).
@@ -204,7 +204,7 @@ type PaletteAdditions = {
Defined at
-[packages/theme/src/types.ts:23](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/theme/src/types.ts#L23).
+[packages/theme/src/types.ts:23](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/theme/src/types.ts#L23).
Referenced by: [BackstagePalette](#backstagepalette).
@@ -227,6 +227,6 @@ export type Subscription = {
Defined at
-[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
Referenced by: [Observable](#observable).
diff --git a/docs/reference/utility-apis/BackstageIdentityApi.md b/docs/reference/utility-apis/BackstageIdentityApi.md
index f75918a8b3..87318ecff5 100644
--- a/docs/reference/utility-apis/BackstageIdentityApi.md
+++ b/docs/reference/utility-apis/BackstageIdentityApi.md
@@ -1,16 +1,20 @@
# BackstageIdentityApi
The BackstageIdentityApi type is defined at
-[packages/core-api/src/apis/definitions/auth.ts:144](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L144).
+[packages/core-api/src/apis/definitions/auth.ts:144](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L144).
The following Utility APIs implement this type:
+- [auth0AuthApiRef](./README.md#auth0auth)
+
- [githubAuthApiRef](./README.md#githubauth)
- [gitlabAuthApiRef](./README.md#gitlabauth)
- [googleAuthApiRef](./README.md#googleauth)
+- [microsoftAuthApiRef](./README.md#microsoftauth)
+
- [oktaAuthApiRef](./README.md#oktaauth)
## Members
@@ -62,7 +66,7 @@ export type AuthRequestOptions = {
Defined at
-[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L40).
+[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L40).
Referenced by: [getBackstageIdentity](#getbackstageidentity).
@@ -83,6 +87,6 @@ export type BackstageIdentity = {
Defined at
-[packages/core-api/src/apis/definitions/auth.ts:157](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L157).
+[packages/core-api/src/apis/definitions/auth.ts:157](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L157).
Referenced by: [getBackstageIdentity](#getbackstageidentity).
diff --git a/docs/reference/utility-apis/Config.md b/docs/reference/utility-apis/Config.md
index c0264489fc..9374fb9962 100644
--- a/docs/reference/utility-apis/Config.md
+++ b/docs/reference/utility-apis/Config.md
@@ -1,13 +1,19 @@
# Config
The Config type is defined at
-[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L32).
+[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L32).
The following Utility API implements this type:
[configApiRef](./README.md#config)
## Members
+### has()
+
+
+has(key: string): boolean
+
+
### keys()
@@ -17,13 +23,13 @@ keys(): string[]
### get()
-get(key: string): JsonValue
+get(key?: string): JsonValue
### getOptional()
-getOptional(key: string): JsonValue | undefined
+getOptional(key?: string): JsonValue | undefined
### getConfig()
@@ -106,10 +112,12 @@ These types are part of the API declaration, but may not be unique to this API.
export type Config = {
+ has(key: string): boolean;
+
keys(): string[];
- get(key: string): JsonValue ;
- getOptional(key: string): JsonValue | undefined;
+ get(key?: string): JsonValue ;
+ getOptional(key?: string): JsonValue | undefined;
getConfig(key: string): Config;
getOptionalConfig(key: string): Config | undefined;
@@ -132,7 +140,7 @@ export type Config = {
Defined at
-[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L32).
+[packages/config/src/types.ts:32](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L32).
Referenced by: [getConfig](#getconfig), [getOptionalConfig](#getoptionalconfig),
[getConfigArray](#getconfigarray),
@@ -145,7 +153,7 @@ export type JsonArray = JsonValue []
Defined at
-[packages/config/src/types.ts:18](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L18).
+[packages/config/src/types.ts:18](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L18).
Referenced by: [JsonValue](#jsonvalue).
@@ -156,7 +164,7 @@ export type JsonObject = { [key in string]?: JsonValue
Defined at
-[packages/config/src/types.ts:17](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L17).
+[packages/config/src/types.ts:17](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L17).
Referenced by: [JsonValue](#jsonvalue).
@@ -173,7 +181,7 @@ export type JsonValue =
Defined at
-[packages/config/src/types.ts:19](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/config/src/types.ts#L19).
+[packages/config/src/types.ts:19](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/config/src/types.ts#L19).
Referenced by: [get](#get), [getOptional](#getoptional),
[JsonObject](#jsonobject), [JsonArray](#jsonarray), [Config](#config).
diff --git a/docs/reference/utility-apis/DiscoveryApi.md b/docs/reference/utility-apis/DiscoveryApi.md
new file mode 100644
index 0000000000..39902789cd
--- /dev/null
+++ b/docs/reference/utility-apis/DiscoveryApi.md
@@ -0,0 +1,24 @@
+# DiscoveryApi
+
+The DiscoveryApi type is defined at
+[packages/core-api/src/apis/definitions/DiscoveryApi.ts:30](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/DiscoveryApi.ts#L30).
+
+The following Utility API implements this type:
+[discoveryApiRef](./README.md#discovery)
+
+## Members
+
+### getBaseUrl()
+
+Returns the HTTP base backend URL for a given plugin, without a trailing slash.
+
+This method must always be called just before making a request. as opposed to
+fetching the URL when constructing an API client. That is to ensure that more
+flexible routing patterns can be supported.
+
+For example, asking for the URL for `auth` may return something like
+`https://backstage.example.com/api/auth`
+
+
+getBaseUrl(pluginId: string): Promise<string>
+
diff --git a/docs/reference/utility-apis/ErrorApi.md b/docs/reference/utility-apis/ErrorApi.md
index a630e65592..c05f060ec3 100644
--- a/docs/reference/utility-apis/ErrorApi.md
+++ b/docs/reference/utility-apis/ErrorApi.md
@@ -1,7 +1,7 @@
# ErrorApi
The ErrorApi type is defined at
-[packages/core-api/src/apis/definitions/ErrorApi.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ErrorApi.ts#L53).
+[packages/core-api/src/apis/definitions/ErrorApi.ts:53](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ErrorApi.ts#L53).
The following Utility API implements this type: [errorApiRef](./README.md#error)
@@ -41,7 +41,7 @@ type Error = {
Defined at
-[packages/core-api/src/apis/definitions/ErrorApi.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ErrorApi.ts#L24).
+[packages/core-api/src/apis/definitions/ErrorApi.ts:24](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ErrorApi.ts#L24).
Referenced by: [post](#post), [error\$](#error).
@@ -58,7 +58,7 @@ export type ErrorContext = {
Defined at
-[packages/core-api/src/apis/definitions/ErrorApi.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ErrorApi.ts#L33).
+[packages/core-api/src/apis/definitions/ErrorApi.ts:33](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ErrorApi.ts#L33).
Referenced by: [post](#post), [error\$](#error).
@@ -87,7 +87,7 @@ export type Observable<T> = {
Defined at
-[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
Referenced by: [error\$](#error).
@@ -106,7 +106,7 @@ export type Observer<T> = {
Defined at
-[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
Referenced by: [Observable](#observable).
@@ -129,6 +129,6 @@ export type Subscription = {
Defined at
-[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
Referenced by: [Observable](#observable).
diff --git a/docs/reference/utility-apis/FeatureFlagsApi.md b/docs/reference/utility-apis/FeatureFlagsApi.md
index 16e30a1c60..5efdb176a4 100644
--- a/docs/reference/utility-apis/FeatureFlagsApi.md
+++ b/docs/reference/utility-apis/FeatureFlagsApi.md
@@ -1,7 +1,7 @@
# FeatureFlagsApi
The FeatureFlagsApi type is defined at
-[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:41](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L41).
+[packages/core-api/src/apis/definitions/FeatureFlagsApi.ts:41](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L41).
The following Utility API implements this type:
[featureFlagsApiRef](./README.md#featureflags)
diff --git a/docs/reference/utility-apis/IdentityApi.md b/docs/reference/utility-apis/IdentityApi.md
index f26ad4b6b3..4e37ad5300 100644
--- a/docs/reference/utility-apis/IdentityApi.md
+++ b/docs/reference/utility-apis/IdentityApi.md
@@ -1,7 +1,7 @@
# IdentityApi
The IdentityApi type is defined at
-[packages/core-api/src/apis/definitions/IdentityApi.ts:22](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/IdentityApi.ts#L22).
+[packages/core-api/src/apis/definitions/IdentityApi.ts:22](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/IdentityApi.ts#L22).
The following Utility API implements this type:
[identityApiRef](./README.md#identity)
@@ -76,6 +76,6 @@ export type ProfileInfo = {
Defined at
-[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L172).
+[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L172).
Referenced by: [getProfile](#getprofile).
diff --git a/docs/reference/utility-apis/OAuthApi.md b/docs/reference/utility-apis/OAuthApi.md
index 766124ccb4..e73d5f645f 100644
--- a/docs/reference/utility-apis/OAuthApi.md
+++ b/docs/reference/utility-apis/OAuthApi.md
@@ -1,7 +1,7 @@
# OAuthApi
The OAuthApi type is defined at
-[packages/core-api/src/apis/definitions/auth.ts:67](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L67).
+[packages/core-api/src/apis/definitions/auth.ts:67](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L67).
The following Utility APIs implement this type:
@@ -11,6 +11,8 @@ The following Utility APIs implement this type:
- [googleAuthApiRef](./README.md#googleauth)
+- [microsoftAuthApiRef](./README.md#microsoftauth)
+
- [oauth2ApiRef](./README.md#oauth2)
- [oktaAuthApiRef](./README.md#oktaauth)
@@ -88,7 +90,7 @@ export type AuthRequestOptions = {
Defined at
-[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L40).
+[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L40).
Referenced by: [getAccessToken](#getaccesstoken).
@@ -114,6 +116,6 @@ export type OAuthScope = string | string[]
Defined at
-[packages/core-api/src/apis/definitions/auth.ts:38](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L38).
+[packages/core-api/src/apis/definitions/auth.ts:38](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L38).
Referenced by: [getAccessToken](#getaccesstoken).
diff --git a/docs/reference/utility-apis/OAuthRequestApi.md b/docs/reference/utility-apis/OAuthRequestApi.md
index b3e0d0d979..c6b9e09189 100644
--- a/docs/reference/utility-apis/OAuthRequestApi.md
+++ b/docs/reference/utility-apis/OAuthRequestApi.md
@@ -1,7 +1,7 @@
# OAuthRequestApi
The OAuthRequestApi type is defined at
-[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:99](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L99).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:99](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L99).
The following Utility API implements this type:
[oauthRequestApiRef](./README.md#oauthrequest)
@@ -72,7 +72,7 @@ export type AuthProvider = {
Defined at
-[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:27](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L27).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:27](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L27).
Referenced by: [AuthRequesterOptions](#authrequesteroptions),
[PendingAuthRequest](#pendingauthrequest).
@@ -96,7 +96,7 @@ export type AuthRequester<AuthResponse> = (
Defined at
-[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:66](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L66).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:66](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L66).
Referenced by: [createAuthRequester](#createauthrequester).
@@ -121,7 +121,7 @@ export type AuthRequesterOptions<AuthResponse> = {
Defined at
-[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:43](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L43).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:43](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L43).
Referenced by: [createAuthRequester](#createauthrequester).
@@ -150,7 +150,7 @@ export type Observable<T> = {
Defined at
-[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
Referenced by: [authRequest\$](#authrequest).
@@ -169,7 +169,7 @@ export type Observer<T> = {
Defined at
-[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
Referenced by: [Observable](#observable).
@@ -204,7 +204,7 @@ export type PendingAuthRequest = {
Defined at
-[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:77](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L77).
+[packages/core-api/src/apis/definitions/OAuthRequestApi.ts:77](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L77).
Referenced by: [authRequest\$](#authrequest).
@@ -227,6 +227,6 @@ export type Subscription = {
Defined at
-[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
Referenced by: [Observable](#observable).
diff --git a/docs/reference/utility-apis/OpenIdConnectApi.md b/docs/reference/utility-apis/OpenIdConnectApi.md
index 7bc571ecb9..41a3247af6 100644
--- a/docs/reference/utility-apis/OpenIdConnectApi.md
+++ b/docs/reference/utility-apis/OpenIdConnectApi.md
@@ -1,12 +1,16 @@
# OpenIdConnectApi
The OpenIdConnectApi type is defined at
-[packages/core-api/src/apis/definitions/auth.ts:104](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L104).
+[packages/core-api/src/apis/definitions/auth.ts:104](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L104).
The following Utility APIs implement this type:
+- [auth0AuthApiRef](./README.md#auth0auth)
+
- [googleAuthApiRef](./README.md#googleauth)
+- [microsoftAuthApiRef](./README.md#microsoftauth)
+
- [oauth2ApiRef](./README.md#oauth2)
- [oktaAuthApiRef](./README.md#oktaauth)
@@ -70,6 +74,6 @@ export type AuthRequestOptions = {
Defined at
-[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L40).
+[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L40).
Referenced by: [getIdToken](#getidtoken).
diff --git a/docs/reference/utility-apis/ProfileInfoApi.md b/docs/reference/utility-apis/ProfileInfoApi.md
index 68b0dea194..09c0f88f83 100644
--- a/docs/reference/utility-apis/ProfileInfoApi.md
+++ b/docs/reference/utility-apis/ProfileInfoApi.md
@@ -1,16 +1,20 @@
# ProfileInfoApi
The ProfileInfoApi type is defined at
-[packages/core-api/src/apis/definitions/auth.ts:127](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L127).
+[packages/core-api/src/apis/definitions/auth.ts:127](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L127).
The following Utility APIs implement this type:
+- [auth0AuthApiRef](./README.md#auth0auth)
+
- [githubAuthApiRef](./README.md#githubauth)
- [gitlabAuthApiRef](./README.md#gitlabauth)
- [googleAuthApiRef](./README.md#googleauth)
+- [microsoftAuthApiRef](./README.md#microsoftauth)
+
- [oauth2ApiRef](./README.md#oauth2)
- [oktaAuthApiRef](./README.md#oktaauth)
@@ -61,7 +65,7 @@ export type AuthRequestOptions = {
Defined at
-[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L40).
+[packages/core-api/src/apis/definitions/auth.ts:40](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L40).
Referenced by: [getProfile](#getprofile).
@@ -89,6 +93,6 @@ export type ProfileInfo = {
Defined at
-[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L172).
+[packages/core-api/src/apis/definitions/auth.ts:172](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L172).
Referenced by: [getProfile](#getprofile).
diff --git a/docs/reference/utility-apis/README.md b/docs/reference/utility-apis/README.md
index 521628fd33..cfdc3b6ef8 100644
--- a/docs/reference/utility-apis/README.md
+++ b/docs/reference/utility-apis/README.md
@@ -1,59 +1,77 @@
----
-id: README
-title: Utility API References
----
+# Backstage Core Utility APIs
The following is a list of all Utility APIs defined by `@backstage/core`. They
are available to use by plugins and components, and can be accessed using the
`useApi` hook, also provided by `@backstage/core`. For more information, see
https://github.com/spotify/backstage/blob/master/docs/api/utility-apis.md.
-## alert
+### alert
Used to report alerts and forward them to the app
Implemented type: [AlertApi](./AlertApi.md)
ApiRef:
-[alertApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AlertApi.ts#L41)
+[alertApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AlertApi.ts#L41)
-## appTheme
+### appTheme
API Used to configure the app theme, and enumerate options
Implemented type: [AppThemeApi](./AppThemeApi.md)
ApiRef:
-[appThemeApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/AppThemeApi.ts#L74)
+[appThemeApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/AppThemeApi.ts#L74)
-## config
+### auth0Auth
+
+Provides authentication towards Auth0 APIs
+
+Implemented types: [OpenIdConnectApi](./OpenIdConnectApi.md),
+[ProfileInfoApi](./ProfileInfoApi.md),
+[BackstageIdentityApi](./BackstageIdentityApi.md),
+[SessionStateApi](./SessionStateApi.md)
+
+ApiRef:
+[auth0AuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L273)
+
+### config
Used to access runtime configuration
Implemented type: [Config](./Config.md)
ApiRef:
-[configApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ConfigApi.ts#L22)
+[configApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ConfigApi.ts#L22)
-## error
+### discovery
+
+Provides service discovery of backend plugins
+
+Implemented type: [DiscoveryApi](./DiscoveryApi.md)
+
+ApiRef:
+[discoveryApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/DiscoveryApi.ts#L44)
+
+### error
Used to report errors and forward them to the app
Implemented type: [ErrorApi](./ErrorApi.md)
ApiRef:
-[errorApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/ErrorApi.ts#L65)
+[errorApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/ErrorApi.ts#L65)
-## featureFlags
+### featureFlags
Used to toggle functionality in features across Backstage
Implemented type: [FeatureFlagsApi](./FeatureFlagsApi.md)
ApiRef:
-[featureFlagsApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L58)
+[featureFlagsApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts#L58)
-## githubAuth
+### githubAuth
Provides authentication towards GitHub APIs
@@ -63,9 +81,9 @@ Implemented types: [OAuthApi](./OAuthApi.md),
[SessionStateApi](./SessionStateApi.md)
ApiRef:
-[githubAuthApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L230)
+[githubAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L230)
-## gitlabAuth
+### gitlabAuth
Provides authentication towards GitLab APIs
@@ -75,9 +93,9 @@ Implemented types: [OAuthApi](./OAuthApi.md),
[SessionStateApi](./SessionStateApi.md)
ApiRef:
-[gitlabAuthApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L260)
+[gitlabAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L260)
-## googleAuth
+### googleAuth
Provides authentication towards Google APIs and identities
@@ -88,18 +106,31 @@ Implemented types: [OAuthApi](./OAuthApi.md),
[SessionStateApi](./SessionStateApi.md)
ApiRef:
-[googleAuthApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L213)
+[googleAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L213)
-## identity
+### identity
Provides access to the identity of the signed in user
Implemented type: [IdentityApi](./IdentityApi.md)
ApiRef:
-[identityApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/IdentityApi.ts#L54)
+[identityApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/IdentityApi.ts#L54)
-## oauth2
+### microsoftAuth
+
+Provides authentication towards Microsoft APIs and identities
+
+Implemented types: [OAuthApi](./OAuthApi.md),
+[OpenIdConnectApi](./OpenIdConnectApi.md),
+[ProfileInfoApi](./ProfileInfoApi.md),
+[BackstageIdentityApi](./BackstageIdentityApi.md),
+[SessionStateApi](./SessionStateApi.md)
+
+ApiRef:
+[microsoftAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L287)
+
+### oauth2
Example of how to use oauth2 custom provider
@@ -108,18 +139,18 @@ Implemented types: [OAuthApi](./OAuthApi.md),
[ProfileInfoApi](./ProfileInfoApi.md), [SessionStateApi](./SessionStateApi.md)
ApiRef:
-[oauth2ApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L270)
+[oauth2ApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L301)
-## oauthRequest
+### oauthRequest
An API for implementing unified OAuth flows in Backstage
Implemented type: [OAuthRequestApi](./OAuthRequestApi.md)
ApiRef:
-[oauthRequestApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130)
+[oauthRequestApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/OAuthRequestApi.ts#L130)
-## oktaAuth
+### oktaAuth
Provides authentication towards Okta APIs
@@ -130,13 +161,13 @@ Implemented types: [OAuthApi](./OAuthApi.md),
[SessionStateApi](./SessionStateApi.md)
ApiRef:
-[oktaAuthApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L243)
+[oktaAuthApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L243)
-## storage
+### storage
Provides the ability to store data which is unique to the user
Implemented type: [StorageApi](./StorageApi.md)
ApiRef:
-[storageApiRef](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/StorageApi.ts#L68)
+[storageApiRef](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/StorageApi.ts#L68)
diff --git a/docs/reference/utility-apis/SessionStateApi.md b/docs/reference/utility-apis/SessionStateApi.md
index 44d329304a..c1821f2381 100644
--- a/docs/reference/utility-apis/SessionStateApi.md
+++ b/docs/reference/utility-apis/SessionStateApi.md
@@ -1,16 +1,20 @@
# SessionStateApi
The SessionStateApi type is defined at
-[packages/core-api/src/apis/definitions/auth.ts:201](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L201).
+[packages/core-api/src/apis/definitions/auth.ts:201](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L201).
The following Utility APIs implement this type:
+- [auth0AuthApiRef](./README.md#auth0auth)
+
- [githubAuthApiRef](./README.md#githubauth)
- [gitlabAuthApiRef](./README.md#gitlabauth)
- [googleAuthApiRef](./README.md#googleauth)
+- [microsoftAuthApiRef](./README.md#microsoftauth)
+
- [oauth2ApiRef](./README.md#oauth2)
- [oktaAuthApiRef](./README.md#oktaauth)
@@ -52,7 +56,7 @@ export type Observable<T> = {
Defined at
-[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
Referenced by: [sessionState\$](#sessionstate).
@@ -71,7 +75,7 @@ export type Observer<T> = {
Defined at
-[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
Referenced by: [Observable](#observable).
@@ -87,7 +91,7 @@ export enum SessionState {
Defined at
-[packages/core-api/src/apis/definitions/auth.ts:192](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/auth.ts#L192).
+[packages/core-api/src/apis/definitions/auth.ts:192](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/auth.ts#L192).
Referenced by: [sessionState\$](#sessionstate).
@@ -110,6 +114,6 @@ export type Subscription = {
Defined at
-[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
Referenced by: [Observable](#observable).
diff --git a/docs/reference/utility-apis/StorageApi.md b/docs/reference/utility-apis/StorageApi.md
index 5bc871e7bf..bee52935da 100644
--- a/docs/reference/utility-apis/StorageApi.md
+++ b/docs/reference/utility-apis/StorageApi.md
@@ -1,7 +1,7 @@
# StorageApi
The StorageApi type is defined at
-[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
+[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
The following Utility API implements this type:
[storageApiRef](./README.md#storage)
@@ -79,7 +79,7 @@ export type Observable<T> = {
Defined at
-[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L53).
+[packages/core-api/src/types.ts:53](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L53).
Referenced by: [observe\$](#observe), [StorageApi](#storageapi).
@@ -98,7 +98,7 @@ export type Observer<T> = {
Defined at
-[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L24).
+[packages/core-api/src/types.ts:24](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L24).
Referenced by: [Observable](#observable).
@@ -144,7 +144,7 @@ export interface StorageApi {
Defined at
-[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
+[packages/core-api/src/apis/definitions/StorageApi.ts:31](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/StorageApi.ts#L31).
Referenced by: [forBucket](#forbucket).
@@ -158,7 +158,7 @@ export type StorageValueChange<T = any> = {
Defined at
-[packages/core-api/src/apis/definitions/StorageApi.ts:21](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/apis/definitions/StorageApi.ts#L21).
+[packages/core-api/src/apis/definitions/StorageApi.ts:21](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/apis/definitions/StorageApi.ts#L21).
Referenced by: [observe\$](#observe), [StorageApi](#storageapi).
@@ -181,6 +181,6 @@ export type Subscription = {
Defined at
-[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/f8780ff32509d0326bc513791ea60846d7614b34/packages/core-api/src/types.ts#L33).
+[packages/core-api/src/types.ts:33](https://github.com/spotify/backstage/blob/82d329555c16af46db9b4e5cd2f44a3cc006a52e/packages/core-api/src/types.ts#L33).
Referenced by: [Observable](#observable).
diff --git a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md
index 65ee02f6a0..dff567eaf3 100644
--- a/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md
+++ b/microsite/blog/2020-04-30-how-to-quickly-set-up-backstage.md
@@ -34,7 +34,7 @@ You get to take full advantage of a platform that we at Spotify have been using
Just run the backstage-cli:
```bash
-npx @backstage/cli create-app
+npx @backstage/create-app
```
Name your app, and we will create everything you need:
@@ -50,7 +50,7 @@ yarn start
And you are good to go! 👍
-Read the full documentation on how to [create an app](/docs/getting-started/create-an-app.md) on GitHub.
+Read the full documentation on how to [create an app](/docs/getting-started/create-an-app) on GitHub.
## What do I get? (Let's get technical...)
diff --git a/microsite/blog/2020-09-08-announcing-tech-docs.md b/microsite/blog/2020-09-08-announcing-tech-docs.md
new file mode 100644
index 0000000000..48b6eeb7cc
--- /dev/null
+++ b/microsite/blog/2020-09-08-announcing-tech-docs.md
@@ -0,0 +1,120 @@
+---
+title: Announcing TechDocs: Spotify’s docs-like-code plugin for Backstage
+author: Gary Niemen
+authorURL: https://github.com/garyniemen
+---
+
+Since we [open sourced Backstage](https://backstage.io/blog/2020/03/16/announcing-backstage), one of the most requested features has been for a technical documentation plugin. Well, good news. The first open source version of TechDocs is here. Now let’s start collaborating and making it better, together.
+
+VIDEO
+
+
+
+Internally, we call it TechDocs. It’s the most used plugin at Spotify by far — accounting for about 20% of our Backstage traffic (even though it is just one of 130+ plugins). Its popularity is evidence of something simple: We made documentation so easy to create, find, and use — people actually use it.
+
+We are quite sure the main reason for the success of TechDocs is our docs-like-code approach — engineers write their technical documentation in Markdown files that live together with the code. During CI, a documentation site is created using MkDocs, and all sites are rendered centrally in a Backstage plugin. On top of the static documentation, we incorporate additional metadata about the documentation site — such as owner, open GitHub Issues, Slack support channel, and Stack Overflow Enterprise tags.
+
+
+
+But this is just one way to do it. Today we’re most excited for what the open version of TechDocs can become.
+
+## Okay, let’s start collaborating
+
+If you go to [GitHub](https://github.com/spotify/backstage/tree/master/plugins) now, you’ll find everything you need to start collaborating with us to build out the docs-like-code Backstage plugin — we’ll call it TechDocs in the open as well.
+
+You’ll find the code in [techdocs](https://github.com/spotify/backstage/tree/master/plugins/techdocs) (frontend) and [techdocs-backend](https://github.com/spotify/backstage/tree/master/plugins/techdocs-backend). (There are also two separate packages [techdocs-cli](https://github.com/spotify/backstage/tree/master/packages/techdocs-cli) and [techdocs-container](https://github.com/spotify/backstage/tree/master/packages/techdocs-container).)
+
+You’ll find issues to work on in the [issues queue](https://github.com/spotify/backstage/issues?q=is%3Aissue+is%3Aopen+label%3A%22docs-like-code%22+label%3A%22help+wanted%22), typically starting with TechDocs: and labeled with docs-like-code, some labeled good first issue. Feel free to add your own issues, of course.
+
+
+
+What we have on GitHub so far is a first iteration of TechDocs that you can use end-to-end — in other words, from docs written in Markdown in GitHub to a published site on Backstage.
+
+More specifically, with this first iteration, you can:
+
+- Run TechDocs locally and read documentation.
+- Configure your entity (e.g. service, website) so that Backstage builds your documentation and serves it in TechDocs. Documentation is displayed on the Docs tab in the Service Catalog and on its own page.
+- Get documentation set up for free in your project when you create a new component out of one of the non-experimental templates (labeled with recommended). If you are looking for a standalone documentation project, use the docs-template.
+- Choose your own storage solution for the documentation.
+- Define your own API to interface with your documentation solution.
+
+For a full overview, including getting started instructions, check out our [TechDocs Documentation](https://backstage.io/docs/features/techdocs/techdocs-overview).
+
+But before you go there, let me tell you a bit about the TechDocs story — and why we believe TechDocs is such a powerful yet simple solution for great documentation.
+
+## The TechDocs story
+
+Here is the TechDocs story. It’s not an uncommon one (we have learned from many other companies).
+
+About a year and a half ago, we conducted a company-wide productivity survey. The third largest problem according to all our engineers? Not being able to find the technical information they needed to do their work. And it’s not surprising. There was no standard way to produce and consume technical documentation, so teams were going their own way — using Confluence, Google Docs, README files, custom built websites, GitHub Pages, and so on and on. And those searching for information were left to hunt for it in all those different places until they found what they were looking for (if they ever did). Worse, if you did happen to find the documentation that you needed, there was no way to know whether the information was up-to-date or correct. In other words, there was no way to know whether you could trust what you found. We did have technical writers at the company, but they were mostly scattered across the company solving documentation problems within their own particular domain.
+
+So this is the fertile soil on which TechDocs was built.
+
+After a Hack Week implementation attracted interest from high up in the company, we formed a cross-functional team made up of technical writers and engineers with the mission to solve internal technical documentation at Spotify. And we started to build TechDocs. We went for a docs-like-code approach, fiercely optimizing for engineers and engineering workflows. We also went for an opinionated approach, telling everybody: This is the standard way to do technical documentation at Spotify. The sense was that engineers appreciated a documentation solution that was in line with their workflow and, after all the frustration of multiple tools, were relieved to be told “this is the way to do it”.
+
+For more information about this journey, take a look at my 20-minute talk from DevRelCon London from last December: [The Hero’s Journey: How we are solving internal technical documentation at Spotify](https://www.linkedin.com/posts/garyniemen_how-we-are-solving-internal-technical-documentation-activity-6646078605594030080-4L31).
+
+## Key problem areas that we are solving
+
+We have come a long way, fast — both in our implementation and in our thinking. Here are some of the key problem areas that we are addressing. Note that they are in various stages of implementation, and we won’t be able to release everything within our minimum plugin. In fact, see this as an appetite taster. What we hope is that we can build together.
+
+### Stuck to unstuck
+
+Very early on, we decided that the main problem we were trying to solve was to help engineers (when using technical documentation) go from stuck to unstuck, and fast. This became our guiding principle. Is what we are building helping engineers get unstuck faster? From this, it follows that we need to promote quality documentation on the one hand, and provide a high level of discoverability on the other. One without the other is not going to cut it.
+
+### Feedback loops
+
+What we want to build is a thriving community of technical documentation creators, contributors, and readers. We want this because, we believe, this is the way to drive up the quality of the documentation. More readers, more feedback, more doc updates. And driving up the quality of the corpus of technical documentation leads to trust which in turn leads to more engagement and, hence, more of a thriving community.
+
+To get this working, we recognised that we need to remove ‘friction from the system’ — we need to build in efficient feedback loops. For example, help engineers get their doc site up by providing documentation improvement hints and build information as close as possible to where they are already working. And for readers, make it easy to give feedback. And then for doc site owners, ensure that they are notified when there is feedback and incentivised to make the fix.
+
+
+
+
+
+### Trust
+
+How do I know whether to trust this piece of documentation? This is a question we want to be able to answer for those using technical documentation in Backstage. It’s not an easy nut to crack. It is almost, one could say, the hard problem of technical documentation. For example, some might say ‘last updated’ is a key factor. But what about stable, good quality documentation that has no need to be updated? What about page views? Yes, this is a good sign that the documentation is being found and viewed, but it doesn’t say anything about whether the documentation can be trusted. How about a button: Did this documentation help? This is good, but will people use it? Will we get enough data to show trust? We have lofty ambitions of one day providing a trust score on the doc site informed by a super-intelligent algorithm. But we are not there yet. For now, we have landed on surfacing when the documentation was last updated, top five contributors, the support channel, owning team, and number of open GitHub Issues. But going forward we are definitely up for solving the hard problem. We think there’s much more work to be done here and look forward to seeing ideas from the community.
+
+### Discoverability and search
+
+How to find stuff? That is another big question. As mentioned above, it’s all well and good having quality documentation, but it’s no use whatsoever if you can’t find it. If you know what you are looking for, then you can use a search engine. If you don’t know what you are looking for, then you are going to need more — like a well designed information architecture, a user friendly browse experience, and even intelligent suggestions based on your role and what you have searched for previously.
+
+In this problem area, we made use of Elasticsearch, the open source search engine that was already being used in Backstage, to implement documentation search across all documentation sites and per documentation site. In terms of discoverability, we implemented a documentation home page in Backstage that surfaces Spotify’s most important documents and uses metrics to list the company’s most used doc sites as well as the documentation equivalent of a “your daily mix” playlist.
+
+
+
+
+
+There is much more to do in the area of discoverability and search.
+
+### Use case variations
+
+The standard use case for TechDocs is: One component in Backstage equals one GitHub repository, equals one doc site. This use case comes in two flavours: the repository is a code repository with docs or a docs-only repository. Then, to meet the needs of one large part of the Spotify engineering organisation that uses monorepos (multi-component repositories), we added a third use case. We built an MkDocs plugin that enabled doc site creators to include documentation from doc folders in other parts of the repository. So this use case is: One main component in Backstage equals a monorepo with distributed documentation, equals one doc site.
+
+These three use cases satisfy most of the needs, but we have had plenty of requests for additional use cases, for example, the ability to create multiple doc sites from a multi-component repository and the ability to create one doc site from documentation in multiple repositories.
+
+### Metrics
+
+There are many good arguments for standardizing the way that technical documentation is produced and consumed. One of them is metrics. If we have one way of producing technical documentation (in our case, GitHub Enterprise) and one place where it shows up (in our case, Backstage), we are in a strong position to build up metrics that help all the various stakeholders — for example, us building TechDocs, teams creating documentation sites, and engineers trying to get unstuck. Just imagine how much harder this would be if technical documentation was produced and consumed in a plethora of places, such as Confluence, Google Docs, README files, custom web sites, and GitHub Pages.
+
+One thing we have recently completed is a Manage page in Backstage for doc site owners. Here teams can see all the documentation that they own, the number of GitHub Issues per doc site or page, and last updated. We have also built a large dashboard using the open source analytics software Redash to inform our own product development process.
+
+
+
+Again, there is a lot more that can be done in the area of metrics. Did I mention the trust score?
+
+### Code-like-docs
+
+Code-like-docs, what? Okay, it’s just my little play on words. This is what I mean. One request that we keep getting is to be able to have code in the documentation fetched from and in sync with code in GitHub. In this way, you can avoid code in the documentation going stale. MkDocs does have an extension for this — but it has some limitations. For example, the code has to be in the /docs folder with the Markdown files. We are working on developing a wider and more flexible solution.
+
+### Golden Paths
+
+At Spotify, we have the concept of [Golden Paths](https://engineering.atspotify.com/2020/08/17/how-we-use-golden-paths-to-solve-fragmentation-in-our-software-ecosystem/) — one for each engineering discipline. My favourite definition of Golden Path is that it is the “opinionated and supported path”. Each Golden Path has an accompanying Golden Path tutorial that walks you through the opinionated and supported path.
+
+The Golden Path tutorials are Spotify’s most used and important documents and have shown themselves to be the most challenging to manage within a docs-like-code environment. One reason for this is that they are long, divided into many parts, and ownership is typically spread among many different teams. We have had to make use of GitHub codeowners to handle ownership and had to create datasets and data pipelines to be able to attach GitHub Issues to the specific parts or files that a team owns. Another challenge of the Golden Path tutorials is that parts are often dependent on other parts. We are just starting to look into how we can solve these dependency challenges in order to remove friction for engineers writing tutorial documentation.
+
+---
+
+So that’s it for now. As you can see, we have come a long way AND there is much more to do. We are looking forward to continuing our docs-like-code journey out in the open with new, enthusiastic technical documentation friends.
diff --git a/microsite/blog/assets/announcing-techdocs/discover1.png b/microsite/blog/assets/announcing-techdocs/discover1.png
new file mode 100644
index 0000000000..5b23e64b43
Binary files /dev/null and b/microsite/blog/assets/announcing-techdocs/discover1.png differ
diff --git a/microsite/blog/assets/announcing-techdocs/discover2.png b/microsite/blog/assets/announcing-techdocs/discover2.png
new file mode 100644
index 0000000000..3737ee68db
Binary files /dev/null and b/microsite/blog/assets/announcing-techdocs/discover2.png differ
diff --git a/microsite/blog/assets/announcing-techdocs/docs-in-backstage.png b/microsite/blog/assets/announcing-techdocs/docs-in-backstage.png
new file mode 100644
index 0000000000..f3b724ff4f
Binary files /dev/null and b/microsite/blog/assets/announcing-techdocs/docs-in-backstage.png differ
diff --git a/microsite/blog/assets/announcing-techdocs/feedback-loop1.png b/microsite/blog/assets/announcing-techdocs/feedback-loop1.png
new file mode 100644
index 0000000000..4e2d0de5df
Binary files /dev/null and b/microsite/blog/assets/announcing-techdocs/feedback-loop1.png differ
diff --git a/microsite/blog/assets/announcing-techdocs/feedback-loop2.png b/microsite/blog/assets/announcing-techdocs/feedback-loop2.png
new file mode 100644
index 0000000000..516780e3ac
Binary files /dev/null and b/microsite/blog/assets/announcing-techdocs/feedback-loop2.png differ
diff --git a/microsite/blog/assets/announcing-techdocs/github-issues.png b/microsite/blog/assets/announcing-techdocs/github-issues.png
new file mode 100644
index 0000000000..3d5f3465c0
Binary files /dev/null and b/microsite/blog/assets/announcing-techdocs/github-issues.png differ
diff --git a/microsite/blog/assets/announcing-techdocs/metrics.png b/microsite/blog/assets/announcing-techdocs/metrics.png
new file mode 100644
index 0000000000..b332a65b0d
Binary files /dev/null and b/microsite/blog/assets/announcing-techdocs/metrics.png differ
diff --git a/microsite/data/plugins/graphiql.yaml b/microsite/data/plugins/graphiql.yaml
index 6c95d1b175..e997360b04 100644
--- a/microsite/data/plugins/graphiql.yaml
+++ b/microsite/data/plugins/graphiql.yaml
@@ -3,12 +3,10 @@ title: GraphiQL
author: Spotify
authorUrl: https://github.com/spotify
category: Debugging
-description: Integrates GraphiQL as a tool to browse GraphiQL endpoints inside Backstage.
-documentation: https://github.com/spotify/backstage/tree/master/plugins/lighthouse
+description: Integrates GraphiQL as a tool to browse GraphQL API endpoints inside Backstage.
+documentation: https://github.com/spotify/backstage/tree/master/plugins/graphiql
iconUrl: https://upload.wikimedia.org/wikipedia/commons/thumb/1/17/GraphQL_Logo.svg/1024px-GraphQL_Logo.svg.png
npmPackageName: '@backstage/plugin-graphiql'
tags:
- graphql
- - github
- - gitlab
- - api
+ - graphiql
diff --git a/microsite/pages/en/demos.js b/microsite/pages/en/demos.js
index 9f9dcec819..d891e05613 100644
--- a/microsite/pages/en/demos.js
+++ b/microsite/pages/en/demos.js
@@ -78,6 +78,41 @@ const Background = props => {
+
+
+
+
+ Make documentation easy
+
+
+ Documentation! Everyone needs it, no one wants to create it, and
+ no one can ever find it. Backstage follows a “docs like code”
+ approach: you write documentation in Markdown files right
+ alongside your code. This makes documentation easier to create,
+ maintain, find — and, you know, actually use. This demo video
+ showcases Spotify’s internal version of TechDocs. Learn more about{' '}
+
+ TechDocs
+
+ .
+
+
+ Watch now
+
+
+
+ VIDEO
+
+
+
+
diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js
index 7a58ede25f..f595032bd0 100644
--- a/microsite/pages/en/index.js
+++ b/microsite/pages/en/index.js
@@ -296,9 +296,7 @@ class Index extends React.Component {
src={`${baseUrl}animations/backstage-techdocs-icon-1.gif`}
/>
-
- Backstage TechDocs (Coming Soon)
-
+ Backstage TechDocs
Docs like code
- Subscribe to our newsletter
-
- TechDocs is our most used feature at Spotify. Be the first to know
- when{' '}
-
- the open source version
- {' '}
- ships.
-
+ Learn more about TechDocs
- Subscribe
+ Docs
diff --git a/microsite/sidebars.json b/microsite/sidebars.json
index d31c14fd1d..3e69ad00f4 100644
--- a/microsite/sidebars.json
+++ b/microsite/sidebars.json
@@ -31,12 +31,13 @@
]
}
],
- "Features": [
+ "Core Features": [
{
"type": "subcategory",
"label": "Software Catalog",
"ids": [
"features/software-catalog/software-catalog-overview",
+ "features/software-catalog/installation",
"features/software-catalog/system-model",
"features/software-catalog/descriptor-format",
"features/software-catalog/extending-the-model",
@@ -46,9 +47,10 @@
},
{
"type": "subcategory",
- "label": "Software creation templates",
+ "label": "Software Templates",
"ids": [
"features/software-templates/software-templates-index",
+ "features/software-templates/installation",
"features/software-templates/adding-templates",
"features/software-templates/extending/extending-index",
"features/software-templates/extending/extending-templater",
diff --git a/mkdocs.yml b/mkdocs.yml
index b12c8f81c7..eb8fa1bf84 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -34,6 +34,7 @@ nav:
- API: 'features/software-catalog/api.md'
- Software creation templates:
- Overview: 'features/software-templates/index.md'
+ - Installation: 'features/software-templates/installation.md'
- Adding templates: 'features/software-templates/adding-templates.md'
- Extending the Scaffolder:
- Overview: 'features/software-templates/extending/index.md'
diff --git a/package.json b/package.json
index b25bcd3788..2622f1e322 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
"start-backend": "yarn workspace example-backend start",
"build": "lerna run build",
"tsc": "tsc",
+ "tsc:full": "tsc --skipLibCheck false --incremental false",
"clean": "backstage-cli clean && lerna run clean",
"diff": "lerna run diff --",
"test": "lerna run test --since origin/master -- --coverage",
diff --git a/packages/app/package.json b/packages/app/package.json
index 77f5ab4432..bf0f340a40 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -27,7 +27,7 @@
"@backstage/plugin-welcome": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@octokit/rest": "^18.0.0",
"@roadiehq/backstage-plugin-github-pull-requests": "0.3.0",
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx
index cb38726716..667b14366f 100644
--- a/packages/app/src/App.tsx
+++ b/packages/app/src/App.tsx
@@ -19,6 +19,7 @@ import {
AlertDisplay,
OAuthRequestDialog,
SignInPage,
+ createRouteRef,
} from '@backstage/core';
import React, { FC } from 'react';
import Root from './components/Root';
@@ -27,6 +28,11 @@ import { apis } from './apis';
import { hot } from 'react-hot-loader/root';
import { providers } from './identityProviders';
import { Router as CatalogRouter } from '@backstage/plugin-catalog';
+import { Router as DocsRouter } from '@backstage/plugin-techdocs';
+import { Router as GraphiQLRouter } from '@backstage/plugin-graphiql';
+import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar';
+import { Router as LighthouseRouter } from '@backstage/plugin-lighthouse';
+import { Router as RegisterComponentRouter } from '@backstage/plugin-register-component';
import { Route, Routes, Navigate } from 'react-router';
import { EntityPage } from './components/catalog/EntityPage';
@@ -52,13 +58,29 @@ const AppProvider = app.getProvider();
const AppRouter = app.getRouter();
const deprecatedAppRoutes = app.getRoutes();
+const catalogRouteRef = createRouteRef({
+ path: '/catalog',
+ title: 'Service Catalog',
+});
+
const AppRoutes = () => (
+
}
/>
-
+ } />
+ }
+ />
+ } />
+ } />
+ }
+ />
{...deprecatedAppRoutes}
);
diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts
index b391e95d62..7f57f6f970 100644
--- a/packages/app/src/apis.ts
+++ b/packages/app/src/apis.ts
@@ -15,65 +15,18 @@
*/
import {
- ApiRegistry,
- alertApiRef,
errorApiRef,
- AlertApiForwarder,
- ConfigApi,
- ErrorApiForwarder,
- ErrorAlerter,
- featureFlagsApiRef,
- FeatureFlags,
discoveryApiRef,
UrlPatternDiscovery,
- GoogleAuth,
- GithubAuth,
- OAuth2,
- OktaAuth,
- GitlabAuth,
- Auth0Auth,
- MicrosoftAuth,
- oauthRequestApiRef,
- OAuthRequestManager,
- googleAuthApiRef,
githubAuthApiRef,
- oauth2ApiRef,
- oktaAuthApiRef,
- gitlabAuthApiRef,
- auth0AuthApiRef,
- microsoftAuthApiRef,
- storageApiRef,
- WebStorage,
+ createApiFactory,
+ configApiRef,
} from '@backstage/core';
-import {
- lighthouseApiRef,
- LighthouseRestApi,
-} from '@backstage/plugin-lighthouse';
-
-import { techRadarApiRef, TechRadar } from '@backstage/plugin-tech-radar';
-
-import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci';
-import { catalogApiRef, CatalogClient } from '@backstage/plugin-catalog';
-
-import { gitOpsApiRef, GitOpsRestApi } from '@backstage/plugin-gitops-profiles';
import {
graphQlBrowseApiRef,
GraphQLEndpoints,
} from '@backstage/plugin-graphiql';
-import { scaffolderApiRef, ScaffolderApi } from '@backstage/plugin-scaffolder';
-import {
- techdocsStorageApiRef,
- TechDocsStorageApi,
-} from '@backstage/plugin-techdocs';
-
-import { rollbarApiRef, RollbarClient } from '@backstage/plugin-rollbar';
-import { GCPClient, GCPApiRef } from '@backstage/plugin-gcp-projects';
-import {
- GithubActionsClient,
- githubActionsApiRef,
-} from '@backstage/plugin-github-actions';
-import { jenkinsApiRef, JenkinsApi } from '@backstage/plugin-jenkins';
import {
TravisCIApi,
@@ -84,143 +37,36 @@ import {
githubPullRequestsApiRef,
} from '@roadiehq/backstage-plugin-github-pull-requests';
-export const apis = (config: ConfigApi) => {
- // eslint-disable-next-line no-console
- console.log(`Creating APIs for ${config.getString('app.title')}`);
+export const apis = [
+ // TODO(Rugvip): migrate to use /api
+ createApiFactory({
+ api: discoveryApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) =>
+ UrlPatternDiscovery.compile(
+ `${configApi.getString('backend.baseUrl')}/{{ pluginId }}`,
+ ),
+ }),
+ createApiFactory({
+ api: graphQlBrowseApiRef,
+ deps: { errorApi: errorApiRef, githubAuthApi: githubAuthApiRef },
+ factory: ({ errorApi, githubAuthApi }) =>
+ GraphQLEndpoints.from([
+ GraphQLEndpoints.create({
+ id: 'gitlab',
+ title: 'GitLab',
+ url: 'https://gitlab.com/api/graphql',
+ }),
+ GraphQLEndpoints.github({
+ id: 'github',
+ title: 'GitHub',
+ errorApi,
+ githubAuthApi,
+ }),
+ ]),
+ }),
- const backendUrl = config.getString('backend.baseUrl');
- const techdocsUrl = config.getString('techdocs.storageUrl');
-
- const builder = ApiRegistry.builder();
-
- const discoveryApi = builder.add(
- discoveryApiRef,
- UrlPatternDiscovery.compile(`${backendUrl}/{{ pluginId }}`),
- );
- const alertApi = builder.add(alertApiRef, new AlertApiForwarder());
- const errorApi = builder.add(
- errorApiRef,
- new ErrorAlerter(alertApi, new ErrorApiForwarder()),
- );
-
- builder.add(storageApiRef, WebStorage.create({ errorApi }));
- builder.add(GCPApiRef, new GCPClient());
- builder.add(
- circleCIApiRef,
- new CircleCIApi(`${backendUrl}/proxy/circleci/api`),
- );
-
- builder.add(jenkinsApiRef, new JenkinsApi(`${backendUrl}/proxy/jenkins/api`));
-
- builder.add(githubActionsApiRef, new GithubActionsClient());
-
- builder.add(featureFlagsApiRef, new FeatureFlags());
-
- builder.add(lighthouseApiRef, LighthouseRestApi.fromConfig(config));
-
- builder.add(travisCIApiRef, new TravisCIApi());
- builder.add(githubPullRequestsApiRef, new GithubPullRequestsClient());
-
- const oauthRequestApi = builder.add(
- oauthRequestApiRef,
- new OAuthRequestManager(),
- );
-
- builder.add(
- googleAuthApiRef,
- GoogleAuth.create({
- discoveryApi,
- oauthRequestApi,
- }),
- );
-
- builder.add(
- microsoftAuthApiRef,
- MicrosoftAuth.create({
- discoveryApi,
- oauthRequestApi,
- }),
- );
-
- const githubAuthApi = builder.add(
- githubAuthApiRef,
- GithubAuth.create({
- discoveryApi,
- oauthRequestApi,
- }),
- );
-
- builder.add(
- oktaAuthApiRef,
- OktaAuth.create({
- discoveryApi,
- oauthRequestApi,
- }),
- );
-
- builder.add(
- gitlabAuthApiRef,
- GitlabAuth.create({
- discoveryApi,
- oauthRequestApi,
- }),
- );
-
- builder.add(
- auth0AuthApiRef,
- Auth0Auth.create({
- discoveryApi,
- oauthRequestApi,
- }),
- );
-
- builder.add(
- oauth2ApiRef,
- OAuth2.create({
- discoveryApi,
- oauthRequestApi,
- }),
- );
-
- builder.add(
- techRadarApiRef,
- new TechRadar({
- width: 1500,
- height: 800,
- }),
- );
-
- builder.add(catalogApiRef, new CatalogClient({ discoveryApi }));
-
- builder.add(scaffolderApiRef, new ScaffolderApi({ discoveryApi }));
-
- builder.add(gitOpsApiRef, new GitOpsRestApi('http://localhost:3008'));
-
- builder.add(
- graphQlBrowseApiRef,
- GraphQLEndpoints.from([
- GraphQLEndpoints.create({
- id: 'gitlab',
- title: 'GitLab',
- url: 'https://gitlab.com/api/graphql',
- }),
- GraphQLEndpoints.github({
- id: 'github',
- title: 'GitHub',
- errorApi,
- githubAuthApi,
- }),
- ]),
- );
-
- builder.add(rollbarApiRef, new RollbarClient({ discoveryApi }));
-
- builder.add(
- techdocsStorageApiRef,
- new TechDocsStorageApi({
- apiOrigin: techdocsUrl,
- }),
- );
-
- return builder.build();
-};
+ // TODO: move to plugins
+ createApiFactory(travisCIApiRef, new TravisCIApi()),
+ createApiFactory(githubPullRequestsApiRef, new GithubPullRequestsClient()),
+];
diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx
index ab95f623af..e91504d619 100644
--- a/packages/app/src/components/Root/Root.tsx
+++ b/packages/app/src/components/Root/Root.tsx
@@ -18,9 +18,7 @@ import React, { FC, useContext } from 'react';
import PropTypes from 'prop-types';
import { Link, makeStyles } from '@material-ui/core';
import HomeIcon from '@material-ui/icons/Home';
-import ExploreIcon from '@material-ui/icons/Explore';
import ExtensionIcon from '@material-ui/icons/Extension';
-import BuildIcon from '@material-ui/icons/BuildRounded';
import RuleIcon from '@material-ui/icons/AssignmentTurnedIn';
import MapIcon from '@material-ui/icons/MyLocation';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
@@ -91,7 +89,6 @@ const Root: FC<{}> = ({ children }) => (
{/* Global nav, not org-specific */}
-
@@ -99,7 +96,6 @@ const Root: FC<{}> = ({ children }) => (
-
{
+ // This component is just an example of how you can implement your company's logic in entity page.
+ // You can for example enforce that all components of type 'service' should use GitHubActions
+ switch (true) {
+ case isGitHubActionsAvailable(entity):
+ return ;
+ case isCircleCIAvailable(entity):
+ return ;
+ default:
+ return (
+
+ No CI/CD is available for this entity. Check corresponding
+ annotations!
+
+ );
+ }
+};
const OverviewContent = ({ entity }: { entity: Entity }) => (
@@ -43,7 +70,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
}
+ element={ }
/>
(
title="API"
element={ }
/>
+ }
+ />
);
@@ -68,16 +100,20 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
}
+ element={ }
/>
}
/>
+ }
+ />
);
-
const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
(
title="Overview"
element={ }
/>
+ }
+ />
);
diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile
index 8d17821101..a7bd814b19 100644
--- a/packages/backend/Dockerfile
+++ b/packages/backend/Dockerfile
@@ -2,10 +2,15 @@ FROM node:12
WORKDIR /usr/src/app
+# Copy repo skeleton first, to avoid unnecessary docker cache invalidation.
+# The skeleton contains the package.json of each package in the monorepo,
+# and along with yarn.lock and the root package.json, that's enough to run yarn install.
+ADD yarn.lock package.json skeleton.tar ./
+
+RUN yarn install --frozen-lockfile --production
+
# This will copy the contents of the dist-workspace when running the build-image command.
# Do not use this Dockerfile outside of that command, as it will copy in the source code instead.
COPY . .
-RUN yarn install --frozen-lockfile --production
-
CMD ["node", "packages/backend"]
diff --git a/packages/cli/config/tsconfig.json b/packages/cli/config/tsconfig.json
index ce031bc5e7..ef1a8df353 100644
--- a/packages/cli/config/tsconfig.json
+++ b/packages/cli/config/tsconfig.json
@@ -25,6 +25,7 @@
"removeComments": false,
"resolveJsonModule": true,
"sourceMap": false,
+ "skipLibCheck": true,
"strict": true,
"strictBindCallApply": true,
"strictFunctionTypes": true,
diff --git a/packages/cli/src/commands/backend/buildImage.ts b/packages/cli/src/commands/backend/buildImage.ts
index 36d4ef7129..b6e9da7b39 100644
--- a/packages/cli/src/commands/backend/buildImage.ts
+++ b/packages/cli/src/commands/backend/buildImage.ts
@@ -41,6 +41,7 @@ export default async (cmd: Command) => {
...appConfigs,
{ src: paths.resolveTarget('Dockerfile'), dest: 'Dockerfile' },
],
+ skeleton: 'skeleton.tar',
});
console.log(`Dist workspace ready at ${tempDistWorkspace}`);
diff --git a/packages/cli/src/lib/bundler/server.ts b/packages/cli/src/lib/bundler/server.ts
index 4c4a424d08..e1fb560e22 100644
--- a/packages/cli/src/lib/bundler/server.ts
+++ b/packages/cli/src/lib/bundler/server.ts
@@ -42,7 +42,11 @@ export async function serveBundle(options: ServeOptions) {
contentBase: paths.targetPublic,
contentBasePublicPath: config.output?.publicPath,
publicPath: config.output?.publicPath,
- historyApiFallback: true,
+ historyApiFallback: {
+ // Paths with dots should still use the history fallback.
+ // See https://github.com/facebookincubator/create-react-app/issues/387.
+ disableDotRule: true,
+ },
clientLogLevel: 'warning',
stats: 'errors-warnings',
https: url.protocol === 'https:',
diff --git a/packages/cli/src/lib/packager/index.ts b/packages/cli/src/lib/packager/index.ts
index 5584e1fc21..564317e46a 100644
--- a/packages/cli/src/lib/packager/index.ts
+++ b/packages/cli/src/lib/packager/index.ts
@@ -15,10 +15,14 @@
*/
import fs from 'fs-extra';
-import { resolve as resolvePath, relative as relativePath } from 'path';
+import {
+ join as joinPath,
+ resolve as resolvePath,
+ relative as relativePath,
+} from 'path';
import { paths } from '../paths';
import { run } from '../run';
-import tar from 'tar';
+import tar, { CreateOptions } from 'tar';
import { tmpdir } from 'os';
type LernaPackage = {
@@ -53,6 +57,12 @@ type Options = {
* If set to true, the target packages are built before they are packaged into the workspace.
*/
buildDependencies?: boolean;
+
+ /**
+ * If set, creates a skeleton tarball that contains all package.json files
+ * with the same structure as the workspace dir.
+ */
+ skeleton?: 'skeleton.tar';
};
/**
@@ -89,6 +99,24 @@ export async function createDistWorkspace(
const dest = typeof file === 'string' ? file : file.dest;
await fs.copy(paths.resolveTargetRoot(src), resolvePath(targetDir, dest));
}
+
+ if (options.skeleton) {
+ const skeletonFiles = targets.map(target => {
+ const dir = relativePath(paths.targetRoot, target.location);
+ return joinPath(dir, 'package.json');
+ });
+
+ await tar.create(
+ {
+ file: resolvePath(targetDir, options.skeleton),
+ cwd: targetDir,
+ portable: true,
+ noMtime: true,
+ } as CreateOptions & { noMtime: boolean },
+ skeletonFiles,
+ );
+ }
+
return targetDir;
}
diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs
index e6f9209997..120563c38a 100644
--- a/packages/cli/templates/default-plugin/package.json.hbs
+++ b/packages/cli/templates/default-plugin/package.json.hbs
@@ -24,7 +24,7 @@
"dependencies": {
"@backstage/core": "^{{backstageVersion}}",
"@backstage/theme": "^{{backstageVersion}}",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
diff --git a/packages/core-api/package.json b/packages/core-api/package.json
index a5e23796cd..191394e536 100644
--- a/packages/core-api/package.json
+++ b/packages/core-api/package.json
@@ -31,7 +31,7 @@
"dependencies": {
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@types/react": "^16.9",
"prop-types": "^15.7.2",
diff --git a/packages/core-api/src/apis/ApiFactoryRegistry.test.ts b/packages/core-api/src/apis/ApiFactoryRegistry.test.ts
new file mode 100644
index 0000000000..9f44adaef3
--- /dev/null
+++ b/packages/core-api/src/apis/ApiFactoryRegistry.test.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ApiFactoryRegistry } from './ApiFactoryRegistry';
+import { createApiRef } from './ApiRef';
+
+const aRef = createApiRef({ id: 'a', description: '' });
+const aFactory1 = { api: aRef, deps: {}, factory: () => 1 };
+const aFactory2 = { api: aRef, deps: {}, factory: () => 2 };
+const bRef = createApiRef({ id: 'b', description: '' });
+const bFactory = { api: bRef, deps: {}, factory: () => 'x' };
+const cRef = createApiRef({ id: 'c', description: '' });
+const cFactory = { api: cRef, deps: {}, factory: () => 'y' };
+
+describe('ApiFactoryRegistry', () => {
+ it('should be empty when created', () => {
+ const registry = new ApiFactoryRegistry();
+ expect(registry.getAllApis()).toEqual(new Set());
+ });
+
+ it('should register a factory', () => {
+ const registry = new ApiFactoryRegistry();
+ expect(registry.register('default', aFactory1)).toBe(true);
+ expect(registry.get(aRef)).toBe(aFactory1);
+ expect(registry.getAllApis()).toEqual(new Set([aRef]));
+ });
+
+ it('should prioritize factories based on scope', () => {
+ const registry = new ApiFactoryRegistry();
+ expect(registry.register('default', aFactory1)).toBe(true);
+ expect(registry.get(aRef)).toBe(aFactory1);
+ expect(registry.register('default', aFactory2)).toBe(false);
+ expect(registry.get(aRef)).toBe(aFactory1);
+ expect(registry.register('app', aFactory2)).toBe(true);
+ expect(registry.get(aRef)).toBe(aFactory2);
+ expect(registry.register('default', aFactory1)).toBe(false);
+ expect(registry.get(aRef)).toBe(aFactory2);
+ expect(registry.register('static', aFactory1)).toBe(true);
+ expect(registry.get(aRef)).toBe(aFactory1);
+ expect(registry.register('static', aFactory2)).toBe(false);
+ expect(registry.get(aRef)).toBe(aFactory1);
+ expect(registry.register('app', aFactory2)).toBe(false);
+ expect(registry.get(aRef)).toBe(aFactory1);
+ expect(registry.getAllApis()).toEqual(new Set([aRef]));
+ });
+
+ it('should register multiple factories without conflict', () => {
+ const registry = new ApiFactoryRegistry();
+ expect(registry.register('static', aFactory1)).toBe(true);
+ expect(registry.register('default', bFactory)).toBe(true);
+ expect(registry.register('app', cFactory)).toBe(true);
+ expect(registry.get(aRef)).toBe(aFactory1);
+ expect(registry.get(bRef)).toBe(bFactory);
+ expect(registry.get(cRef)).toBe(cFactory);
+ expect(registry.getAllApis()).toEqual(new Set([aRef, bRef, cRef]));
+ });
+});
diff --git a/packages/core-api/src/apis/ApiFactoryRegistry.ts b/packages/core-api/src/apis/ApiFactoryRegistry.ts
new file mode 100644
index 0000000000..0d36e6c550
--- /dev/null
+++ b/packages/core-api/src/apis/ApiFactoryRegistry.ts
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ ApiFactoryHolder,
+ ApiFactory,
+ AnyApiRef,
+ AnyApiFactory,
+} from './types';
+import { ApiRef } from './ApiRef';
+
+type ApiFactoryScope =
+ | 'default' // Default factories registered by core and plugins
+ | 'app' // Factories registered in the app, overriding default ones
+ | 'static'; // APIs that can't be overridden, e.g. config
+
+enum ScopePriority {
+ default = 10,
+ app = 50,
+ static = 100,
+}
+
+type FactoryTuple = {
+ priority: number;
+ factory: AnyApiFactory;
+};
+
+/**
+ * ApiFactoryRegistry is an ApiFactoryHolder implementation that enables
+ * registration of API Factories with different scope.
+ *
+ * Each scope has an assigned priority, where factories registered with
+ * higher priority scopes override ones with lower priority.
+ */
+export class ApiFactoryRegistry implements ApiFactoryHolder {
+ private readonly factories = new Map();
+
+ /**
+ * Register a new API factory. Returns true if the factory was added
+ * to the registry.
+ *
+ * A factory will not be added to the registry if there is already
+ * an existing factory with the same or higher priority.
+ */
+ register(
+ scope: ApiFactoryScope,
+ factory: ApiFactory,
+ ) {
+ const priority = ScopePriority[scope];
+ const existing = this.factories.get(factory.api);
+ if (existing && existing.priority >= priority) {
+ return false;
+ }
+
+ this.factories.set(factory.api, { priority, factory });
+ return true;
+ }
+
+ get(api: ApiRef): ApiFactory | undefined {
+ const tuple = this.factories.get(api);
+ if (!tuple) {
+ return undefined;
+ }
+ return tuple.factory as ApiFactory;
+ }
+
+ getAllApis(): Set {
+ return new Set(this.factories.keys());
+ }
+}
diff --git a/packages/core-api/src/apis/ApiResolver.test.ts b/packages/core-api/src/apis/ApiResolver.test.ts
new file mode 100644
index 0000000000..3cd40f289e
--- /dev/null
+++ b/packages/core-api/src/apis/ApiResolver.test.ts
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { ApiResolver } from './ApiResolver';
+import { createApiRef } from './ApiRef';
+import { ApiFactoryRegistry } from './ApiFactoryRegistry';
+
+const aRef = createApiRef({ id: 'a', description: '' });
+const bRef = createApiRef({ id: 'b', description: '' });
+const cRef = createApiRef<{ x: string }>({ id: 'c', description: '' });
+
+function createRegistry() {
+ const registry = new ApiFactoryRegistry();
+ registry.register('default', {
+ api: aRef,
+ deps: {},
+ factory: () => 1,
+ });
+ registry.register('default', {
+ api: bRef,
+ deps: {},
+ factory: () => 'b',
+ });
+ registry.register('default', {
+ api: cRef,
+ deps: { b: bRef },
+ factory: ({ b }) => ({ x: 'x', b }),
+ });
+ return registry;
+}
+
+function createLongCyclicRegistry() {
+ const registry = new ApiFactoryRegistry();
+ registry.register('default', {
+ api: aRef,
+ deps: { b: bRef },
+ factory: () => 1,
+ });
+ registry.register('default', {
+ api: bRef,
+ deps: { c: cRef },
+ factory: () => 'b',
+ });
+ registry.register('default', {
+ api: cRef,
+ deps: { a: aRef },
+ factory: () => ({ x: 'x' }),
+ });
+ return registry;
+}
+
+function createShortCyclicRegistry() {
+ const registry = new ApiFactoryRegistry();
+ registry.register('default', {
+ api: aRef,
+ deps: { a: aRef },
+ factory: () => 1,
+ });
+ registry.register('default', {
+ api: bRef,
+ deps: { c: cRef },
+ factory: () => 'b',
+ });
+ registry.register('default', {
+ api: cRef,
+ deps: { b: bRef },
+ factory: () => ({ x: 'x' }),
+ });
+ return registry;
+}
+
+describe('ApiResolver', () => {
+ it('should be created empty', () => {
+ const resolver = new ApiResolver(new ApiFactoryRegistry());
+ expect(resolver.get(aRef)).toBe(undefined);
+ expect(resolver.get(bRef)).toBe(undefined);
+ expect(resolver.get(cRef)).toBe(undefined);
+ });
+
+ it('should instantiate APIs', () => {
+ const resolver = new ApiResolver(createRegistry());
+ expect(resolver.get(aRef)).toBe(1);
+ expect(resolver.get(bRef)).toBe('b');
+ expect(resolver.get(cRef)).toEqual({ x: 'x', b: 'b' });
+ expect(resolver.get(cRef)).toBe(resolver.get(cRef));
+ });
+
+ it('should detect long dependency cycles', () => {
+ const resolver = new ApiResolver(createLongCyclicRegistry());
+ expect(() => resolver.get(aRef)).toThrow(
+ 'Circular dependency of api factory for apiRef{a}',
+ );
+ // Second call for same ref should still throw
+ expect(() => resolver.get(aRef)).toThrow(
+ 'Circular dependency of api factory for apiRef{a}',
+ );
+ expect(() => resolver.get(bRef)).toThrow(
+ 'Circular dependency of api factory for apiRef{b}',
+ );
+ expect(() => resolver.get(cRef)).toThrow(
+ 'Circular dependency of api factory for apiRef{c}',
+ );
+ });
+
+ it('should detect short dependency cycles', () => {
+ const resolver = new ApiResolver(createShortCyclicRegistry());
+ expect(() => resolver.get(aRef)).toThrow(
+ 'Circular dependency of api factory for apiRef{a}',
+ );
+ expect(() => resolver.get(bRef)).toThrow(
+ 'Circular dependency of api factory for apiRef{b}',
+ );
+ expect(() => resolver.get(cRef)).toThrow(
+ 'Circular dependency of api factory for apiRef{c}',
+ );
+ });
+
+ it('should validate a factory holder', () => {
+ expect(() => {
+ ApiResolver.validateFactories(createRegistry(), [aRef, bRef, cRef]);
+ }).not.toThrow();
+ });
+
+ it('should find dependency cycles with validation', () => {
+ const short = createShortCyclicRegistry();
+ expect(() =>
+ ApiResolver.validateFactories(short, short.getAllApis()),
+ ).toThrow('Circular dependency of api factory for apiRef{a}');
+ expect(() => ApiResolver.validateFactories(short, [bRef])).toThrow(
+ 'Circular dependency of api factory for apiRef{b}',
+ );
+ expect(() => ApiResolver.validateFactories(short, [cRef])).toThrow(
+ 'Circular dependency of api factory for apiRef{c}',
+ );
+
+ const long = createLongCyclicRegistry();
+ expect(() =>
+ ApiResolver.validateFactories(long, long.getAllApis()),
+ ).toThrow('Circular dependency of api factory for apiRef{a}');
+ expect(() => ApiResolver.validateFactories(long, [bRef])).toThrow(
+ 'Circular dependency of api factory for apiRef{b}',
+ );
+ expect(() => ApiResolver.validateFactories(long, [cRef])).toThrow(
+ 'Circular dependency of api factory for apiRef{c}',
+ );
+ });
+
+ it('should only call factory func once', () => {
+ const registry = new ApiFactoryRegistry();
+ const factory = jest.fn().mockReturnValue(2);
+ registry.register('default', {
+ api: aRef,
+ deps: {},
+ factory,
+ });
+
+ const resolver = new ApiResolver(registry);
+ expect(factory).toHaveBeenCalledTimes(0);
+ expect(resolver.get(aRef)).toBe(2);
+ expect(factory).toHaveBeenCalledTimes(1);
+ expect(resolver.get(aRef)).toBe(2);
+ expect(factory).toHaveBeenCalledTimes(1);
+ });
+});
diff --git a/packages/core-api/src/apis/ApiTestRegistry.ts b/packages/core-api/src/apis/ApiResolver.ts
similarity index 57%
rename from packages/core-api/src/apis/ApiTestRegistry.ts
rename to packages/core-api/src/apis/ApiResolver.ts
index 7aed3c7917..cb65186a37 100644
--- a/packages/core-api/src/apis/ApiTestRegistry.ts
+++ b/packages/core-api/src/apis/ApiResolver.ts
@@ -15,38 +15,54 @@
*/
import { ApiRef } from './ApiRef';
-import { TypesToApiRefs, AnyApiRef, ApiHolder, ApiFactory } from './types';
+import {
+ ApiHolder,
+ ApiFactoryHolder,
+ AnyApiRef,
+ TypesToApiRefs,
+} from './types';
-export class ApiTestRegistry implements ApiHolder {
+export class ApiResolver implements ApiHolder {
private readonly apis = new Map();
- private factories = new Map<
- AnyApiRef,
- ApiFactory
- >();
- private savedFactories = new Map<
- AnyApiRef,
- ApiFactory
- >();
+
+ /**
+ * Validate factories by making sure that each of the apis can be created
+ * without hitting any circular dependencies.
+ */
+ static validateFactories(
+ factories: ApiFactoryHolder,
+ apis: Iterable,
+ ) {
+ for (const api of apis) {
+ const heap = [api];
+ const allDeps = new Set();
+
+ while (heap.length) {
+ const apiRef = heap.shift()!;
+ const factory = factories.get(apiRef);
+ if (!factory) {
+ continue;
+ }
+
+ for (const dep of Object.values(factory.deps)) {
+ if (dep === api) {
+ throw new Error(`Circular dependency of api factory for ${api}`);
+ }
+ if (!allDeps.has(dep)) {
+ allDeps.add(dep);
+ heap.push(dep);
+ }
+ }
+ }
+ }
+ }
+
+ constructor(private readonly factories: ApiFactoryHolder) {}
get(ref: ApiRef): T | undefined {
return this.load(ref);
}
- register(factory: ApiFactory ): ApiTestRegistry {
- this.factories.set(factory.implements, factory);
- return this;
- }
-
- reset() {
- this.factories = this.savedFactories;
- this.apis.clear();
- }
-
- save(): ApiTestRegistry {
- this.savedFactories = new Map(this.factories);
- return this;
- }
-
private load(ref: ApiRef, loading: AnyApiRef[] = []): T | undefined {
const impl = this.apis.get(ref);
if (impl) {
@@ -58,16 +74,11 @@ export class ApiTestRegistry implements ApiHolder {
return undefined;
}
- if (loading.includes(factory.implements)) {
- throw new Error(
- `Circular dependency of api factory for ${factory.implements}`,
- );
+ if (loading.includes(factory.api)) {
+ throw new Error(`Circular dependency of api factory for ${factory.api}`);
}
- const deps = this.loadDeps(ref, factory.deps, [
- ...loading,
- factory.implements,
- ]);
+ const deps = this.loadDeps(ref, factory.deps, [...loading, factory.api]);
const api = factory.factory(deps);
this.apis.set(ref, api);
return api as T;
diff --git a/packages/core-api/src/apis/ApiTestRegistry.test.ts b/packages/core-api/src/apis/ApiTestRegistry.test.ts
deleted file mode 100644
index fa42e64707..0000000000
--- a/packages/core-api/src/apis/ApiTestRegistry.test.ts
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { ApiTestRegistry } from './ApiTestRegistry';
-import { createApiRef } from './ApiRef';
-
-describe('ApiTestRegistry', () => {
- const aRef = createApiRef({ id: 'a', description: '' });
- const bRef = createApiRef({ id: 'b', description: '' });
- const cRef = createApiRef({ id: 'c', description: '' });
-
- it('should be created', () => {
- const registry = new ApiTestRegistry();
- expect(registry.get(aRef)).toBe(undefined);
- expect(registry.get(bRef)).toBe(undefined);
- expect(registry.get(cRef)).toBe(undefined);
- });
-
- it('should register a factory', () => {
- const registry = new ApiTestRegistry();
- registry.register({ implements: aRef, deps: {}, factory: () => 3 });
- expect(registry.get(aRef)).toBe(3);
- expect(registry.get(bRef)).toBe(undefined);
- expect(registry.get(cRef)).toBe(undefined);
- });
-
- it('should remove factories when resetting', () => {
- const registry = new ApiTestRegistry();
- registry.register({ implements: aRef, deps: {}, factory: () => 3 });
- expect(registry.get(aRef)).toBe(3);
- registry.reset();
- expect(registry.get(aRef)).toBe(undefined);
- });
-
- it('should keep saved factories when resetting', () => {
- const registry = new ApiTestRegistry();
- registry.register({ implements: aRef, deps: {}, factory: () => 3 });
- registry.save();
- registry.register({ implements: bRef, deps: {}, factory: () => 'x' });
- expect(registry.get(aRef)).toBe(3);
- expect(registry.get(bRef)).toBe('x');
- registry.reset();
- expect(registry.get(aRef)).toBe(3);
- expect(registry.get(bRef)).toBe(undefined);
- });
-
- it('should register factories with dependencies', () => {
- // 100% coverage + happy typescript = hasOwnProperty + this atrocity
- const cDeps = Object.create(
- { c: cRef },
- { a: { enumerable: true, value: aRef } },
- );
- cDeps.b = bRef;
-
- const registry = new ApiTestRegistry();
- registry.register({ implements: aRef, deps: {}, factory: () => 3 });
- registry.register({
- implements: bRef,
- deps: { dep: aRef },
- factory: ({ dep }) => `hello ${dep}`,
- });
- registry.register({
- implements: cRef,
- deps: cDeps,
- factory: ({ a, b }) => b.repeat(a),
- });
- expect(registry.get(aRef)).toBe(3);
- expect(registry.get(bRef)).toBe('hello 3');
- expect(registry.get(cRef)).toBe('hello 3hello 3hello 3');
- });
-
- it('should not allow cyclic dependencies', () => {
- const registry = new ApiTestRegistry();
- registry.register({
- implements: aRef,
- deps: { b: bRef },
- factory: () => 1,
- });
- registry.register({
- implements: bRef,
- deps: { c: cRef },
- factory: () => 'b',
- });
- registry.register({
- implements: cRef,
- deps: { a: aRef },
- factory: () => 'c',
- });
- expect(() => registry.get(aRef)).toThrow(
- 'Circular dependency of api factory for apiRef{a}',
- );
- expect(() => registry.get(bRef)).toThrow(
- 'Circular dependency of api factory for apiRef{b}',
- );
- expect(() => registry.get(cRef)).toThrow(
- 'Circular dependency of api factory for apiRef{c}',
- );
- });
-
- it('should throw error if dependency is not available', () => {
- const registry = new ApiTestRegistry();
- registry.register({
- implements: aRef,
- deps: { b: bRef },
- factory: () => 1,
- });
- expect(() => registry.get(aRef)).toThrow(
- 'No API factory available for dependency apiRef{b} of dependent apiRef{a}',
- );
- expect(registry.get(bRef)).toBe(undefined);
- expect(registry.get(cRef)).toBe(undefined);
- });
-
- it('should only call factory func once', () => {
- const registry = new ApiTestRegistry();
- const factory = jest.fn().mockReturnValue(2);
- registry.register({ implements: aRef, deps: {}, factory });
-
- expect(factory).toHaveBeenCalledTimes(0);
- expect(registry.get(aRef)).toBe(2);
- expect(factory).toHaveBeenCalledTimes(1);
- expect(registry.get(aRef)).toBe(2);
- expect(factory).toHaveBeenCalledTimes(1);
- });
-
- it('should call factory again after reset', () => {
- const registry = new ApiTestRegistry();
- const factory = jest.fn().mockReturnValue(2);
- registry.register({ implements: aRef, deps: {}, factory });
- registry.save();
-
- expect(factory).toHaveBeenCalledTimes(0);
- expect(registry.get(aRef)).toBe(2);
- expect(factory).toHaveBeenCalledTimes(1);
- expect(registry.get(aRef)).toBe(2);
- expect(factory).toHaveBeenCalledTimes(1);
- registry.reset();
- expect(registry.get(aRef)).toBe(2);
- expect(factory).toHaveBeenCalledTimes(2);
- });
-});
diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts
index 469414d3dc..1ff8c46dad 100644
--- a/packages/core-api/src/apis/definitions/auth.ts
+++ b/packages/core-api/src/apis/definitions/auth.ts
@@ -299,7 +299,11 @@ export const microsoftAuthApiRef = createApiRef<
* Provides authentication for custom identity providers.
*/
export const oauth2ApiRef = createApiRef<
- OAuthApi & OpenIdConnectApi & ProfileInfoApi & SessionStateApi
+ OAuthApi &
+ OpenIdConnectApi &
+ ProfileInfoApi &
+ SessionStateApi &
+ BackstageIdentityApi
>({
id: 'core.auth.oauth2',
description: 'Example of how to use oauth2 custom provider',
diff --git a/packages/core-api/src/apis/helpers.ts b/packages/core-api/src/apis/helpers.ts
index d8f8e8ac11..7d616acd01 100644
--- a/packages/core-api/src/apis/helpers.ts
+++ b/packages/core-api/src/apis/helpers.ts
@@ -14,15 +14,36 @@
* limitations under the License.
*/
-import { ApiFactory } from './types';
+import { ApiFactory, TypesToApiRefs } from './types';
+import { ApiRef } from './ApiRef';
/**
* Used to infer types for a standalone ApiFactory that isn't immediately passed
* to another function.
* This function doesn't actually do anything, it's only used to infer types.
*/
-export function createApiFactory(
- factory: ApiFactory,
-): ApiFactory {
+export function createApiFactory<
+ Api,
+ Impl extends Api,
+ Deps extends { [name in string]: unknown }
+>(factory: ApiFactory): ApiFactory;
+export function createApiFactory(
+ api: ApiRef,
+ instance: Api,
+): ApiFactory;
+export function createApiFactory<
+ Api,
+ Deps extends { [name in string]: unknown }
+>(
+ factory: ApiFactory | ApiRef,
+ instance?: Api,
+): ApiFactory {
+ if ('id' in factory) {
+ return {
+ api: factory,
+ deps: {} as TypesToApiRefs,
+ factory: () => instance!,
+ };
+ }
return factory;
}
diff --git a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts
index cf582b3a13..3f8ebce5ba 100644
--- a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts
+++ b/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts
@@ -42,6 +42,7 @@ type CreateOptions = {
environment?: string;
provider?: AuthProvider & { id: string };
+ defaultScopes?: string[];
};
export type OAuth2Response = {
@@ -61,8 +62,6 @@ const DEFAULT_PROVIDER = {
icon: OAuth2Icon,
};
-const SCOPE_PREFIX = '';
-
class OAuth2
implements OAuthApi, OpenIdConnectApi, ProfileInfoApi, SessionStateApi {
static create({
@@ -70,6 +69,7 @@ class OAuth2
environment = 'development',
provider = DEFAULT_PROVIDER,
oauthRequestApi,
+ defaultScopes = [],
}: CreateOptions) {
const connector = new DefaultAuthConnector({
discoveryApi,
@@ -93,11 +93,7 @@ class OAuth2
const sessionManager = new RefreshingAuthSessionManager({
connector,
- defaultScopes: new Set([
- 'openid',
- `${SCOPE_PREFIX}userinfo.email`,
- `${SCOPE_PREFIX}userinfo.profile`,
- ]),
+ defaultScopes: new Set(defaultScopes),
sessionScopes: (session: OAuth2Session) => session.providerInfo.scopes,
sessionShouldRefresh: (session: OAuth2Session) => {
const expiresInSec =
diff --git a/packages/core-api/src/apis/index.ts b/packages/core-api/src/apis/index.ts
index c3689b6703..c856a13668 100644
--- a/packages/core-api/src/apis/index.ts
+++ b/packages/core-api/src/apis/index.ts
@@ -16,7 +16,6 @@
export { ApiProvider, useApi, useApiHolder } from './ApiProvider';
export { ApiRegistry } from './ApiRegistry';
-export { ApiTestRegistry } from './ApiTestRegistry';
export * from './ApiRef';
export * from './types';
export * from './helpers';
diff --git a/packages/core-api/src/apis/types.ts b/packages/core-api/src/apis/types.ts
index 4ee9b39ca9..61c229b18e 100644
--- a/packages/core-api/src/apis/types.ts
+++ b/packages/core-api/src/apis/types.ts
@@ -16,13 +16,13 @@
import { ApiRef } from './ApiRef';
-export type AnyApiRef = ApiRef;
+export type AnyApiRef = ApiRef;
export type ApiRefType = T extends ApiRef ? U : never;
export type TypesToApiRefs = { [key in keyof T]: ApiRef };
-export type ApiRefsToTypes }> = {
+export type ApiRefsToTypes }> = {
[key in keyof T]: ApiRefType;
};
@@ -30,8 +30,16 @@ export type ApiHolder = {
get(api: ApiRef): T | undefined;
};
-export type ApiFactory = {
- implements: ApiRef;
+export type ApiFactory = {
+ api: ApiRef;
deps: TypesToApiRefs;
- factory(deps: Deps): Impl extends Api ? Impl : never;
+ factory(deps: Deps): Api;
+};
+
+export type AnyApiFactory = ApiFactory;
+
+export type ApiFactoryHolder = {
+ get(
+ api: ApiRef,
+ ): ApiFactory | undefined;
};
diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx
index bfa26b7547..9dd2fb138f 100644
--- a/packages/core-api/src/app/App.tsx
+++ b/packages/core-api/src/app/App.tsx
@@ -26,7 +26,6 @@ import {
BackstageApp,
AppComponents,
AppConfigLoader,
- Apis,
SignInResult,
SignInPageProps,
} from './types';
@@ -42,7 +41,6 @@ import { AppThemeProvider } from './AppThemeProvider';
import { IconComponent, SystemIcons, SystemIconKey } from '../icons';
import {
- ApiHolder,
ApiProvider,
ApiRegistry,
AppTheme,
@@ -51,18 +49,22 @@ import {
configApiRef,
ConfigReader,
useApi,
+ AnyApiFactory,
+ ApiHolder,
} from '../apis';
-import { ApiAggregator } from '../apis/ApiAggregator';
import { useAsync } from 'react-use';
import { AppIdentity } from './AppIdentity';
+import { ApiFactoryRegistry } from '../apis/ApiFactoryRegistry';
+import { ApiResolver } from '../apis/ApiResolver';
type FullAppOptions = {
- apis: Apis;
+ apis: Iterable;
icons: SystemIcons;
plugins: BackstagePlugin[];
components: AppComponents;
themes: AppTheme[];
configLoader?: AppConfigLoader;
+ defaultApis: Iterable;
};
function useConfigLoader(
@@ -101,31 +103,27 @@ function useConfigLoader(
}
export class PrivateAppImpl implements BackstageApp {
- private apis?: ApiHolder = undefined;
+ private apiHolder?: ApiHolder;
+ private configApi?: ConfigApi;
+
+ private readonly apis: Iterable;
private readonly icons: SystemIcons;
private readonly plugins: BackstagePlugin[];
private readonly components: AppComponents;
private readonly themes: AppTheme[];
private readonly configLoader?: AppConfigLoader;
+ private readonly defaultApis: Iterable;
private readonly identityApi = new AppIdentity();
- private apisOrFactory: Apis;
-
constructor(options: FullAppOptions) {
- this.apisOrFactory = options.apis;
+ this.apis = options.apis;
this.icons = options.icons;
this.plugins = options.plugins;
this.components = options.components;
this.themes = options.themes;
this.configLoader = options.configLoader;
- }
-
- getApis(): ApiHolder {
- if (!this.apis) {
- throw new Error('Tried to access APIs before app was loaded');
- }
- return this.apis;
+ this.defaultApis = options.defaultApis;
}
getPlugins(): BackstagePlugin[] {
@@ -186,12 +184,12 @@ export class PrivateAppImpl implements BackstageApp {
}
}
- const FeatureFlags = this.apis && this.apis.get(featureFlagsApiRef);
- if (FeatureFlags) {
- FeatureFlags.registeredFeatureFlags = registeredFeatureFlags;
+ const featureFlags = this.getApiHolder().get(featureFlagsApiRef);
+ if (featureFlags) {
+ featureFlags.registeredFeatureFlags = registeredFeatureFlags;
}
- routes.push( } />);
+ routes.push( } />);
return routes;
}
@@ -210,28 +208,14 @@ export class PrivateAppImpl implements BackstageApp {
);
if ('node' in loadedConfig) {
+ // Loading or error
return loadedConfig.node;
}
- const configApi = loadedConfig.api;
- const appApis = ApiRegistry.from([
- [appThemeApiRef, appThemeApi],
- [configApiRef, configApi],
- [identityApiRef, this.identityApi],
- ]);
-
- if (!this.apis) {
- if ('get' in this.apisOrFactory) {
- this.apis = this.apisOrFactory;
- } else {
- this.apis = this.apisOrFactory(configApi);
- }
- }
-
- const apis = new ApiAggregator(this.apis, appApis);
+ this.configApi = loadedConfig.api;
return (
-
+
{children}
@@ -306,6 +290,67 @@ export class PrivateAppImpl implements BackstageApp {
return AppRouter;
}
+ private getApiHolder(): ApiHolder {
+ if (this.apiHolder) {
+ return this.apiHolder;
+ }
+
+ const registry = new ApiFactoryRegistry();
+
+ registry.register('static', {
+ api: appThemeApiRef,
+ deps: {},
+ factory: () => AppThemeSelector.createWithStorage(this.themes),
+ });
+ registry.register('static', {
+ api: configApiRef,
+ deps: {},
+ factory: () => {
+ if (!this.configApi) {
+ throw new Error(
+ 'Tried to access config API before config was loaded',
+ );
+ }
+ return this.configApi;
+ },
+ });
+ registry.register('static', {
+ api: identityApiRef,
+ deps: {},
+ factory: () => this.identityApi,
+ });
+
+ for (const factory of this.defaultApis) {
+ registry.register('default', factory);
+ }
+
+ for (const plugin of this.plugins) {
+ for (const factory of plugin.getApis()) {
+ if (!registry.register('default', factory)) {
+ throw new Error(
+ `Plugin ${plugin.getId()} tried to register duplicate or forbidden API factory for ${
+ factory.api
+ }`,
+ );
+ }
+ }
+ }
+
+ for (const factory of this.apis) {
+ if (!registry.register('app', factory)) {
+ throw new Error(
+ `Duplicate or forbidden API factory for ${factory.api} in app`,
+ );
+ }
+ }
+
+ ApiResolver.validateFactories(registry, registry.getAllApis());
+
+ this.apiHolder = new ApiResolver(registry);
+
+ return this.apiHolder;
+ }
+
verify() {
const pluginIds = new Set();
diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts
index db7a0897b1..565e073aed 100644
--- a/packages/core-api/src/app/types.ts
+++ b/packages/core-api/src/app/types.ts
@@ -17,8 +17,8 @@
import { ComponentType } from 'react';
import { IconComponent, SystemIconKey, SystemIcons } from '../icons';
import { BackstagePlugin } from '../plugin';
-import { ApiHolder } from '../apis';
-import { AppTheme, ConfigApi, ProfileInfo } from '../apis/definitions';
+import { AnyApiFactory } from '../apis';
+import { AppTheme, ProfileInfo } from '../apis/definitions';
import { AppConfig } from '@backstage/config';
export type BootErrorPageProps = {
@@ -77,16 +77,12 @@ export type AppComponents = {
*/
export type AppConfigLoader = () => Promise;
-// TODO(Rugvip): Temporary workaround for accessing config when instantiating APIs, we might want to do this differently
-export type Apis = ApiHolder | ((config: ConfigApi) => ApiHolder);
-
export type AppOptions = {
/**
- * A holder of all APIs available in the app.
- *
- * Use for example ApiRegistry or ApiTestRegistry.
+ * A collection of ApiFactories to register in the application to either
+ * add add new ones, or override factories provided by default or by plugins.
*/
- apis?: Apis;
+ apis?: Iterable;
/**
* Supply icons to override the default ones.
@@ -138,11 +134,6 @@ export type AppOptions = {
};
export type BackstageApp = {
- /**
- * Get the holder for all APIs available in the app.
- */
- getApis(): ApiHolder;
-
/**
* Returns all plugins registered for the app.
*/
diff --git a/packages/core-api/src/plugin/Plugin.tsx b/packages/core-api/src/plugin/Plugin.tsx
index bf98919c2b..1835ec03ad 100644
--- a/packages/core-api/src/plugin/Plugin.tsx
+++ b/packages/core-api/src/plugin/Plugin.tsx
@@ -24,9 +24,11 @@ import {
} from './types';
import { validateBrowserCompat, validateFlagName } from '../app/FeatureFlags';
import { RouteRef } from '../routing';
+import { AnyApiFactory } from '../apis';
export type PluginConfig = {
id: string;
+ apis?: Iterable;
register?(hooks: PluginHooks): void;
};
@@ -65,6 +67,10 @@ export class PluginImpl {
return this.config.id;
}
+ getApis(): Iterable {
+ return this.config.apis ?? [];
+ }
+
output(): PluginOutput[] {
if (this.storedOutput) {
return this.storedOutput;
diff --git a/packages/core-api/src/plugin/types.ts b/packages/core-api/src/plugin/types.ts
index fabe97434e..12992f3620 100644
--- a/packages/core-api/src/plugin/types.ts
+++ b/packages/core-api/src/plugin/types.ts
@@ -16,6 +16,7 @@
import { ComponentType } from 'react';
import { RouteRef } from '../routing';
+import { AnyApiFactory } from '../apis';
export type RouteOptions = {
// Whether the route path must match exactly, defaults to true.
@@ -70,4 +71,5 @@ export type PluginOutput =
export type BackstagePlugin = {
getId(): string;
output(): PluginOutput[];
+ getApis(): Iterable;
};
diff --git a/packages/core/package.json b/packages/core/package.json
index d6731aca9a..7e9932bdc1 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -32,7 +32,7 @@
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/core-api": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@types/react": "^16.9",
diff --git a/packages/core/src/api-wrappers/createApp.tsx b/packages/core/src/api-wrappers/createApp.tsx
index 9d2096cfff..7fe77423e2 100644
--- a/packages/core/src/api-wrappers/createApp.tsx
+++ b/packages/core/src/api-wrappers/createApp.tsx
@@ -17,7 +17,6 @@
import React, { FC } from 'react';
import privateExports, {
AppOptions,
- ApiRegistry,
defaultSystemIcons,
BootErrorPageProps,
AppConfigLoader,
@@ -26,6 +25,7 @@ import { BrowserRouter, MemoryRouter } from 'react-router-dom';
import { ErrorPage } from '../layout/ErrorPage';
import { Progress } from '../components/Progress';
+import { defaultApis } from './defaultApis';
import { lightTheme, darkTheme } from '@backstage/theme';
import { AppConfig, JsonObject } from '@backstage/config';
@@ -94,7 +94,7 @@ export function createApp(options?: AppOptions) {
);
};
- const apis = options?.apis ?? ApiRegistry.from([]);
+ const apis = options?.apis ?? [];
const icons = { ...defaultSystemIcons, ...options?.icons };
const plugins = options?.plugins ?? [];
const components = {
@@ -127,6 +127,7 @@ export function createApp(options?: AppOptions) {
components,
themes,
configLoader,
+ defaultApis,
});
app.verify();
diff --git a/packages/core/src/api-wrappers/defaultApis.ts b/packages/core/src/api-wrappers/defaultApis.ts
new file mode 100644
index 0000000000..a3f0cb0251
--- /dev/null
+++ b/packages/core/src/api-wrappers/defaultApis.ts
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ alertApiRef,
+ errorApiRef,
+ AlertApiForwarder,
+ ErrorApiForwarder,
+ ErrorAlerter,
+ featureFlagsApiRef,
+ FeatureFlags,
+ discoveryApiRef,
+ GoogleAuth,
+ GithubAuth,
+ OAuth2,
+ OktaAuth,
+ GitlabAuth,
+ Auth0Auth,
+ MicrosoftAuth,
+ oauthRequestApiRef,
+ OAuthRequestManager,
+ googleAuthApiRef,
+ githubAuthApiRef,
+ oauth2ApiRef,
+ oktaAuthApiRef,
+ gitlabAuthApiRef,
+ auth0AuthApiRef,
+ microsoftAuthApiRef,
+ storageApiRef,
+ WebStorage,
+ createApiFactory,
+ configApiRef,
+ UrlPatternDiscovery,
+} from '@backstage/core-api';
+
+export const defaultApis = [
+ createApiFactory({
+ api: discoveryApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) =>
+ UrlPatternDiscovery.compile(
+ `${configApi.getString('backend.baseUrl')}/api/{{ pluginId }}`,
+ ),
+ }),
+ createApiFactory(alertApiRef, new AlertApiForwarder()),
+ createApiFactory({
+ api: errorApiRef,
+ deps: { alertApi: alertApiRef },
+ factory: ({ alertApi }) =>
+ new ErrorAlerter(alertApi, new ErrorApiForwarder()),
+ }),
+ createApiFactory({
+ api: storageApiRef,
+ deps: { errorApi: errorApiRef },
+ factory: ({ errorApi }) => WebStorage.create({ errorApi }),
+ }),
+ createApiFactory(featureFlagsApiRef, new FeatureFlags()),
+ createApiFactory(oauthRequestApiRef, new OAuthRequestManager()),
+ createApiFactory({
+ api: googleAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi }) =>
+ GoogleAuth.create({ discoveryApi, oauthRequestApi }),
+ }),
+ createApiFactory({
+ api: microsoftAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi }) =>
+ MicrosoftAuth.create({ discoveryApi, oauthRequestApi }),
+ }),
+ createApiFactory({
+ api: githubAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi }) =>
+ GithubAuth.create({ discoveryApi, oauthRequestApi }),
+ }),
+ createApiFactory({
+ api: oktaAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi }) =>
+ OktaAuth.create({ discoveryApi, oauthRequestApi }),
+ }),
+ createApiFactory({
+ api: gitlabAuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi }) =>
+ GitlabAuth.create({ discoveryApi, oauthRequestApi }),
+ }),
+ createApiFactory({
+ api: auth0AuthApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi }) =>
+ Auth0Auth.create({ discoveryApi, oauthRequestApi }),
+ }),
+ createApiFactory({
+ api: oauth2ApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ oauthRequestApi: oauthRequestApiRef,
+ },
+ factory: ({ discoveryApi, oauthRequestApi }) =>
+ OAuth2.create({ discoveryApi, oauthRequestApi }),
+ }),
+];
diff --git a/packages/core/src/components/WarningPanel/WarningPanel.tsx b/packages/core/src/components/WarningPanel/WarningPanel.tsx
index ab0927fc4c..ae4d2bff02 100644
--- a/packages/core/src/components/WarningPanel/WarningPanel.tsx
+++ b/packages/core/src/components/WarningPanel/WarningPanel.tsx
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-import React, { FC } from 'react';
-import { Typography, makeStyles } from '@material-ui/core';
import { BackstageTheme } from '@backstage/theme';
+import { makeStyles, Typography } from '@material-ui/core';
import ErrorOutline from '@material-ui/icons/ErrorOutline';
+import React from 'react';
const useErrorOutlineStyles = makeStyles(theme => ({
root: {
@@ -60,9 +60,10 @@ const useStyles = makeStyles(theme => ({
type Props = {
message?: React.ReactNode;
title?: string;
+ children?: React.ReactNode;
};
-export const WarningPanel: FC = props => {
+export const WarningPanel = (props: Props) => {
const classes = useStyles(props);
const { title, message, children } = props;
return (
diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx
index 86e9c79ced..6914d396b1 100644
--- a/packages/core/src/layout/InfoCard/InfoCard.tsx
+++ b/packages/core/src/layout/InfoCard/InfoCard.tsx
@@ -14,12 +14,13 @@
* limitations under the License.
*/
-import React, { FC, ReactNode } from 'react';
+import React, { ReactNode } from 'react';
import {
Card,
CardActions,
CardContent,
CardHeader,
+ CardHeaderProps,
Divider,
withStyles,
makeStyles,
@@ -29,22 +30,26 @@ import { ErrorBoundary } from '../ErrorBoundary';
import { BottomLink, BottomLinkProps } from '../BottomLink';
const useStyles = makeStyles(theme => ({
- header: {
- padding: theme.spacing(2, 2, 2, 2.5),
- },
noPadding: {
padding: 0,
'&:last-child': {
paddingBottom: 0,
},
},
+ header: {
+ padding: theme.spacing(2, 2, 2, 2.5),
+ },
+ headerTitle: {
+ fontWeight: 700,
+ },
+ headerSubheader: {
+ paddingTop: theme.spacing(1),
+ },
+ headerAvatar: {},
+ headerAction: {},
+ headerContent: {},
}));
-const BoldHeader = withStyles(theme => ({
- title: { fontWeight: 700 },
- subheader: { paddingTop: theme.spacing(1) },
-}))(CardHeader);
-
const CardActionsTopRight = withStyles(theme => ({
root: {
display: 'inline-block',
@@ -130,7 +135,7 @@ type Props = {
cardStyle?: object;
children?: ReactNode;
headerStyle?: object;
- headerProps?: object;
+ headerProps?: CardHeaderProps;
actionsClassName?: string;
actions?: ReactNode;
cardClassName?: string;
@@ -139,7 +144,7 @@ type Props = {
noPadding?: boolean;
};
-export const InfoCard: FC = ({
+export const InfoCard = ({
title,
subheader,
divider,
@@ -155,7 +160,7 @@ export const InfoCard: FC = ({
actionsTopRight,
className,
noPadding,
-}) => {
+}: Props): JSX.Element => {
const classes = useStyles();
/**
@@ -186,8 +191,15 @@ export const InfoCard: FC = ({
{title && (
<>
- = () => (
@@ -29,11 +40,20 @@ const App: FC<{}> = () => (
+
}
/>
-
+ } />
+ }
+ />
+ }
+ />
{deprecatedAppRoutes}
diff --git a/packages/create-app/templates/default-app/packages/app/src/apis.ts b/packages/create-app/templates/default-app/packages/app/src/apis.ts
index 14351eaba7..e0503e504f 100644
--- a/packages/create-app/templates/default-app/packages/app/src/apis.ts
+++ b/packages/create-app/templates/default-app/packages/app/src/apis.ts
@@ -1,89 +1,17 @@
import {
- ApiRegistry,
- alertApiRef,
- errorApiRef,
- AlertApiForwarder,
- ConfigApi,
- ErrorApiForwarder,
- ErrorAlerter,
discoveryApiRef,
UrlPatternDiscovery,
- oauthRequestApiRef,
- OAuthRequestManager,
- storageApiRef,
- WebStorage,
+ createApiFactory,
+ configApiRef,
} from '@backstage/core';
-import {
- lighthouseApiRef,
- LighthouseRestApi,
-} from '@backstage/plugin-lighthouse';
-
-import {
- GithubActionsClient,
- githubActionsApiRef,
-} from '@backstage/plugin-github-actions';
-
-import {
- techdocsStorageApiRef,
- TechDocsStorageApi,
-} from '@backstage/plugin-techdocs';
-
-import { techRadarApiRef, TechRadar } from '@backstage/plugin-tech-radar';
-
-import { catalogApiRef, CatalogClient } from '@backstage/plugin-catalog';
-import { CircleCIApi, circleCIApiRef } from '@backstage/plugin-circleci';
-
-import { scaffolderApiRef, ScaffolderApi } from '@backstage/plugin-scaffolder';
-
-
-
-export const apis = (config: ConfigApi) => {
- // eslint-disable-next-line no-console
- console.log(`Creating APIs for ${config.getString('app.title')}`);
-
- const backendUrl = config.getString('backend.baseUrl');
- const techdocsStorageUrl = config.getString('techdocs.storageUrl');
-
- const builder = ApiRegistry.builder();
-
- const discoveryApi = builder.add(
- discoveryApiRef,
- UrlPatternDiscovery.compile(`${backendUrl}/{{ pluginId }}`),
- );
- const alertApi = builder.add(alertApiRef, new AlertApiForwarder());
- const errorApi = builder.add(
- errorApiRef,
- new ErrorAlerter(alertApi, new ErrorApiForwarder()),
- );
-
- builder.add(storageApiRef, WebStorage.create({ errorApi }));
- builder.add(oauthRequestApiRef, new OAuthRequestManager());
-
- builder.add(catalogApiRef, new CatalogClient({ discoveryApi }));
- builder.add(githubActionsApiRef, new GithubActionsClient());
-
- builder.add(lighthouseApiRef, new LighthouseRestApi('http://localhost:3003'));
-
- builder.add(
- circleCIApiRef,
- new CircleCIApi(`${backendUrl}/proxy/circleci/api`),
- );
-
- builder.add(scaffolderApiRef, new ScaffolderApi({ discoveryApi }));
-
- builder.add(
- techRadarApiRef,
- new TechRadar({
- width: 1500,
- height: 800,
- }),
- );
-
- builder.add(
- techdocsStorageApiRef,
- new TechDocsStorageApi({ apiOrigin: techdocsStorageUrl }),
- );
-
- return builder.build();
-};
+export const apis = [
+ createApiFactory({
+ api: discoveryApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) =>
+ UrlPatternDiscovery.compile(
+ `${configApi.getString('backend.baseUrl')}/{{ pluginId }}`,
+ ),
+ }),
+];
diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx
index be22003f4f..5480d8218a 100644
--- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx
+++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx
@@ -13,8 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import {
+ Router as GitHubActionsRouter,
+ isPluginApplicableToEntity as isGitHubActionsAvailable,
+} from '@backstage/plugin-github-actions';
+import {
+ Router as CircleCIRouter,
+ isPluginApplicableToEntity as isCircleCIAvailable,
+} from '@backstage/plugin-circleci';
import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs';
-import { Router as GitHubActionsRouter } from '@backstage/plugin-github-actions';
+import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs';
+
import React from 'react';
import {
EntityPageLayout,
@@ -22,9 +31,33 @@ import {
AboutCard,
} from '@backstage/plugin-catalog';
import { Entity } from '@backstage/catalog-model';
+import { Grid } from '@material-ui/core';
+import { WarningPanel } from '@backstage/core';
+
+const CICDSwitcher = ({ entity }: { entity: Entity }) => {
+ // This component is just an example of how you can implement your company's logic in entity page.
+ // You can for example enforce that all components of type 'service' should use GitHubActions
+ switch (true) {
+ case isGitHubActionsAvailable(entity):
+ return ;
+ case isCircleCIAvailable(entity):
+ return ;
+ default:
+ return (
+
+ No CI/CD is available for this entity. Check corresponding
+ annotations!
+
+ );
+ }
+};
const OverviewContent = ({ entity }: { entity: Entity }) => (
-
+
+
+
+
+
);
const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
@@ -37,13 +70,18 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
}
+ element={ }
/>
}
/>
+ }
+ />
);
@@ -57,7 +95,12 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => (
}
+ element={ }
+ />
+ }
/>
);
@@ -69,6 +112,11 @@ const DefaultEntityPage = ({ entity }: { entity: Entity }) => (
title="Overview"
element={ }
/>
+ }
+ />
);
diff --git a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx
index fa9a397751..9e343b86ac 100644
--- a/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx
+++ b/packages/create-app/templates/default-app/packages/app/src/sidebar.tsx
@@ -2,7 +2,6 @@ import React from 'react';
import HomeIcon from '@material-ui/icons/Home';
import LibraryBooks from '@material-ui/icons/LibraryBooks';
import CreateComponentIcon from '@material-ui/icons/AddCircleOutline';
-import ExploreIcon from '@material-ui/icons/Explore';
import BuildIcon from '@material-ui/icons/BuildRounded';
import RuleIcon from '@material-ui/icons/AssignmentTurnedIn';
import MapIcon from '@material-ui/icons/MyLocation';
@@ -23,7 +22,6 @@ export const AppSidebar = () => (
{/* Global nav, not org-specific */}
-
diff --git a/packages/create-app/templates/default-app/tsconfig.json b/packages/create-app/templates/default-app/tsconfig.json
index b1ec99b986..ba3f90177d 100644
--- a/packages/create-app/templates/default-app/tsconfig.json
+++ b/packages/create-app/templates/default-app/tsconfig.json
@@ -9,7 +9,6 @@
"exclude": ["node_modules"],
"compilerOptions": {
"outDir": "dist-types",
- "rootDir": ".",
- "skipLibCheck": true
+ "rootDir": "."
}
}
diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json
index c917130cbd..fe7272bf04 100644
--- a/packages/dev-utils/package.json
+++ b/packages/dev-utils/package.json
@@ -33,7 +33,7 @@
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
diff --git a/packages/dev-utils/src/devApp/apiFactories.ts b/packages/dev-utils/src/devApp/apiFactories.ts
deleted file mode 100644
index 4cf208f5d2..0000000000
--- a/packages/dev-utils/src/devApp/apiFactories.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import {
- alertApiRef,
- errorApiRef,
- ErrorApiForwarder,
- AlertApi,
- createApiFactory,
- ErrorAlerter,
- AlertApiForwarder,
- oauthRequestApiRef,
- OAuthRequestManager,
- UrlPatternDiscovery,
- discoveryApiRef,
- GoogleAuth,
- googleAuthApiRef,
- GithubAuth,
- githubAuthApiRef,
- GitlabAuth,
- gitlabAuthApiRef,
- Auth0Auth,
- auth0AuthApiRef,
-} from '@backstage/core';
-
-// TODO(rugvip): We should likely figure out how to reuse all of these between apps
-// and plugin serve with minimal boilerplate. For example we might move everything
-// to DI, and provide factories for the default implementations, so this just becomes
-// a list of things like `[ErrorApiForwarder.factory, AlertApiDialog.factory]`.
-
-export const alertApiFactory = createApiFactory({
- implements: alertApiRef,
- deps: {},
- factory: (): AlertApi => new AlertApiForwarder(),
-});
-
-export const errorApiFactory = createApiFactory({
- implements: errorApiRef,
- deps: { alertApi: alertApiRef },
- factory: ({ alertApi }) =>
- new ErrorAlerter(alertApi, new ErrorApiForwarder()),
-});
-
-export const oauthRequestApiFactory = createApiFactory({
- implements: oauthRequestApiRef,
- deps: {},
- factory: () => new OAuthRequestManager(),
-});
-
-export const discoveryApiFactory = createApiFactory({
- implements: discoveryApiRef,
- deps: {},
- factory: () =>
- UrlPatternDiscovery.compile(`http://localhost:7000/{{ pluginId }}`),
-});
-
-export const googleAuthApiFactory = createApiFactory({
- implements: googleAuthApiRef,
- deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef },
- factory: ({ discoveryApi, oauthRequestApi }) =>
- GoogleAuth.create({
- discoveryApi,
- oauthRequestApi,
- }),
-});
-
-export const githubAuthApiFactory = createApiFactory({
- implements: githubAuthApiRef,
- deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef },
- factory: ({ discoveryApi, oauthRequestApi }) =>
- GithubAuth.create({
- discoveryApi,
- oauthRequestApi,
- }),
-});
-
-export const gitlabAuthApiFactory = createApiFactory({
- implements: gitlabAuthApiRef,
- deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef },
- factory: ({ discoveryApi, oauthRequestApi }) =>
- GitlabAuth.create({
- discoveryApi,
- oauthRequestApi,
- }),
-});
-
-export const auth0AuthApiFactory = createApiFactory({
- implements: auth0AuthApiRef,
- deps: { discoveryApi: discoveryApiRef, oauthRequestApi: oauthRequestApiRef },
- factory: ({ discoveryApi, oauthRequestApi }) =>
- Auth0Auth.create({
- discoveryApi,
- oauthRequestApi,
- }),
-});
diff --git a/packages/dev-utils/src/devApp/render.test.tsx b/packages/dev-utils/src/devApp/render.test.tsx
new file mode 100644
index 0000000000..fbebf2039b
--- /dev/null
+++ b/packages/dev-utils/src/devApp/render.test.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { render } from '@testing-library/react';
+import { useApi, configApiRef } from '@backstage/core';
+import { createDevApp } from './render';
+
+const anyEnv = (process.env = { ...process.env }) as any;
+
+describe('DevAppBuilder', () => {
+ it('should be able to render a component in a dev app', async () => {
+ anyEnv.APP_CONFIG = [
+ { context: 'test', data: { app: { title: 'Test App' } } },
+ ];
+
+ const MyComponent = () => {
+ const configApi = useApi(configApiRef);
+ return My App: {configApi.getString('app.title')}
;
+ };
+
+ const DevApp = createDevApp()
+ .addRootChild( )
+ .build();
+
+ const rendered = render( );
+
+ expect(await rendered.findByText('My App: Test App')).toBeInTheDocument();
+ });
+});
diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx
index cde39fdd05..f223fcd4e7 100644
--- a/packages/dev-utils/src/devApp/render.tsx
+++ b/packages/dev-utils/src/devApp/render.tsx
@@ -26,12 +26,10 @@ import {
SidebarSpacer,
ApiFactory,
createPlugin,
- ApiTestRegistry,
- ApiHolder,
AlertDisplay,
OAuthRequestDialog,
+ AnyApiFactory,
} from '@backstage/core';
-import * as defaultApiFactories from './apiFactories';
import SentimentDissatisfiedIcon from '@material-ui/icons/SentimentDissatisfied';
// TODO(rugvip): export proper plugin type from core that isn't the plugin class
@@ -43,7 +41,7 @@ type BackstagePlugin = ReturnType;
*/
class DevAppBuilder {
private readonly plugins = new Array();
- private readonly factories = new Array>();
+ private readonly apis = new Array();
private readonly rootChildren = new Array();
/**
@@ -57,10 +55,10 @@ class DevAppBuilder {
/**
* Register an API factory to add to the app
*/
- registerApiFactory(
- factory: ApiFactory,
+ registerApi(
+ factory: ApiFactory,
): DevAppBuilder {
- this.factories.push(factory);
+ this.apis.push(factory);
return this;
}
@@ -79,7 +77,7 @@ class DevAppBuilder {
*/
build(): ComponentType<{}> {
const app = createApp({
- apis: this.setupApiRegistry(this.factories),
+ apis: this.apis,
plugins: this.plugins,
});
@@ -170,30 +168,6 @@ class DevAppBuilder {
);
}
- // Set up an API registry that merges together default implementations with ones provided through config.
- private setupApiRegistry(
- providedFactories: ApiFactory[],
- ): ApiHolder {
- const providedApis = new Set(
- providedFactories.map(factory => factory.implements),
- );
-
- // Exlude any default API factory that we receive a factory for in the config
- const defaultFactories = Object.values(defaultApiFactories).filter(
- factory => !providedApis.has(factory.implements),
- );
- const allFactories = [...defaultFactories, ...providedFactories];
-
- // Use a test registry with dependency injection so that the consumer
- // can override APIs but still depend on the default implementations.
- const registry = new ApiTestRegistry();
- for (const factory of allFactories) {
- registry.register(factory);
- }
-
- return registry;
- }
-
private findPluginPaths(plugins: BackstagePlugin[]) {
const paths = new Array();
diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json
index d28b66a9ed..8567af8c14 100644
--- a/packages/test-utils/package.json
+++ b/packages/test-utils/package.json
@@ -33,7 +33,7 @@
"@backstage/core-api": "^0.1.1-alpha.21",
"@backstage/test-utils-core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^10.4.1",
"@testing-library/user-event": "^12.0.7",
diff --git a/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts b/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts
index 9b22a352e8..7bf50314d0 100644
--- a/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts
+++ b/packages/test-utils/src/testUtils/apis/ErrorApi/MockErrorApi.ts
@@ -14,12 +14,7 @@
* limitations under the License.
*/
-import {
- ErrorApi,
- ErrorContext,
- errorApiRef,
- Observable,
-} from '@backstage/core-api';
+import { ErrorApi, ErrorContext, Observable } from '@backstage/core-api';
type Options = {
collect?: boolean;
@@ -40,12 +35,6 @@ const nullObservable = {
};
export class MockErrorApi implements ErrorApi {
- static factory = {
- implements: errorApiRef,
- deps: {},
- factory: () => new MockErrorApi(),
- };
-
private readonly errors = new Array();
private readonly waiters = new Set();
diff --git a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts
index a3a4ef16d6..006b44a49c 100644
--- a/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts
+++ b/packages/test-utils/src/testUtils/apis/StorageApi/MockStorageApi.ts
@@ -17,7 +17,6 @@
import {
Observable,
StorageApi,
- storageApiRef,
StorageValueChange,
} from '@backstage/core-api';
import ObservableImpl from 'zen-observable';
@@ -25,12 +24,6 @@ import ObservableImpl from 'zen-observable';
export type MockStorageBucket = { [key: string]: any };
export class MockStorageApi implements StorageApi {
- static factory = {
- implements: storageApiRef,
- deps: {},
- factory: () => MockStorageApi.create(),
- };
-
private readonly namespace: string;
private readonly data: MockStorageBucket;
diff --git a/packages/test-utils/src/testUtils/appWrappers.test.tsx b/packages/test-utils/src/testUtils/appWrappers.test.tsx
index 19c02c2158..464c547ecc 100644
--- a/packages/test-utils/src/testUtils/appWrappers.test.tsx
+++ b/packages/test-utils/src/testUtils/appWrappers.test.tsx
@@ -49,9 +49,6 @@ describe('wrapInTestApp', () => {
expect.stringMatching(
/^Warning: An update to %s inside a test was not wrapped in act\(...\)/,
),
- expect.stringMatching(
- /^Warning: An update to %s inside a test was not wrapped in act\(...\)/,
- ),
]);
});
diff --git a/packages/test-utils/src/testUtils/appWrappers.tsx b/packages/test-utils/src/testUtils/appWrappers.tsx
index 2e3a73e346..182de1ef63 100644
--- a/packages/test-utils/src/testUtils/appWrappers.tsx
+++ b/packages/test-utils/src/testUtils/appWrappers.tsx
@@ -24,7 +24,7 @@ import privateExports, {
} from '@backstage/core-api';
import { RenderResult } from '@testing-library/react';
import { renderWithEffects } from '@backstage/test-utils-core';
-import { createMockApiRegistry } from './mockApiRegistry';
+import { mockApis } from './mockApis';
const { PrivateAppImpl } = privateExports;
@@ -58,10 +58,9 @@ export function wrapInTestApp(
options: TestAppOptions = {},
): ReactElement {
const { routeEntries = ['/'] } = options;
- const apis = createMockApiRegistry();
const app = new PrivateAppImpl({
- apis,
+ apis: [],
components: {
NotFoundErrorPage,
BootErrorPage,
@@ -80,6 +79,7 @@ export function wrapInTestApp(
variant: 'light',
},
],
+ defaultApis: mockApis,
});
let Wrapper: ComponentType;
diff --git a/packages/test-utils/src/testUtils/mockApiRegistry.ts b/packages/test-utils/src/testUtils/mockApis.ts
similarity index 70%
rename from packages/test-utils/src/testUtils/mockApiRegistry.ts
rename to packages/test-utils/src/testUtils/mockApis.ts
index 15733ead88..e05a8e6cac 100644
--- a/packages/test-utils/src/testUtils/mockApiRegistry.ts
+++ b/packages/test-utils/src/testUtils/mockApis.ts
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-import { ApiTestRegistry } from '@backstage/core-api';
+import {
+ storageApiRef,
+ errorApiRef,
+ createApiFactory,
+} from '@backstage/core-api';
import { MockErrorApi, MockStorageApi } from './apis';
-export function createMockApiRegistry(): ApiTestRegistry {
- const registry = new ApiTestRegistry();
-
- registry.register(MockErrorApi.factory);
- registry.register(MockStorageApi.factory);
-
- return registry;
-}
+export const mockApis = [
+ createApiFactory(errorApiRef, new MockErrorApi()),
+ createApiFactory(storageApiRef, MockStorageApi.create()),
+];
diff --git a/packages/theme/package.json b/packages/theme/package.json
index e946482f32..436455633b 100644
--- a/packages/theme/package.json
+++ b/packages/theme/package.json
@@ -28,7 +28,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
- "@material-ui/core": "^4.9.1"
+ "@material-ui/core": "^4.11.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21"
diff --git a/packages/theme/src/themes.ts b/packages/theme/src/themes.ts
index eddd6a4ede..73d6c13859 100644
--- a/packages/theme/src/themes.ts
+++ b/packages/theme/src/themes.ts
@@ -15,7 +15,7 @@
*/
import { createTheme } from './baseTheme';
-import { blue, yellow } from '@material-ui/core/colors';
+import { yellow } from '@material-ui/core/colors';
export const lightTheme = createTheme({
palette: {
@@ -39,7 +39,7 @@ export const lightTheme = createTheme({
},
},
primary: {
- main: blue[500],
+ main: '#2E77D0',
},
banner: {
info: '#2E77D0',
@@ -95,7 +95,7 @@ export const darkTheme = createTheme({
},
},
primary: {
- main: blue[500],
+ main: '#2E77D0',
},
banner: {
info: '#2E77D0',
diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json
index b22cb6fd63..a913316925 100644
--- a/plugins/api-docs/package.json
+++ b/plugins/api-docs/package.json
@@ -26,7 +26,7 @@
"@backstage/theme": "^0.1.1-alpha.21",
"@kyma-project/asyncapi-react": "^0.11.0",
"@material-icons/font": "^1.0.2",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json
index e6c3dde7b6..f4080abc42 100644
--- a/plugins/app-backend/package.json
+++ b/plugins/app-backend/package.json
@@ -4,7 +4,7 @@
"main": "src/index.ts",
"types": "src/index.ts",
"license": "Apache-2.0",
- "private": true,
+ "private": false,
"publishConfig": {
"access": "public",
"main": "dist/index.cjs.js",
diff --git a/plugins/catalog-backend/src/ingestion/LocationReaders.ts b/plugins/catalog-backend/src/ingestion/LocationReaders.ts
index bddef6b4de..a86e073af9 100644
--- a/plugins/catalog-backend/src/ingestion/LocationReaders.ts
+++ b/plugins/catalog-backend/src/ingestion/LocationReaders.ts
@@ -77,7 +77,7 @@ export class LocationReaders implements LocationReader {
return [
StaticLocationProcessor.fromConfig(config),
new FileReaderProcessor(),
- new GithubReaderProcessor(),
+ new GithubReaderProcessor(config),
new GithubApiReaderProcessor(config),
new GitlabApiReaderProcessor(config),
new GitlabReaderProcessor(),
diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json
index 5f17fc2a80..d32033d4fc 100644
--- a/plugins/catalog/package.json
+++ b/plugins/catalog/package.json
@@ -29,7 +29,7 @@
"@backstage/plugin-sentry": "^0.1.1-alpha.21",
"@backstage/plugin-techdocs": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"moment": "^2.26.0",
diff --git a/plugins/catalog/src/plugin.ts b/plugins/catalog/src/plugin.ts
index 4bfbf2453b..dfe12fd500 100644
--- a/plugins/catalog/src/plugin.ts
+++ b/plugins/catalog/src/plugin.ts
@@ -14,8 +14,21 @@
* limitations under the License.
*/
-import { createPlugin } from '@backstage/core';
+import {
+ createPlugin,
+ createApiFactory,
+ discoveryApiRef,
+} from '@backstage/core';
+import { catalogApiRef } from './api/types';
+import { CatalogClient } from './api/CatalogClient';
export const plugin = createPlugin({
id: 'catalog',
+ apis: [
+ createApiFactory({
+ api: catalogApiRef,
+ deps: { discoveryApi: discoveryApiRef },
+ factory: ({ discoveryApi }) => new CatalogClient({ discoveryApi }),
+ }),
+ ],
});
diff --git a/plugins/circleci/README.md b/plugins/circleci/README.md
index 2dcc137a47..48e1bee6cc 100644
--- a/plugins/circleci/README.md
+++ b/plugins/circleci/README.md
@@ -4,8 +4,6 @@ Website: [https://circleci.com/](https://circleci.com/)
-
-
## Setup
@@ -35,8 +33,39 @@ export default builder.build() as ApiHolder;
export { plugin as Circleci } from '@backstage/plugin-circleci';
```
-3. Run app with `yarn start` and navigate to `/circleci/settings`
-4. Enter project settings and **project** token, acquired according to [https://circleci.com/docs/2.0/managing-api-tokens/](https://circleci.com/docs/2.0/managing-api-tokens/)
+3. Register the plugin router:
+
+```jsx
+// packages/app/src/components/catalog/EntityPage.tsx
+
+import { Router as CircleCIRouter } from '@backstage/plugin-circleci';
+
+// Then somewhere inside
+ }
+/>;
+```
+
+4. Add proxy config:
+
+```
+// app-config.yaml
+proxy:
+ '/circleci/api':
+ target: https://circleci.com/api/v1.1
+ changeOrigin: true
+ pathRewrite:
+ '^/proxy/circleci/api/': '/'
+ headers:
+ Circle-Token:
+ $secret:
+ env: CIRCLECI_AUTH_TOKEN
+```
+
+5. Get and provide `CIRCLECI_AUTH_TOKEN` as env variable (https://circleci.com/docs/api/#add-an-api-token)
+6. Add `circleci.com/project-slug` annotation to your component-info.yaml file in format // (https://backstage.io/docs/architecture-decisions/adrs-adr002#format)
## Features
@@ -50,3 +79,4 @@ export { plugin as Circleci } from '@backstage/plugin-circleci';
## Limitations
- CircleCI has pretty strict rate limits per token, be careful with opened tabs
+- CircelCI doesn't provide a way to auth by 3rd party (e.g. GitHub) token, nor by calling their OAuth endpoints, which currently stands in the way of better auth integration with Backstage (https://discuss.circleci.com/t/circleci-api-authorization-with-github-token/5356)
diff --git a/plugins/circleci/dev/index.tsx b/plugins/circleci/dev/index.tsx
index ed7dd5de9c..4bf67d5cb2 100644
--- a/plugins/circleci/dev/index.tsx
+++ b/plugins/circleci/dev/index.tsx
@@ -20,9 +20,9 @@ import { circleCIApiRef, CircleCIApi } from '../src/api';
createDevApp()
.registerPlugin(plugin)
- .registerApiFactory({
+ .registerApi({
+ api: circleCIApiRef,
deps: {},
factory: () => new CircleCIApi(),
- implements: circleCIApiRef,
})
.render();
diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json
index b5824d3869..7c8f36d370 100644
--- a/plugins/circleci/package.json
+++ b/plugins/circleci/package.json
@@ -22,8 +22,10 @@
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
+ "@backstage/catalog-model": "^0.1.1-alpha.21",
+ "@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"circleci-api": "^4.0.0",
diff --git a/plugins/circleci/src/api/index.ts b/plugins/circleci/src/api/index.ts
index 64a33e4c13..da4c811812 100644
--- a/plugins/circleci/src/api/index.ts
+++ b/plugins/circleci/src/api/index.ts
@@ -42,8 +42,8 @@ export class CircleCIApi {
this.apiUrl = apiUrl;
}
- async retry(buildNumber: number, options: CircleCIOptions) {
- return postBuildActions(options.token, buildNumber, BuildAction.RETRY, {
+ async retry(buildNumber: number, options: Partial) {
+ return postBuildActions('', buildNumber, BuildAction.RETRY, {
circleHost: this.apiUrl,
...options.vcs,
});
@@ -51,9 +51,9 @@ export class CircleCIApi {
async getBuilds(
{ limit = 10, offset = 0 }: { limit: number; offset: number },
- options: CircleCIOptions,
+ options: Partial,
) {
- return getBuildSummaries(options.token, {
+ return getBuildSummaries('', {
options: {
limit,
offset,
@@ -64,12 +64,12 @@ export class CircleCIApi {
});
}
- async getUser(options: CircleCIOptions) {
- return getMe(options.token, { circleHost: this.apiUrl, ...options });
+ async getUser(options: Partial) {
+ return getMe('', { circleHost: this.apiUrl, ...options });
}
- async getBuild(buildNumber: number, options: CircleCIOptions) {
- return getFullBuild(options.token, buildNumber, {
+ async getBuild(buildNumber: number, options: Partial) {
+ return getFullBuild('', buildNumber, {
circleHost: this.apiUrl,
...options.vcs,
});
diff --git a/plugins/circleci/src/assets/screenshot-1.png b/plugins/circleci/src/assets/screenshot-1.png
index 2e3f1f420b..db99230a65 100644
Binary files a/plugins/circleci/src/assets/screenshot-1.png and b/plugins/circleci/src/assets/screenshot-1.png differ
diff --git a/plugins/circleci/src/assets/screenshot-2.png b/plugins/circleci/src/assets/screenshot-2.png
index 4e97cbcf8e..4f9ddcaec6 100644
Binary files a/plugins/circleci/src/assets/screenshot-2.png and b/plugins/circleci/src/assets/screenshot-2.png differ
diff --git a/plugins/circleci/src/assets/screenshot-3.png b/plugins/circleci/src/assets/screenshot-3.png
deleted file mode 100644
index ac20d58246..0000000000
Binary files a/plugins/circleci/src/assets/screenshot-3.png and /dev/null differ
diff --git a/plugins/circleci/src/assets/screenshot-4.png b/plugins/circleci/src/assets/screenshot-4.png
deleted file mode 100644
index 2d4ab5fe77..0000000000
Binary files a/plugins/circleci/src/assets/screenshot-4.png and /dev/null differ
diff --git a/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx b/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx
similarity index 80%
rename from plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx
rename to plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx
index d0e0c655c3..30994f5ce3 100644
--- a/plugins/circleci/src/pages/BuildWithStepsPage/BuildWithStepsPage.tsx
+++ b/plugins/circleci/src/components/BuildWithStepsPage/BuildWithStepsPage.tsx
@@ -15,20 +15,22 @@
*/
import React, { FC, useEffect } from 'react';
import { useParams } from 'react-router-dom';
-import { Content, InfoCard, Progress } from '@backstage/core';
+import { InfoCard, Progress, Link } from '@backstage/core';
import { BuildWithSteps, BuildStepAction } from '../../api';
-import { Grid, Box, Link, IconButton } from '@material-ui/core';
+import {
+ Grid,
+ Box,
+ IconButton,
+ Breadcrumbs,
+ Typography,
+ Link as MaterialLink,
+} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
-import { PluginHeader } from '../../components/PluginHeader';
import { ActionOutput } from './lib/ActionOutput/ActionOutput';
-import { Layout } from '../../components/Layout';
import LaunchIcon from '@material-ui/icons/Launch';
-import { useSettings } from '../../state/useSettings';
import { useBuildWithSteps } from '../../state/useBuildWithSteps';
-import { AppStateProvider } from '../../state';
-import { Settings } from '../../components/Settings';
-const IconLink = IconButton as typeof Link;
+const IconLink = (IconButton as any) as typeof MaterialLink;
const BuildName: FC<{ build?: BuildWithSteps }> = ({ build }) => (
#{build?.build_num} - {build?.subject}
@@ -94,56 +96,13 @@ const pickClassName = (
return classes.neutral;
};
-const Page = () => (
-
-
-
-
-
-
-
-
-);
-
-const BuildWithStepsView: FC<{}> = () => {
- const { buildId = '' } = useParams();
- const classes = useStyles();
- const [settings] = useSettings();
- const [{ loading, value }, { startPolling, stopPolling }] = useBuildWithSteps(
- parseInt(buildId, 10),
- );
-
- useEffect(() => {
- startPolling();
- return () => stopPolling();
- }, [buildId, settings, startPolling, stopPolling]);
-
- return (
- <>
-
-
-
-
- }
- cardClassName={classes.cardContent}
- >
- {loading ? : }
-
-
-
- >
- );
-};
-
const BuildsList: FC<{ build?: BuildWithSteps }> = ({ build }) => (
{build &&
build.steps &&
build.steps.map(
({ name, actions }: { name: string; actions: BuildStepAction[] }) => (
-
+
),
)}
@@ -167,5 +126,35 @@ const ActionsList: FC<{ actions: BuildStepAction[]; name: string }> = ({
);
};
-export default Page;
-export { BuildWithStepsView as BuildWithSteps };
+export const BuildWithStepsPage = () => {
+ const { buildId = '' } = useParams();
+ const classes = useStyles();
+ const [{ loading, value }, { startPolling, stopPolling }] = useBuildWithSteps(
+ parseInt(buildId, 10),
+ );
+
+ useEffect(() => {
+ startPolling();
+ return () => stopPolling();
+ }, [buildId, startPolling, stopPolling]);
+
+ return (
+ <>
+
+ All builds
+ Build details
+
+
+
+ }
+ cardClassName={classes.cardContent}
+ >
+ {loading ? : }
+
+
+
+ >
+ );
+};
diff --git a/plugins/circleci/src/components/Settings/index.ts b/plugins/circleci/src/components/BuildWithStepsPage/index.ts
similarity index 90%
rename from plugins/circleci/src/components/Settings/index.ts
rename to plugins/circleci/src/components/BuildWithStepsPage/index.ts
index c04ded6dea..c5627bda1c 100644
--- a/plugins/circleci/src/components/Settings/index.ts
+++ b/plugins/circleci/src/components/BuildWithStepsPage/index.ts
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { default as Settings } from './Settings';
+export { BuildWithStepsPage } from './BuildWithStepsPage';
diff --git a/plugins/circleci/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx b/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx
similarity index 85%
rename from plugins/circleci/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx
rename to plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx
index dd526c1b5f..2a059f7e02 100644
--- a/plugins/circleci/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx
+++ b/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx
@@ -13,18 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, { useEffect, useState, FC, Suspense } from 'react';
+
import {
- ExpansionPanel,
- ExpansionPanelSummary,
- Typography,
- ExpansionPanelDetails,
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
LinearProgress,
+ Typography,
} from '@material-ui/core';
-import moment from 'moment';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { makeStyles } from '@material-ui/core/styles';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { BuildStepAction } from 'circleci-api';
+import moment from 'moment';
+import React, { FC, Suspense, useEffect, useState } from 'react';
const LazyLog = React.lazy(() => import('react-lazylog/build/LazyLog'));
moment.relativeTimeThreshold('ss', 0);
@@ -66,11 +67,8 @@ export const ActionOutput: FC<{
)
.humanize();
return (
-
-
+ }
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
@@ -81,8 +79,8 @@ export const ActionOutput: FC<{
{name} ({timeElapsed})
-
-
+
+
{messages.length === 0 ? (
'Nothing here...'
) : (
@@ -92,7 +90,7 @@ export const ActionOutput: FC<{
)}
-
-
+
+
);
};
diff --git a/plugins/circleci/src/pages/BuildWithStepsPage/lib/ActionOutput/index.ts b/plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/index.ts
similarity index 100%
rename from plugins/circleci/src/pages/BuildWithStepsPage/lib/ActionOutput/index.ts
rename to plugins/circleci/src/components/BuildWithStepsPage/lib/ActionOutput/index.ts
diff --git a/plugins/circleci/src/pages/BuildWithStepsPage/index.ts b/plugins/circleci/src/components/BuildsPage/BuildsPage.tsx
similarity index 70%
rename from plugins/circleci/src/pages/BuildWithStepsPage/index.ts
rename to plugins/circleci/src/components/BuildsPage/BuildsPage.tsx
index fddff7088c..d8c38de481 100644
--- a/plugins/circleci/src/pages/BuildWithStepsPage/index.ts
+++ b/plugins/circleci/src/components/BuildsPage/BuildsPage.tsx
@@ -13,7 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export {
- default as DetailedViewPage,
- BuildWithSteps,
-} from './BuildWithStepsPage';
+import React from 'react';
+import { Builds } from './lib/Builds';
+import { Grid } from '@material-ui/core';
+
+export const BuildsPage = () => (
+
+
+
+
+
+);
diff --git a/plugins/circleci/src/components/Layout/index.ts b/plugins/circleci/src/components/BuildsPage/index.ts
similarity index 93%
rename from plugins/circleci/src/components/Layout/index.ts
rename to plugins/circleci/src/components/BuildsPage/index.ts
index 236fc98851..f9543ed0a8 100644
--- a/plugins/circleci/src/components/Layout/index.ts
+++ b/plugins/circleci/src/components/BuildsPage/index.ts
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export * from './Layout';
+export { BuildsPage } from './BuildsPage';
diff --git a/plugins/circleci/src/pages/BuildsPage/lib/Builds/Builds.tsx b/plugins/circleci/src/components/BuildsPage/lib/Builds/Builds.tsx
similarity index 100%
rename from plugins/circleci/src/pages/BuildsPage/lib/Builds/Builds.tsx
rename to plugins/circleci/src/components/BuildsPage/lib/Builds/Builds.tsx
diff --git a/plugins/circleci/src/pages/BuildsPage/lib/Builds/index.ts b/plugins/circleci/src/components/BuildsPage/lib/Builds/index.ts
similarity index 100%
rename from plugins/circleci/src/pages/BuildsPage/lib/Builds/index.ts
rename to plugins/circleci/src/components/BuildsPage/lib/Builds/index.ts
diff --git a/plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx b/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx
similarity index 93%
rename from plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx
rename to plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx
index b349dcfcbe..8e693429c2 100644
--- a/plugins/circleci/src/pages/BuildsPage/lib/CITable/CITable.tsx
+++ b/plugins/circleci/src/components/BuildsPage/lib/CITable/CITable.tsx
@@ -17,7 +17,7 @@ import React, { FC } from 'react';
import { Link, Typography, Box, IconButton } from '@material-ui/core';
import RetryIcon from '@material-ui/icons/Replay';
import GitHubIcon from '@material-ui/icons/GitHub';
-import { Link as RouterLink } from 'react-router-dom';
+import { Link as RouterLink, generatePath } from 'react-router-dom';
import {
StatusError,
StatusWarning,
@@ -27,6 +27,7 @@ import {
Table,
TableColumn,
} from '@backstage/core';
+import { circleCIBuildRouteRef } from '../../../../route-refs';
export type CITableBuildInfo = {
id: string;
@@ -80,7 +81,10 @@ const generatedColumns: TableColumn[] = [
field: 'buildName',
highlight: true,
render: (row: Partial) => (
-
+
{row.buildName}
),
diff --git a/plugins/circleci/src/pages/BuildsPage/lib/CITable/index.ts b/plugins/circleci/src/components/BuildsPage/lib/CITable/index.ts
similarity index 100%
rename from plugins/circleci/src/pages/BuildsPage/lib/CITable/index.ts
rename to plugins/circleci/src/components/BuildsPage/lib/CITable/index.ts
diff --git a/plugins/circleci/src/components/CircleCIWidget.tsx b/plugins/circleci/src/components/CircleCIWidget.tsx
deleted file mode 100644
index 767eb6d5c7..0000000000
--- a/plugins/circleci/src/components/CircleCIWidget.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React from 'react';
-import { Route, MemoryRouter, Routes } from 'react-router';
-import { Builds } from '../pages/BuildsPage';
-import { BuildWithSteps } from '../pages/BuildWithStepsPage';
-import { AppStateProvider } from '../state';
-import { Settings } from './Settings';
-
-// TODO: allow pass in settings as props
-// When some shared settings workflow
-// will be established
-export const CircleCIWidget = () => (
-
-
- <>
-
- } />
- } />
-
-
- >
-
-
-);
diff --git a/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx b/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx
deleted file mode 100644
index 9f94cff480..0000000000
--- a/plugins/circleci/src/components/PluginHeader/PluginHeader.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React, { FC } from 'react';
-import { Link as RouterLink, useLocation } from 'react-router-dom';
-import { ContentHeader, SupportButton } from '@backstage/core';
-import { Button, IconButton, Box, Typography } from '@material-ui/core';
-import ArrowBack from '@material-ui/icons/ArrowBack';
-import SettingsIcon from '@material-ui/icons/Settings';
-import { useSettings } from '../../state';
-
-export type Props = { title?: string };
-export const PluginHeader: FC = ({ title = 'CircleCI' }) => {
- const [, { showSettings }] = useSettings();
- const location = useLocation();
- const notRoot = !location.pathname.match(/\/circleci\/?$/);
- const isSettingsPage = location.pathname.match(/\/circleci\/settings\/?/);
- return (
- (
-
- {notRoot && (
-
-
-
- )}
- {title}
-
- )}
- >
- {!isSettingsPage && (
- }>
- Settings
-
- )}
-
- This plugin allows you to view and interact with your builds within the
- Circle CI environment.
-
-
- );
-};
diff --git a/plugins/circleci/src/components/Router.tsx b/plugins/circleci/src/components/Router.tsx
new file mode 100644
index 0000000000..282eac9111
--- /dev/null
+++ b/plugins/circleci/src/components/Router.tsx
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Routes, Route } from 'react-router';
+import { circleCIRouteRef, circleCIBuildRouteRef } from '../route-refs';
+import { BuildWithStepsPage } from './BuildWithStepsPage/';
+import { BuildsPage } from './BuildsPage';
+import { CIRCLECI_ANNOTATION } from '../constants';
+import { Entity } from '@backstage/catalog-model';
+import { WarningPanel } from '@backstage/core';
+
+export const isPluginApplicableToEntity = (entity: Entity) =>
+ Boolean(entity.metadata.annotations?.[CIRCLECI_ANNOTATION]) &&
+ entity.metadata.annotations?.[CIRCLECI_ANNOTATION] !== '';
+
+export const Router = ({ entity }: { entity: Entity }) =>
+ !isPluginApplicableToEntity(entity) ? (
+
+ {CIRCLECI_ANNOTATION} annotation is missing on the entity.
+
+ ) : (
+
+ } />
+ }
+ />
+
+ );
diff --git a/plugins/circleci/src/components/Settings/Settings.tsx b/plugins/circleci/src/components/Settings/Settings.tsx
deleted file mode 100644
index b3b62ce930..0000000000
--- a/plugins/circleci/src/components/Settings/Settings.tsx
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React, { useState, useEffect } from 'react';
-import {
- Button,
- TextField,
- List,
- ListItem,
- Snackbar,
- Box,
- Dialog,
- DialogTitle,
-} from '@material-ui/core';
-import { Alert } from '@material-ui/lab';
-import { useSettings } from '../../state';
-
-const Settings = () => {
- const [
- {
- repo: repoFromStore,
- owner: ownerFromStore,
- token: tokenFromStore,
- showSettings,
- },
- { saveSettings, hideSettings },
- ] = useSettings();
-
- const [token, setToken] = useState(() => tokenFromStore);
- const [owner, setOwner] = useState(() => ownerFromStore);
- const [repo, setRepo] = useState(() => repoFromStore);
-
- useEffect(() => {
- if (tokenFromStore !== token) {
- setToken(token);
- }
- if (ownerFromStore !== owner) {
- setOwner(owner);
- }
- if (repoFromStore !== repo) {
- setRepo(repo);
- }
- }, [ownerFromStore, repoFromStore, tokenFromStore, token, owner, repo]);
-
- const [saved, setSaved] = useState(false);
-
- return (
- <>
- setSaved(false)}
- >
- Credentials saved.
-
-
-
- Project Credentials
- {/* {authed ? : } */}
-
-
-
-
- setToken(e.target.value)}
- />
-
-
- setOwner(e.target.value)}
- />
-
-
- setRepo(e.target.value)}
- />
-
-
-
- {
- setSaved(true);
- saveSettings({ repo, owner, token });
- hideSettings();
- }}
- >
- Save credentials
-
-
-
-
-
-
- >
- );
-};
-
-export default Settings;
diff --git a/plugins/circleci/src/pages/BuildsPage/index.ts b/plugins/circleci/src/constants.ts
similarity index 90%
rename from plugins/circleci/src/pages/BuildsPage/index.ts
rename to plugins/circleci/src/constants.ts
index 72b46d6bc9..8c96db93e2 100644
--- a/plugins/circleci/src/pages/BuildsPage/index.ts
+++ b/plugins/circleci/src/constants.ts
@@ -13,4 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export { default as BuildsPage, Builds } from './BuildsPage';
+
+export const CIRCLECI_ANNOTATION = 'circleci.com/project-slug';
diff --git a/plugins/circleci/src/index.ts b/plugins/circleci/src/index.ts
index e2e6c4fa69..c6cb140208 100644
--- a/plugins/circleci/src/index.ts
+++ b/plugins/circleci/src/index.ts
@@ -17,4 +17,5 @@
export { plugin } from './plugin';
export * from './api';
export * from './route-refs';
-export { CircleCIWidget } from './components/CircleCIWidget';
+export { Router, isPluginApplicableToEntity } from './components/Router';
+export { CIRCLECI_ANNOTATION } from './constants';
diff --git a/plugins/circleci/src/pages/BuildsPage/BuildsPage.tsx b/plugins/circleci/src/pages/BuildsPage/BuildsPage.tsx
deleted file mode 100644
index 6e7fc44249..0000000000
--- a/plugins/circleci/src/pages/BuildsPage/BuildsPage.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React, { FC } from 'react';
-import { Content } from '@backstage/core';
-import { Grid } from '@material-ui/core';
-import { Builds as BuildsComp } from './lib/Builds';
-import { Layout } from '../../components/Layout';
-import { PluginHeader } from '../../components/PluginHeader';
-import { AppStateProvider } from '../../state/AppState';
-import { Settings } from '../../components/Settings';
-
-const BuildsPage: FC<{}> = () => (
-
-
-
-
-
-
-
-
-);
-
-const Builds = () => (
- <>
-
-
-
-
-
-
- >
-);
-
-export default BuildsPage;
-export { Builds };
diff --git a/plugins/circleci/src/plugin.ts b/plugins/circleci/src/plugin.ts
index 47ecccef4f..f966caa383 100644
--- a/plugins/circleci/src/plugin.ts
+++ b/plugins/circleci/src/plugin.ts
@@ -13,15 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { createPlugin } from '@backstage/core';
-import { circleCIRouteRef, circleCIBuildRouteRef } from './route-refs';
-import BuildsPage from './pages/BuildsPage/BuildsPage';
-import BuildWithStepsPage from './pages/BuildWithStepsPage/BuildWithStepsPage';
+
+import { createPlugin, createApiFactory, configApiRef } from '@backstage/core';
+import { circleCIApiRef, CircleCIApi } from './api';
export const plugin = createPlugin({
id: 'circleci',
- register({ router }) {
- router.addRoute(circleCIRouteRef, BuildsPage);
- router.addRoute(circleCIBuildRouteRef, BuildWithStepsPage);
- },
+ apis: [
+ createApiFactory({
+ api: circleCIApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) =>
+ new CircleCIApi(
+ `${configApi.getString('backend.baseUrl')}/proxy/circleci/api`,
+ ),
+ }),
+ ],
});
diff --git a/plugins/circleci/src/route-refs.tsx b/plugins/circleci/src/route-refs.tsx
index 581f035c1d..d6bad363ed 100644
--- a/plugins/circleci/src/route-refs.tsx
+++ b/plugins/circleci/src/route-refs.tsx
@@ -33,11 +33,11 @@ const CircleCIIcon: FC = props => (
export const circleCIRouteRef = createRouteRef({
icon: CircleCIIcon,
- path: '/circleci',
+ path: '',
title: 'CircleCI | All builds',
});
export const circleCIBuildRouteRef = createRouteRef({
- path: '/circleci/build/:buildId',
+ path: ':buildId',
title: 'CircleCI | Build info',
});
diff --git a/plugins/circleci/src/state/AppState.tsx b/plugins/circleci/src/state/AppState.tsx
deleted file mode 100644
index 2ee00362ee..0000000000
--- a/plugins/circleci/src/state/AppState.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import React, { FC, useReducer, Dispatch, Reducer } from 'react';
-import { circleCIApiRef } from '../api';
-import type { State, Action, SettingsState } from './types';
-
-export type { SettingsState };
-
-export const AppContext = React.createContext<[State, Dispatch]>(
- [] as any,
-);
-export const STORAGE_KEY = `${circleCIApiRef.id}.settings`;
-
-const initialState: State = {
- owner: '',
- repo: '',
- token: '',
- showSettings: false,
-};
-
-const reducer: Reducer = (state, action) => {
- switch (action.type) {
- case 'setCredentials':
- return {
- ...state,
- ...action.payload,
- };
- case 'showSettings':
- return { ...state, showSettings: true };
- case 'hideSettings':
- return { ...state, showSettings: false };
- default:
- return state;
- }
-};
-
-export const AppStateProvider: FC = ({ children }) => {
- const [state, dispatch] = useReducer(reducer, initialState);
- return (
-
- <>{children}>
-
- );
-};
diff --git a/plugins/circleci/src/state/index.ts b/plugins/circleci/src/state/index.ts
index 2321103eb4..d21a380c2a 100644
--- a/plugins/circleci/src/state/index.ts
+++ b/plugins/circleci/src/state/index.ts
@@ -13,7 +13,5 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export * from './AppState';
-export * from './useSettings';
export * from './useBuilds';
export * from './useBuildWithSteps';
diff --git a/plugins/circleci/src/state/useBuildWithSteps.ts b/plugins/circleci/src/state/useBuildWithSteps.ts
index 8fa7fa896d..c169b8fe63 100644
--- a/plugins/circleci/src/state/useBuildWithSteps.ts
+++ b/plugins/circleci/src/state/useBuildWithSteps.ts
@@ -14,31 +14,35 @@
* limitations under the License.
*/
import { errorApiRef, useApi } from '@backstage/core';
-import { useCallback } from 'react';
+import { useCallback, useMemo } from 'react';
import { useAsyncRetry } from 'react-use';
-import { circleCIApiRef, GitType } from '../api/index';
+import { circleCIApiRef } from '../api/index';
import { useAsyncPolling } from './useAsyncPolling';
-import { useSettings } from './useSettings';
+import { useProjectSlugFromEntity, mapVcsType } from './useBuilds';
const INTERVAL_AMOUNT = 1500;
export function useBuildWithSteps(buildId: number) {
- const [{ token, repo, owner }] = useSettings();
+ const { vcs, repo, owner } = useProjectSlugFromEntity();
const api = useApi(circleCIApiRef);
const errorApi = useApi(errorApiRef);
+ const vcsOption = useMemo(
+ () => ({
+ owner: owner,
+ repo: repo,
+ type: mapVcsType(vcs),
+ }),
+ [owner, repo, vcs],
+ );
+
const getBuildWithSteps = useCallback(async () => {
- if (owner === '' || repo === '' || token === '') {
+ if (owner === '' || repo === '' || vcs === '') {
return Promise.reject('No credentials provided');
}
try {
const options = {
- token: token,
- vcs: {
- owner: owner,
- repo: repo,
- type: GitType.GITHUB,
- },
+ vcs: vcsOption,
};
const build = await api.getBuild(buildId, options);
return Promise.resolve(build);
@@ -46,17 +50,12 @@ export function useBuildWithSteps(buildId: number) {
errorApi.post(e);
return Promise.reject(e);
}
- }, [token, owner, repo, buildId, api, errorApi]);
+ }, [vcsOption, buildId, api, errorApi]); // eslint-disable-line react-hooks/exhaustive-deps
const restartBuild = async () => {
try {
await api.retry(buildId, {
- token: token,
- vcs: {
- owner: owner,
- repo: repo,
- type: GitType.GITHUB,
- },
+ vcs: vcsOption,
});
} catch (e) {
errorApi.post(e);
diff --git a/plugins/circleci/src/state/useBuilds.ts b/plugins/circleci/src/state/useBuilds.ts
index 282ef68f12..2a4aed7fd6 100644
--- a/plugins/circleci/src/state/useBuilds.ts
+++ b/plugins/circleci/src/state/useBuilds.ts
@@ -18,8 +18,9 @@ import { BuildSummary, GitType } from 'circleci-api';
import { useCallback, useEffect, useState } from 'react';
import { useAsyncRetry } from 'react-use';
import { circleCIApiRef } from '../api/index';
-import type { CITableBuildInfo } from '../pages/BuildsPage/lib/CITable';
-import { useSettings } from './useSettings';
+import type { CITableBuildInfo } from '../components/BuildsPage/lib/CITable';
+import { useEntity } from '@backstage/plugin-catalog';
+import { CIRCLECI_ANNOTATION } from '../constants';
const makeReadableStatus = (status: string | undefined) => {
if (!status) return '';
@@ -68,8 +69,25 @@ export const transform = (
});
};
+export const useProjectSlugFromEntity = () => {
+ const { entity } = useEntity();
+ const [vcs, owner, repo] = (
+ entity.metadata.annotations?.[CIRCLECI_ANNOTATION] ?? ''
+ ).split('/');
+ return { vcs, owner, repo };
+};
+
+export function mapVcsType(vcs: string): GitType {
+ switch (vcs) {
+ case 'github':
+ return GitType.GITHUB;
+ default:
+ return GitType.BITBUCKET;
+ }
+}
+
export function useBuilds() {
- const [{ repo, owner, token }] = useSettings();
+ const { repo, owner, vcs } = useProjectSlugFromEntity();
const api = useApi(circleCIApiRef);
const errorApi = useApi(errorApiRef);
@@ -79,7 +97,7 @@ export function useBuilds() {
const getBuilds = useCallback(
async ({ limit, offset }: { limit: number; offset: number }) => {
- if (owner === '' || repo === '' || token === '') {
+ if (owner === '' || repo === '' || vcs === '') {
return Promise.reject('No credentials provided');
}
@@ -87,11 +105,10 @@ export function useBuilds() {
return await api.getBuilds(
{ limit, offset },
{
- token: token,
vcs: {
owner: owner,
repo: repo,
- type: GitType.GITHUB,
+ type: mapVcsType(vcs),
},
},
);
@@ -100,13 +117,12 @@ export function useBuilds() {
return Promise.reject(e);
}
},
- [repo, token, owner, api, errorApi],
+ [repo, owner, vcs, api, errorApi],
);
const restartBuild = async (buildId: number) => {
try {
await api.retry(buildId, {
- token: token,
vcs: {
owner: owner,
repo: repo,
diff --git a/plugins/circleci/src/state/useSettings.ts b/plugins/circleci/src/state/useSettings.ts
deleted file mode 100644
index 3cc58a65bd..0000000000
--- a/plugins/circleci/src/state/useSettings.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-import { errorApiRef, useApi } from '@backstage/core';
-import { useContext, useEffect } from 'react';
-import { AppContext, STORAGE_KEY } from './AppState';
-import { Settings } from './types';
-
-export function useSettings() {
- const [settings, dispatch] = useContext(AppContext);
-
- const errorApi = useApi(errorApiRef);
-
- useEffect(() => {
- const rehydrate = () => {
- try {
- const stateFromStorage = JSON.parse(
- sessionStorage.getItem(STORAGE_KEY)!,
- );
- if (
- stateFromStorage &&
- Object.keys(stateFromStorage).some(
- k => (settings as any)[k] !== stateFromStorage[k],
- )
- )
- dispatch({
- type: 'setCredentials',
- payload: stateFromStorage,
- });
- } catch (error) {
- errorApi.post(error);
- }
- };
-
- rehydrate();
- }, [dispatch, errorApi, settings]);
-
- const persist = (state: Settings) => {
- sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
- };
-
- return [
- settings,
- {
- saveSettings: (state: Settings) => {
- persist(state);
- dispatch({
- type: 'setCredentials',
- payload: state,
- });
- },
- showSettings: () => dispatch({ type: 'showSettings' }),
- hideSettings: () => dispatch({ type: 'hideSettings' }),
- },
- ] as const;
-}
diff --git a/plugins/explore/package.json b/plugins/explore/package.json
index 42176c45cf..f302f4080c 100644
--- a/plugins/explore/package.json
+++ b/plugins/explore/package.json
@@ -23,13 +23,14 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"classnames": "^2.2.6",
"react": "^16.13.1",
"react-dom": "^16.13.1",
- "react-use": "^15.3.3"
+ "react-use": "^15.3.3",
+ "react-router": "6.0.0-beta.0"
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
diff --git a/plugins/explore/src/components/ExplorePluginPage.tsx b/plugins/explore/src/components/ExplorePluginPage.tsx
index 3e073ad322..4365157f62 100644
--- a/plugins/explore/src/components/ExplorePluginPage.tsx
+++ b/plugins/explore/src/components/ExplorePluginPage.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { FC } from 'react';
+import React from 'react';
import { makeStyles, Typography } from '@material-ui/core';
import {
Content,
@@ -107,7 +107,7 @@ const toolsCards = [
},
];
-const ExplorePluginPage: FC<{}> = () => {
+export const ExplorePluginPage = () => {
const classes = useStyles();
return (
@@ -130,5 +130,3 @@ const ExplorePluginPage: FC<{}> = () => {
);
};
-
-export default ExplorePluginPage;
diff --git a/plugins/explore/src/components/Router.tsx b/plugins/explore/src/components/Router.tsx
new file mode 100644
index 0000000000..becb2522d0
--- /dev/null
+++ b/plugins/explore/src/components/Router.tsx
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Route, Routes } from 'react-router';
+import { ExplorePluginPage } from './ExplorePluginPage';
+import { rootRouteRef } from '../plugin';
+
+export const Router = () => (
+
+ } />
+
+);
diff --git a/plugins/explore/src/index.ts b/plugins/explore/src/index.ts
index 3a0a0fe2d3..ff7857cacd 100644
--- a/plugins/explore/src/index.ts
+++ b/plugins/explore/src/index.ts
@@ -15,3 +15,4 @@
*/
export { plugin } from './plugin';
+export { Router } from './components/Router';
diff --git a/plugins/explore/src/plugin.ts b/plugins/explore/src/plugin.ts
index 66e48a9b15..75ea892242 100644
--- a/plugins/explore/src/plugin.ts
+++ b/plugins/explore/src/plugin.ts
@@ -14,12 +14,9 @@
* limitations under the License.
*/
-import { createPlugin } from '@backstage/core';
-import ExplorePluginPage from './components/ExplorePluginPage';
+import { createPlugin, createRouteRef } from '@backstage/core';
+export const rootRouteRef = createRouteRef({ path: '', title: 'Explore' });
export const plugin = createPlugin({
id: 'explore',
- register({ router }) {
- router.registerRoute('/explore', ExplorePluginPage);
- },
});
diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json
index 55ed362876..cdc4bb507d 100644
--- a/plugins/gcp-projects/package.json
+++ b/plugins/gcp-projects/package.json
@@ -23,7 +23,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
diff --git a/plugins/gcp-projects/src/plugin.ts b/plugins/gcp-projects/src/plugin.ts
index 29c7d74434..41aacde597 100644
--- a/plugins/gcp-projects/src/plugin.ts
+++ b/plugins/gcp-projects/src/plugin.ts
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-import { createPlugin, createRouteRef } from '@backstage/core';
+import {
+ createPlugin,
+ createRouteRef,
+ createApiFactory,
+} from '@backstage/core';
import { ProjectListPage } from './components/ProjectListPage';
import { ProjectDetailsPage } from './components/ProjectDetailsPage';
import { NewProjectPage } from './components/NewProjectPage';
+import { GCPApiRef, GCPClient } from './api';
export const rootRouteRef = createRouteRef({
path: '/gcp-projects',
@@ -34,6 +39,7 @@ export const NewProjectRouteRef = createRouteRef({
export const plugin = createPlugin({
id: 'gcp-projects',
+ apis: [createApiFactory(GCPApiRef, new GCPClient())],
register({ router }) {
router.addRoute(rootRouteRef, ProjectListPage);
router.addRoute(ProjectRouteRef, ProjectDetailsPage);
diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json
index e4f09976eb..e55a195f6e 100644
--- a/plugins/github-actions/package.json
+++ b/plugins/github-actions/package.json
@@ -27,11 +27,11 @@
"@backstage/core-api": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@octokit/rest": "^18.0.0",
- "@octokit/types": "^5.0.1",
+ "@octokit/types": "^5.4.1",
"moment": "^2.27.0",
"react": "^16.13.1",
"react-dom": "^16.13.1",
diff --git a/plugins/github-actions/src/components/Router.tsx b/plugins/github-actions/src/components/Router.tsx
index d0745f67d4..7d340fb8b8 100644
--- a/plugins/github-actions/src/components/Router.tsx
+++ b/plugins/github-actions/src/components/Router.tsx
@@ -22,7 +22,7 @@ import { WorkflowRunsTable } from './WorkflowRunsTable';
import { GITHUB_ACTIONS_ANNOTATION } from './useProjectName';
import { WarningPanel } from '@backstage/core';
-const isPluginApplicableToEntity = (entity: Entity) =>
+export const isPluginApplicableToEntity = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION]) &&
entity.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION] !== '';
@@ -30,8 +30,8 @@ export const Router = ({ entity }: { entity: Entity }) =>
// TODO(shmidt-i): move warning to a separate standardized component
!isPluginApplicableToEntity(entity) ? (
- `entity.metadata.annotations['
- {GITHUB_ACTIONS_ANNOTATION}']` key is missing on the entity.{' '}
+ {GITHUB_ACTIONS_ANNOTATION} annotation is missing on the
+ entity.
) : (
diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx
index d8e3b02524..a8ceb717da 100644
--- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx
+++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx
@@ -13,37 +13,38 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React from 'react';
-import { useWorkflowRunsDetails } from './useWorkflowRunsDetails';
-import { useWorkflowRunJobs } from './useWorkflowRunJobs';
-import { useProjectName } from '../useProjectName';
-import {
- makeStyles,
- Box,
- TableRow,
- TableCell,
- ListItemText,
- ExpansionPanel,
- ExpansionPanelSummary,
- Typography,
- ExpansionPanelDetails,
- TableContainer,
- Table,
- Paper,
- TableBody,
- LinearProgress,
- CircularProgress,
- Theme,
- Breadcrumbs,
- Link as MaterialLink,
-} from '@material-ui/core';
-import { Jobs, Job, Step } from '../../api';
-import moment from 'moment';
-import { WorkflowRunStatus } from '../WorkflowRunStatus';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
-import ExternalLinkIcon from '@material-ui/icons/Launch';
+
import { Entity } from '@backstage/catalog-model';
import { Link } from '@backstage/core';
+import {
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
+ Box,
+ Breadcrumbs,
+ CircularProgress,
+ LinearProgress,
+ Link as MaterialLink,
+ ListItemText,
+ makeStyles,
+ Paper,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableRow,
+ Theme,
+ Typography,
+} from '@material-ui/core';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import ExternalLinkIcon from '@material-ui/icons/Launch';
+import moment from 'moment';
+import React from 'react';
+import { Job, Jobs, Step } from '../../api';
+import { useProjectName } from '../useProjectName';
+import { WorkflowRunStatus } from '../WorkflowRunStatus';
+import { useWorkflowRunJobs } from './useWorkflowRunJobs';
+import { useWorkflowRunsDetails } from './useWorkflowRunsDetails';
const useStyles = makeStyles(theme => ({
root: {
@@ -113,11 +114,8 @@ const StepView = ({ step }: { step: Step }) => {
const JobListItem = ({ job, className }: { job: Job; className: string }) => {
const classes = useStyles();
return (
-
-
+ }
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
@@ -128,8 +126,8 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => {
{job.name} ({getElapsedTime(job.started_at, job.completed_at)})
-
-
+
+
{job.steps.map((step: Step) => (
@@ -137,8 +135,8 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => {
))}
-
-
+
+
);
};
diff --git a/plugins/github-actions/src/index.ts b/plugins/github-actions/src/index.ts
index 17c1fa2dd7..24fe6fc90d 100644
--- a/plugins/github-actions/src/index.ts
+++ b/plugins/github-actions/src/index.ts
@@ -16,6 +16,6 @@
export { plugin } from './plugin';
export * from './api';
-export { Router } from './components/Router';
+export { Router, isPluginApplicableToEntity } from './components/Router';
export * from './components/Cards';
export { GITHUB_ACTIONS_ANNOTATION } from './components/useProjectName';
diff --git a/plugins/github-actions/src/plugin.ts b/plugins/github-actions/src/plugin.ts
index 7e08229d6d..9e3c965d46 100644
--- a/plugins/github-actions/src/plugin.ts
+++ b/plugins/github-actions/src/plugin.ts
@@ -14,7 +14,12 @@
* limitations under the License.
*/
-import { createPlugin, createRouteRef } from '@backstage/core';
+import {
+ createPlugin,
+ createRouteRef,
+ createApiFactory,
+} from '@backstage/core';
+import { githubActionsApiRef, GithubActionsClient } from './api';
// TODO(freben): This is just a demo route for now
export const rootRouteRef = createRouteRef({
@@ -29,4 +34,5 @@ export const buildRouteRef = createRouteRef({
export const plugin = createPlugin({
id: 'github-actions',
+ apis: [createApiFactory(githubActionsApiRef, new GithubActionsClient())],
});
diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json
index dc32a4df14..62dacb5e91 100644
--- a/plugins/gitops-profiles/package.json
+++ b/plugins/gitops-profiles/package.json
@@ -23,7 +23,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
diff --git a/plugins/gitops-profiles/src/plugin.ts b/plugins/gitops-profiles/src/plugin.ts
index af180aff20..45820644f7 100644
--- a/plugins/gitops-profiles/src/plugin.ts
+++ b/plugins/gitops-profiles/src/plugin.ts
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { createPlugin } from '@backstage/core';
+import { createPlugin, createApiFactory } from '@backstage/core';
import ProfileCatalog from './components/ProfileCatalog';
import ClusterPage from './components/ClusterPage';
import ClusterList from './components/ClusterList';
@@ -23,9 +23,13 @@ import {
gitOpsClusterDetailsRoute,
gitOpsClusterCreateRoute,
} from './routes';
+import { gitOpsApiRef, GitOpsRestApi } from './api';
export const plugin = createPlugin({
id: 'gitops-profiles',
+ apis: [
+ createApiFactory(gitOpsApiRef, new GitOpsRestApi('http://localhost:3008')),
+ ],
register({ router }) {
router.addRoute(gitOpsClusterListRoute, ClusterList);
router.addRoute(gitOpsClusterDetailsRoute, ClusterPage);
diff --git a/plugins/graphiql/dev/index.tsx b/plugins/graphiql/dev/index.tsx
index efcf19a89d..b93995a5dc 100644
--- a/plugins/graphiql/dev/index.tsx
+++ b/plugins/graphiql/dev/index.tsx
@@ -20,8 +20,8 @@ import { plugin, GraphQLEndpoints, graphQlBrowseApiRef } from '../src';
createDevApp()
.registerPlugin(plugin)
- .registerApiFactory({
- implements: graphQlBrowseApiRef,
+ .registerApi({
+ api: graphQlBrowseApiRef,
deps: {
errorApi: errorApiRef,
githubAuthApi: githubAuthApiRef,
diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json
index cc1a0a7204..b4d0b685da 100644
--- a/plugins/graphiql/package.json
+++ b/plugins/graphiql/package.json
@@ -33,7 +33,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"graphiql": "^1.0.0-alpha.10",
diff --git a/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.tsx b/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.tsx
index a16fdf3ee6..7eb91b59d8 100644
--- a/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.tsx
+++ b/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { FC } from 'react';
+import React from 'react';
import {
Content,
Header,
@@ -30,7 +30,7 @@ import { graphQlBrowseApiRef } from '../../lib/api';
import { GraphiQLBrowser } from '../GraphiQLBrowser';
import { Typography } from '@material-ui/core';
-export const GraphiQLPage: FC<{}> = () => {
+export const GraphiQLPage = () => {
const graphQlBrowseApi = useApi(graphQlBrowseApiRef);
const endpoints = useAsync(() => graphQlBrowseApi.getEndpoints());
diff --git a/plugins/graphiql/src/index.ts b/plugins/graphiql/src/index.ts
index e7614aba81..50698cdb4c 100644
--- a/plugins/graphiql/src/index.ts
+++ b/plugins/graphiql/src/index.ts
@@ -15,5 +15,6 @@
*/
export { plugin } from './plugin';
+export { GraphiQLPage as Router } from './components';
export * from './lib/api';
export * from './route-refs';
diff --git a/plugins/graphiql/src/plugin.ts b/plugins/graphiql/src/plugin.ts
index f118faf871..28d27802d0 100644
--- a/plugins/graphiql/src/plugin.ts
+++ b/plugins/graphiql/src/plugin.ts
@@ -14,13 +14,22 @@
* limitations under the License.
*/
-import { createPlugin } from '@backstage/core';
-import { GraphiQLPage } from './components';
-import { graphiQLRouteRef } from './route-refs';
+import { createPlugin, createApiFactory } from '@backstage/core';
+import { graphQlBrowseApiRef, GraphQLEndpoints } from './lib/api';
export const plugin = createPlugin({
id: 'graphiql',
- register({ router }) {
- router.addRoute(graphiQLRouteRef, GraphiQLPage);
- },
+ apis: [
+ // GitLab is used as an example endpoint, but most plug
+ createApiFactory(
+ graphQlBrowseApiRef,
+ GraphQLEndpoints.from([
+ GraphQLEndpoints.create({
+ id: 'gitlab',
+ title: 'GitLab',
+ url: 'https://gitlab.com/api/graphql',
+ }),
+ ]),
+ ),
+ ],
});
diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json
index da1754749f..0d532f8c45 100644
--- a/plugins/jenkins/package.json
+++ b/plugins/jenkins/package.json
@@ -24,7 +24,7 @@
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"jenkins": "^0.28.0",
diff --git a/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx b/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx
index 1143121a98..a9b73f6f0b 100644
--- a/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx
+++ b/plugins/jenkins/src/pages/BuildWithStepsPage/lib/ActionOutput/ActionOutput.tsx
@@ -13,15 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, { useEffect, FC } from 'react';
+
import {
- ExpansionPanel,
- ExpansionPanelSummary,
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
Typography,
- ExpansionPanelDetails,
} from '@material-ui/core';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { makeStyles } from '@material-ui/core/styles';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import React, { FC, useEffect } from 'react';
const useStyles = makeStyles({
expansionPanelDetails: {
@@ -45,11 +46,8 @@ export const ActionOutput: FC<{
useEffect(() => {}, [url]);
return (
-
-
+ }
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
@@ -58,10 +56,10 @@ export const ActionOutput: FC<{
}}
>
{name}
-
-
+
+
Nothing here...
-
-
+
+
);
};
diff --git a/plugins/jenkins/src/plugin.ts b/plugins/jenkins/src/plugin.ts
index 979b0a31cf..4d1af322c4 100644
--- a/plugins/jenkins/src/plugin.ts
+++ b/plugins/jenkins/src/plugin.ts
@@ -14,8 +14,14 @@
* limitations under the License.
*/
-import { createPlugin, createRouteRef } from '@backstage/core';
+import {
+ createPlugin,
+ createRouteRef,
+ createApiFactory,
+ configApiRef,
+} from '@backstage/core';
import { DetailedViewPage } from './pages/BuildWithStepsPage';
+import { jenkinsApiRef, JenkinsApi } from './api';
export const buildRouteRef = createRouteRef({
path: '/jenkins/job',
@@ -24,6 +30,16 @@ export const buildRouteRef = createRouteRef({
export const plugin = createPlugin({
id: 'jenkins',
+ apis: [
+ createApiFactory({
+ api: jenkinsApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) =>
+ new JenkinsApi(
+ `${configApi.getString('backend.baseUrl')}/proxy/jenkins/api`,
+ ),
+ }),
+ ],
register({ router }) {
router.addRoute(buildRouteRef, DetailedViewPage);
},
diff --git a/plugins/lighthouse/dev/index.tsx b/plugins/lighthouse/dev/index.tsx
index 6496fb658a..bf761965f1 100644
--- a/plugins/lighthouse/dev/index.tsx
+++ b/plugins/lighthouse/dev/index.tsx
@@ -20,8 +20,8 @@ import { lighthouseApiRef, LighthouseRestApi } from '../src';
createDevApp()
.registerPlugin(plugin)
- .registerApiFactory({
- implements: lighthouseApiRef,
+ .registerApi({
+ api: lighthouseApiRef,
deps: {},
factory: () => new LighthouseRestApi('http://localhost:3003'),
})
diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json
index 647215f7f8..24a106e53d 100644
--- a/plugins/lighthouse/package.json
+++ b/plugins/lighthouse/package.json
@@ -24,7 +24,7 @@
"@backstage/config": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
diff --git a/packages/dev-utils/src/devApp/apiFactories.test.ts b/plugins/lighthouse/src/Router.tsx
similarity index 50%
rename from packages/dev-utils/src/devApp/apiFactories.test.ts
rename to plugins/lighthouse/src/Router.tsx
index be3ae2cdbe..46843504fd 100644
--- a/packages/dev-utils/src/devApp/apiFactories.test.ts
+++ b/plugins/lighthouse/src/Router.tsx
@@ -14,23 +14,17 @@
* limitations under the License.
*/
-import * as apiFactories from './apiFactories';
-import { ApiTestRegistry, ApiFactory } from '@backstage/core';
+import React from 'react';
+import { Routes, Route } from 'react-router-dom';
+import { rootRouteRef, viewAuditRouteRef, createAuditRouteRef } from './plugin';
+import AuditList from './components/AuditList';
+import AuditView from './components/AuditView';
+import CreateAudit from './components/CreateAudit';
-describe('apiFactories', () => {
- it('should be possible to get an instance of each API', () => {
- const registry = new ApiTestRegistry();
- const factories: ApiFactory[] = Object.values(
- apiFactories,
- );
-
- for (const factory of factories) {
- registry.register(factory);
- }
-
- for (const factory of factories) {
- const api = registry.get(factory.implements);
- expect(api).toBeDefined();
- }
- });
-});
+export const Router = () => (
+
+ } />
+ } />
+ } />
+
+);
diff --git a/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx b/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx
index 039b397083..e67f939ba1 100644
--- a/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx
+++ b/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx
@@ -58,10 +58,7 @@ describe('AuditListTable', () => {
if (!website)
throw new Error('https://anchor.fm must be present in fixture');
expect(link).toBeInTheDocument();
- expect(link).toHaveAttribute(
- 'href',
- `/lighthouse/audit/${website.lastAudit.id}`,
- );
+ expect(link).toHaveAttribute('href', `/audit/${website.lastAudit.id}`);
});
it('renders the dates that are available for a given row', () => {
diff --git a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx
index e52805000a..332dd5e3ca 100644
--- a/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx
+++ b/plugins/lighthouse/src/components/AuditList/AuditListTable.tsx
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import React, { FC, useState } from 'react';
+import React, { FC, useState, useEffect } from 'react';
import { Table, TableColumn, TrendLine, useApi } from '@backstage/core';
import { Website, lighthouseApiRef } from '../../api';
import { useInterval } from 'react-use';
@@ -23,8 +23,9 @@ import {
CATEGORY_LABELS,
buildSparklinesDataForItem,
} from '../../utils';
-import { Link } from '@material-ui/core';
+import { Link, generatePath } from 'react-router-dom';
import AuditStatusIcon from '../AuditStatusIcon';
+import { viewAuditRouteRef } from '../../plugin';
const columns: TableColumn[] = [
{
@@ -55,6 +56,10 @@ export const AuditListTable: FC<{ items: Website[] }> = ({ items }) => {
const [websiteState, setWebsiteState] = useState(items);
const lighthouseApi = useApi(lighthouseApiRef);
+ useEffect(() => {
+ setWebsiteState(items);
+ }, [items]);
+
const runRefresh = (websites: Website[]) => {
websites.forEach(async website => {
const response = await lighthouseApi.getWebsiteForAuditId(
@@ -94,7 +99,11 @@ export const AuditListTable: FC<{ items: Website[] }> = ({ items }) => {
return {
websiteUrl: (
-
+
{website.url}
),
diff --git a/plugins/lighthouse/src/components/AuditList/index.test.tsx b/plugins/lighthouse/src/components/AuditList/index.test.tsx
index 076bb5819a..34fd760801 100644
--- a/plugins/lighthouse/src/components/AuditList/index.test.tsx
+++ b/plugins/lighthouse/src/components/AuditList/index.test.tsx
@@ -63,7 +63,7 @@ describe('AuditList', () => {
expect(element).toBeInTheDocument();
});
- it('renders a link to create a new audit', async () => {
+ it('renders a button to create a new audit', async () => {
const rendered = render(
wrapInTestApp(
@@ -71,12 +71,8 @@ describe('AuditList', () => {
,
),
);
- const element = await rendered.findByText('Create Audit');
- expect(element).toBeInTheDocument();
- expect(element.parentElement).toHaveAttribute(
- 'href',
- '/lighthouse/create-audit',
- );
+ const button = await rendered.findByText('Create Audit');
+ expect(button).toBeInTheDocument();
});
describe('pagination', () => {
@@ -87,7 +83,7 @@ describe('AuditList', () => {
,
- { routeEntries: ['/lighthouse?page=2'] },
+ { routeEntries: ['?page=2'] },
),
);
expect(mockFetch).toHaveBeenLastCalledWith(
@@ -137,13 +133,13 @@ describe('AuditList', () => {
,
- { routeEntries: ['/lighthouse?page=2'] },
+ { routeEntries: ['?page=2'] },
),
);
const element = await rendered.findByLabelText(/Go to page 1/);
fireEvent.click(element);
- expect(useNavigate()).toHaveBeenCalledWith(`/lighthouse?page=1`);
+ expect(useNavigate()).toHaveBeenCalledWith(`?page=1`);
});
});
});
diff --git a/plugins/lighthouse/src/components/AuditList/index.tsx b/plugins/lighthouse/src/components/AuditList/index.tsx
index c83ce9f641..a75bb25a0d 100644
--- a/plugins/lighthouse/src/components/AuditList/index.tsx
+++ b/plugins/lighthouse/src/components/AuditList/index.tsx
@@ -37,6 +37,7 @@ import { useQuery } from '../../utils';
import LighthouseSupportButton from '../SupportButton';
import LighthouseIntro, { LIGHTHOUSE_INTRO_LOCAL_STORAGE } from '../Intro';
import AuditListTable from './AuditListTable';
+import { createAuditRouteRef } from '../../plugin';
export const LIMIT = 10;
@@ -77,7 +78,7 @@ const AuditList: FC<{}> = () => {
page={page}
count={pageCount}
onChange={(_event: Event, newPage: number) => {
- navigate(`/lighthouse?page=${newPage}`);
+ navigate(`?page=${newPage}`);
}}
/>
)}
@@ -111,7 +112,7 @@ const AuditList: FC<{}> = () => {
navigate(createAuditRouteRef.path)}
>
Create Audit
diff --git a/plugins/lighthouse/src/components/AuditView/index.test.tsx b/plugins/lighthouse/src/components/AuditView/index.test.tsx
index b2c15cd8b7..742be67fed 100644
--- a/plugins/lighthouse/src/components/AuditView/index.test.tsx
+++ b/plugins/lighthouse/src/components/AuditView/index.test.tsx
@@ -18,15 +18,17 @@
jest.mock('react-router-dom', () => {
const actual = jest.requireActual('react-router-dom');
+ const mockNavigation = jest.fn();
return {
...actual,
useParams: jest.fn(() => ({})),
+ useNavigate: jest.fn(() => mockNavigation),
};
});
import React from 'react';
import mockFetch from 'jest-fetch-mock';
-import { render } from '@testing-library/react';
+import { render, fireEvent } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
import { ApiRegistry, ApiProvider } from '@backstage/core';
@@ -34,11 +36,13 @@ import AuditView from '.';
import { lighthouseApiRef, LighthouseRestApi, Audit, Website } from '../../api';
import { formatTime } from '../../utils';
import * as data from '../../__fixtures__/website-response.json';
+import { act } from 'react-dom/test-utils';
const { useParams }: { useParams: jest.Mock } = jest.requireMock(
'react-router-dom',
);
const websiteResponse = data as Website;
+const { useNavigate } = jest.requireMock('react-router-dom');
describe('AuditView', () => {
let apis: ApiRegistry;
@@ -70,7 +74,7 @@ describe('AuditView', () => {
expect(iframe).toHaveAttribute('src', `https://lighthouse/v1/audits/${id}`);
});
- it('renders a link to create a new audit for this website', async () => {
+ it('renders a button to click to create a new audit for this website', async () => {
const rendered = render(
wrapInTestApp(
@@ -79,13 +83,15 @@ describe('AuditView', () => {
),
);
- const button = await rendered.findByText('Create Audit');
+ const button = await rendered.findByText('Create New Audit');
expect(button).toBeInTheDocument();
- expect(button.parentElement).toHaveAttribute(
- 'href',
- `/lighthouse/create-audit?url=${encodeURIComponent(
- 'https://spotify.com',
- )}`,
+
+ act(() => {
+ fireEvent.click(button);
+ });
+
+ expect(useNavigate()).toHaveBeenCalledWith(
+ `../../create-audit?url=${encodeURIComponent('https://spotify.com')}`,
);
});
@@ -151,7 +157,7 @@ describe('AuditView', () => {
expect(
rendered.getByText(formatTime(a.timeCreated)).parentElement
?.parentElement,
- ).toHaveAttribute('href', `/lighthouse/audit/${a.id}`);
+ ).toHaveAttribute('href', `/audit/${a.id}`);
});
});
});
diff --git a/plugins/lighthouse/src/components/AuditView/index.tsx b/plugins/lighthouse/src/components/AuditView/index.tsx
index 12606482bf..d0e743a77f 100644
--- a/plugins/lighthouse/src/components/AuditView/index.tsx
+++ b/plugins/lighthouse/src/components/AuditView/index.tsx
@@ -14,7 +14,13 @@
* limitations under the License.
*/
import React, { useState, useEffect, ReactNode, FC } from 'react';
-import { Link, useParams } from 'react-router-dom';
+import {
+ Link,
+ useParams,
+ useNavigate,
+ generatePath,
+ resolvePath,
+} from 'react-router-dom';
import { useAsync } from 'react-use';
import {
makeStyles,
@@ -42,6 +48,7 @@ import { lighthouseApiRef, Website, Audit } from '../../api';
import AuditStatusIcon from '../AuditStatusIcon';
import LighthouseSupportButton from '../SupportButton';
import { formatTime } from '../../utils';
+import { viewAuditRouteRef, createAuditRouteRef } from '../../plugin';
const useStyles = makeStyles({
contentGrid: {
@@ -75,7 +82,12 @@ const AuditLinkList: FC = ({
button
component={Link}
replace
- to={`/lighthouse/audit/${audit.id}`}
+ to={resolvePath(
+ generatePath(viewAuditRouteRef.path, {
+ id: audit.id,
+ }),
+ '../../',
+ )}
>
@@ -116,6 +128,7 @@ const ConnectedAuditView: FC<{}> = () => {
const lighthouseApi = useApi(lighthouseApiRef);
const params = useParams() as { id: string };
const classes = useStyles();
+ const navigate = useNavigate();
const { loading, error, value: nextValue } = useAsync(
async () => await lighthouseApi.getWebsiteForAuditId(params.id),
@@ -154,7 +167,7 @@ const ConnectedAuditView: FC<{}> = () => {
);
}
- let createAuditButtonUrl = '/lighthouse/create-audit';
+ let createAuditButtonUrl = createAuditRouteRef.path;
if (value?.url) {
createAuditButtonUrl += `?url=${encodeURIComponent(value.url)}`;
}
@@ -176,9 +189,9 @@ const ConnectedAuditView: FC<{}> = () => {
navigate(`../../${createAuditButtonUrl}`)}
>
- Create Audit
+ Create New Audit
diff --git a/plugins/lighthouse/src/components/CreateAudit/index.test.tsx b/plugins/lighthouse/src/components/CreateAudit/index.test.tsx
index aaf376923f..b2348bf5ff 100644
--- a/plugins/lighthouse/src/components/CreateAudit/index.test.tsx
+++ b/plugins/lighthouse/src/components/CreateAudit/index.test.tsx
@@ -78,9 +78,7 @@ describe('CreateAudit', () => {
,
{
- routeEntries: [
- `/lighthouse/create-audit?url=${encodeURIComponent(url)}`,
- ],
+ routeEntries: [`/create-audit?url=${encodeURIComponent(url)}`],
},
),
);
@@ -137,7 +135,7 @@ describe('CreateAudit', () => {
await wait(() => expect(rendered.getByLabelText(/URL/)).toBeEnabled());
- expect(useNavigate()).toHaveBeenCalledWith('/lighthouse');
+ expect(useNavigate()).toHaveBeenCalledWith('..');
});
});
diff --git a/plugins/lighthouse/src/components/CreateAudit/index.tsx b/plugins/lighthouse/src/components/CreateAudit/index.tsx
index a699e148c8..36bd97485a 100644
--- a/plugins/lighthouse/src/components/CreateAudit/index.tsx
+++ b/plugins/lighthouse/src/components/CreateAudit/index.tsx
@@ -78,7 +78,7 @@ const CreateAudit: FC<{}> = () => {
},
},
});
- navigate('/lighthouse');
+ navigate('..');
} catch (err) {
errorApi.post(err);
} finally {
@@ -154,7 +154,7 @@ const CreateAudit: FC<{}> = () => {
navigate('..')}
disabled={submitting}
>
Cancel
diff --git a/plugins/lighthouse/src/index.ts b/plugins/lighthouse/src/index.ts
index d67bc6a864..bebdaaf713 100644
--- a/plugins/lighthouse/src/index.ts
+++ b/plugins/lighthouse/src/index.ts
@@ -15,4 +15,5 @@
*/
export { plugin } from './plugin';
+export { Router } from './Router';
export * from './api';
diff --git a/plugins/lighthouse/src/plugin.ts b/plugins/lighthouse/src/plugin.ts
index f8da8d84ed..e9055b4db7 100644
--- a/plugins/lighthouse/src/plugin.ts
+++ b/plugins/lighthouse/src/plugin.ts
@@ -14,16 +14,36 @@
* limitations under the License.
*/
-import { createPlugin } from '@backstage/core';
-import AuditList from './components/AuditList';
-import AuditView from './components/AuditView';
-import CreateAudit from './components/CreateAudit';
+import {
+ createPlugin,
+ createRouteRef,
+ createApiFactory,
+ configApiRef,
+} from '@backstage/core';
+import { lighthouseApiRef, LighthouseRestApi } from './api';
+
+export const rootRouteRef = createRouteRef({
+ path: '',
+ title: 'Lighthouse',
+});
+
+export const viewAuditRouteRef = createRouteRef({
+ path: 'audit/:id',
+ title: 'View Lighthouse Audit',
+});
+
+export const createAuditRouteRef = createRouteRef({
+ path: 'create-audit',
+ title: 'Create Lighthouse Audit',
+});
export const plugin = createPlugin({
id: 'lighthouse',
- register({ router }) {
- router.registerRoute('/lighthouse', AuditList);
- router.registerRoute('/lighthouse/audit/:id', AuditView);
- router.registerRoute('/lighthouse/create-audit', CreateAudit);
- },
+ apis: [
+ createApiFactory({
+ api: lighthouseApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) => LighthouseRestApi.fromConfig(configApi),
+ }),
+ ],
});
diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json
index 84f066edae..9c9d609c04 100644
--- a/plugins/newrelic/package.json
+++ b/plugins/newrelic/package.json
@@ -23,7 +23,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json
index 0064d50aaa..3dda51c3d5 100644
--- a/plugins/register-component/package.json
+++ b/plugins/register-component/package.json
@@ -25,7 +25,7 @@
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx
index 32213f04e4..32fa4a85c0 100644
--- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx
+++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.test.tsx
@@ -16,10 +16,15 @@
import React from 'react';
import { render, cleanup } from '@testing-library/react';
-import RegisterComponentPage from './RegisterComponentPage';
+import { RegisterComponentPage } from './RegisterComponentPage';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
-import { errorApiRef, ApiProvider, ApiRegistry } from '@backstage/core';
+import {
+ errorApiRef,
+ ApiProvider,
+ ApiRegistry,
+ createRouteRef,
+} from '@backstage/core';
import { catalogApiRef } from '@backstage/plugin-catalog';
import { MemoryRouter } from 'react-router-dom';
@@ -45,7 +50,12 @@ const setup = () => ({
])}
>
-
+
,
diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx
index e61f904347..032a7d5257 100644
--- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx
+++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { FC, useState } from 'react';
+import React, { useState } from 'react';
import { Grid, makeStyles } from '@material-ui/core';
import {
InfoCard,
@@ -26,6 +26,7 @@ import {
Header,
SupportButton,
ContentHeader,
+ RouteRef,
} from '@backstage/core';
import RegisterComponentForm from '../RegisterComponentForm';
import { catalogApiRef } from '@backstage/plugin-catalog';
@@ -54,7 +55,11 @@ const FormStates = {
} as const;
type ValuesOf = T extends Record ? V : never;
-const RegisterComponentPage: FC<{}> = () => {
+export const RegisterComponentPage = ({
+ catalogRouteRef,
+}: {
+ catalogRouteRef: RouteRef;
+}) => {
const classes = useStyles();
const catalogApi = useApi(catalogApiRef);
const [formState, setFormState] = useState>(
@@ -130,10 +135,9 @@ const RegisterComponentPage: FC<{}> = () => {
entities={result.data!.entities}
onClose={() => setFormState(FormStates.Idle)}
classes={{ paper: classes.dialogPaper }}
+ catalogRouteRef={catalogRouteRef}
/>
)}
);
};
-
-export default RegisterComponentPage;
diff --git a/plugins/register-component/src/components/RegisterComponentPage/index.ts b/plugins/register-component/src/components/RegisterComponentPage/index.ts
index e0757e0eee..8c325fe8b4 100644
--- a/plugins/register-component/src/components/RegisterComponentPage/index.ts
+++ b/plugins/register-component/src/components/RegisterComponentPage/index.ts
@@ -14,4 +14,4 @@
* limitations under the License.
*/
-export { default } from './RegisterComponentPage';
+export { RegisterComponentPage } from './RegisterComponentPage';
diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx
index 71933a0302..ae262ab866 100644
--- a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx
+++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx
@@ -20,6 +20,7 @@ import { cleanup, render } from '@testing-library/react';
import React, { ComponentProps } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { RegisterComponentResultDialog } from './RegisterComponentResultDialog';
+import { createRouteRef } from '@backstage/core';
const setup = (
props?: Partial>,
@@ -30,6 +31,10 @@ const setup = (
{}}
entities={[]}
+ catalogRouteRef={createRouteRef({
+ path: '/catalog',
+ title: 'Service Catalog',
+ })}
{...props}
/>
diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx
index 15d0b9ced6..57b7fae9c3 100644
--- a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx
+++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import React, { FC } from 'react';
+import React from 'react';
import {
Dialog,
DialogTitle,
@@ -28,25 +28,50 @@ import {
Button,
} from '@material-ui/core';
import { Entity } from '@backstage/catalog-model';
-import { StructuredMetadataTable } from '@backstage/core';
-import { generatePath } from 'react-router';
-import {
- entityRoute,
- rootRoute as catalogRootRoute,
-} from '@backstage/plugin-catalog';
+import { StructuredMetadataTable, RouteRef } from '@backstage/core';
+import { generatePath, resolvePath } from 'react-router';
+import { entityRoute } from '@backstage/plugin-catalog';
import { Link as RouterLink } from 'react-router-dom';
type Props = {
onClose: () => void;
classes?: Record;
entities: Entity[];
+ catalogRouteRef: RouteRef;
};
-export const RegisterComponentResultDialog: FC = ({
+const getEntityCatalogPath = ({
+ entity,
+ catalogRouteRef,
+}: {
+ entity: Entity;
+ catalogRouteRef: RouteRef;
+}) => {
+ const optionalNamespaceAndName = [
+ entity.metadata.namespace,
+ entity.metadata.name,
+ ]
+ .filter(Boolean)
+ .join(':');
+
+ const relativeEntityPathInsideCatalog = generatePath(entityRoute.path, {
+ optionalNamespaceAndName,
+ kind: entity.kind,
+ });
+
+ const resolvedAbsolutePath = resolvePath(
+ relativeEntityPathInsideCatalog,
+ catalogRouteRef.path,
+ )?.pathname;
+ return resolvedAbsolutePath;
+};
+
+export const RegisterComponentResultDialog = ({
onClose,
classes,
entities,
-}) => (
+ catalogRouteRef,
+}: Props) => (
Component Registration Result
@@ -55,17 +80,7 @@ export const RegisterComponentResultDialog: FC = ({
{entities.map((entity: any, index: number) => {
- const entityPath = generatePath(entityRoute.path, {
- optionalNamespaceAndName: [
- entity.metadata.namespace,
- entity.metadata.name,
- ]
- .filter(Boolean)
- .join(':'),
- kind: entity.kind,
- selectedTabId: 'overview',
- });
-
+ const entityPath = getEntityCatalogPath({ entity, catalogRouteRef });
return (
= ({
-
+
To Catalog
diff --git a/plugins/circleci/src/components/Layout/Layout.tsx b/plugins/register-component/src/components/Router.tsx
similarity index 57%
rename from plugins/circleci/src/components/Layout/Layout.tsx
rename to plugins/register-component/src/components/Router.tsx
index 09479e6ef0..eee7fd3806 100644
--- a/plugins/circleci/src/components/Layout/Layout.tsx
+++ b/plugins/register-component/src/components/Router.tsx
@@ -14,16 +14,16 @@
* limitations under the License.
*/
import React from 'react';
-import { Header, Page, pageTheme, HeaderLabel } from '@backstage/core';
+import { Route, Routes } from 'react-router';
+import { RegisterComponentPage } from './RegisterComponentPage';
+import { RouteRef } from '@backstage/core';
-export const Layout: React.FC = ({ children }) => {
- return (
-
-
- {children}
-
- );
-};
+// As we don't know which path the catalog's router mounted on
+// We need to inject this from the app
+export const Router = ({ catalogRouteRef }: { catalogRouteRef: RouteRef }) => (
+
+ }
+ />
+
+);
diff --git a/plugins/register-component/src/index.ts b/plugins/register-component/src/index.ts
index 5b20cb0158..ff7857cacd 100644
--- a/plugins/register-component/src/index.ts
+++ b/plugins/register-component/src/index.ts
@@ -14,4 +14,5 @@
* limitations under the License.
*/
-export { plugin, rootRoute } from './plugin';
+export { plugin } from './plugin';
+export { Router } from './components/Router';
diff --git a/plugins/register-component/src/plugin.ts b/plugins/register-component/src/plugin.ts
index 9c73688a70..17f99917c8 100644
--- a/plugins/register-component/src/plugin.ts
+++ b/plugins/register-component/src/plugin.ts
@@ -14,18 +14,8 @@
* limitations under the License.
*/
-import { createPlugin, createRouteRef } from '@backstage/core';
-import RegisterComponentPage from './components/RegisterComponentPage';
-
-export const rootRoute = createRouteRef({
- icon: () => null,
- path: '/register-component',
- title: 'Register component',
-});
+import { createPlugin } from '@backstage/core';
export const plugin = createPlugin({
id: 'register-component',
- register({ router }) {
- router.addRoute(rootRoute, RegisterComponentPage);
- },
});
diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json
index 1708dc31ce..ae9d0c5bc5 100644
--- a/plugins/rollbar/package.json
+++ b/plugins/rollbar/package.json
@@ -25,7 +25,7 @@
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"lodash": "^4.17.15",
diff --git a/plugins/rollbar/src/components/Router.tsx b/plugins/rollbar/src/components/Router.tsx
index c765c14f14..bcf325f281 100644
--- a/plugins/rollbar/src/components/Router.tsx
+++ b/plugins/rollbar/src/components/Router.tsx
@@ -32,10 +32,7 @@ type Props = {
export const Router = ({ entity }: Props) =>
!isPluginApplicableToEntity(entity) ? (
-
- entity.metadata.annotations['{ROLLBAR_ANNOTATION}']` key is missing on
- the entity.
-
+ {ROLLBAR_ANNOTATION} annotation is missing on the entity.
) : (
diff --git a/plugins/rollbar/src/plugin.ts b/plugins/rollbar/src/plugin.ts
index 2cf464a8f6..c3ca6173ff 100644
--- a/plugins/rollbar/src/plugin.ts
+++ b/plugins/rollbar/src/plugin.ts
@@ -14,13 +14,26 @@
* limitations under the License.
*/
-import { createPlugin } from '@backstage/core';
+import {
+ createPlugin,
+ createApiFactory,
+ discoveryApiRef,
+} from '@backstage/core';
import { rootRouteRef, entityRouteRef } from './routes';
import { RollbarHome } from './components/RollbarHome/RollbarHome';
import { RollbarProjectPage } from './components/RollbarProjectPage/RollbarProjectPage';
+import { rollbarApiRef } from './api/RollbarApi';
+import { RollbarClient } from './api/RollbarClient';
export const plugin = createPlugin({
id: 'rollbar',
+ apis: [
+ createApiFactory({
+ api: rollbarApiRef,
+ deps: { discoveryApi: discoveryApiRef },
+ factory: ({ discoveryApi }) => new RollbarClient({ discoveryApi }),
+ }),
+ ],
register({ router }) {
router.addRoute(rootRouteRef, RollbarHome);
router.addRoute(entityRouteRef, RollbarProjectPage);
diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json
index 720eb7cc15..2fe27f4eb7 100644
--- a/plugins/scaffolder-backend/package.json
+++ b/plugins/scaffolder-backend/package.json
@@ -45,7 +45,7 @@
},
"devDependencies": {
"@backstage/cli": "^0.1.1-alpha.21",
- "@octokit/types": "^5.0.1",
+ "@octokit/types": "^5.4.1",
"@types/fs-extra": "^9.0.1",
"@types/git-url-parse": "^9.0.0",
"@types/nodegit": "0.26.8",
diff --git a/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml b/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml
index da2152280d..65d44d1b8a 100644
--- a/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml
+++ b/plugins/scaffolder-backend/sample-templates/docs-template/template.yaml
@@ -5,7 +5,7 @@ metadata:
title: Documentation Template
description: Create a new standalone documentation project
tags:
- - experimental
+ - recommended
- techdocs
- mkdocs
spec:
diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json
index 7497cbe024..2efc2c270a 100644
--- a/plugins/scaffolder/package.json
+++ b/plugins/scaffolder/package.json
@@ -25,7 +25,7 @@
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@rjsf/core": "^2.1.0",
diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts
index 3c42ed2ca4..cc69ed7627 100644
--- a/plugins/scaffolder/src/api.ts
+++ b/plugins/scaffolder/src/api.ts
@@ -30,6 +30,8 @@ export class ScaffolderApi {
}
/**
+ * Executes the scaffolding of a component, given a template and its
+ * parameter values.
*
* @param template Template entity for the scaffolder to use. New project is going to be created out of this template.
* @param values Parameters for the template, e.g. name, description
@@ -49,7 +51,9 @@ export class ScaffolderApi {
});
if (response.status !== 201) {
- throw new Error(await response.text());
+ const status = `${response.status} ${response.statusText}`;
+ const body = await response.text();
+ throw new Error(`Backend request failed, ${status} ${body.trim()}`);
}
const { id } = await response.json();
@@ -57,9 +61,8 @@ export class ScaffolderApi {
}
async getJob(jobId: string) {
- const url = `${await this.discoveryApi.getBaseUrl(
- 'scaffolder',
- )}/v1/job/${encodeURIComponent(jobId)}`;
+ const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder');
+ const url = `${baseUrl}/v1/job/${encodeURIComponent(jobId)}`;
return fetch(url).then(x => x.json());
}
}
diff --git a/plugins/scaffolder/src/components/JobStage/JobStage.tsx b/plugins/scaffolder/src/components/JobStage/JobStage.tsx
index 1e9adafb18..c7de6dff61 100644
--- a/plugins/scaffolder/src/components/JobStage/JobStage.tsx
+++ b/plugins/scaffolder/src/components/JobStage/JobStage.tsx
@@ -13,16 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
import {
+ Accordion,
+ AccordionDetails,
+ AccordionSummary,
Box,
- ExpansionPanel,
- ExpansionPanelDetails,
- ExpansionPanelSummary,
+ CircularProgress,
LinearProgress,
Typography,
} from '@material-ui/core';
import { makeStyles } from '@material-ui/core/styles';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import ExpandLessIcon from '@material-ui/icons/ExpandLess';
import cn from 'classnames';
import moment from 'moment';
import React, { Suspense, useEffect, useState } from 'react';
@@ -37,8 +40,7 @@ const useStyles = makeStyles(theme => ({
},
button: {
order: -1,
- marginRight: 0,
- marginLeft: '-20px',
+ margin: '0 1em 0 -20px',
},
cardContent: {
backgroundColor: theme.palette.background.default,
@@ -98,7 +100,7 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => {
: null;
return (
- {
expanded={expanded}
onChange={(_, newState) => setExpanded(newState)}
>
- }
+ : }
aria-controls={`panel-${name}-content`}
id={`panel-${name}-header`}
IconButtonProps={{
@@ -117,10 +119,11 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => {
}}
>
- {name} {timeElapsed && `(${timeElapsed})`}
+ {name} {timeElapsed && `(${timeElapsed})`}{' '}
+ {startedAt && !endedAt && }
-
-
+
+
{log.length === 0 ? (
No logs available for this step
) : (
@@ -130,7 +133,7 @@ export const JobStage = ({ endedAt, startedAt, name, log, status }: Props) => {
)}
-
-
+
+
);
};
diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx
index c6ed55c92c..102e93f91d 100644
--- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx
+++ b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx
@@ -51,7 +51,7 @@ export const JobStatusModal = ({
return (
- Creating component...
+ Creating Component...
{!job ? (
diff --git a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx
index 6f8bf0d550..b5bdaaef4a 100644
--- a/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx
+++ b/plugins/scaffolder/src/components/ScaffolderPage/ScaffolderPage.tsx
@@ -26,9 +26,10 @@ import {
Progress,
SupportButton,
useApi,
+ WarningPanel,
} from '@backstage/core';
import { catalogApiRef } from '@backstage/plugin-catalog';
-import { Button, Grid, Typography, Link } from '@material-ui/core';
+import { Button, Grid, Link, Typography } from '@material-ui/core';
import React, { useEffect } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import useStaleWhileRevalidate from 'swr';
@@ -46,7 +47,8 @@ const getTemplateCardProps = (
tags: (template.metadata?.tags as string[]) ?? [],
};
};
-export const ScaffolderPage: React.FC<{}> = () => {
+
+export const ScaffolderPage = () => {
const catalogApi = useApi(catalogApiRef);
const errorApi = useApi(errorApiRef);
@@ -66,23 +68,23 @@ export const ScaffolderPage: React.FC<{}> = () => {
return (
- Create a new component
+ Create a New Component
>
}
subtitle="Create new software components using standard templates"
/>
-
+
- Register existing component
+ Register Existing Component
Create new software components using standard templates. Different
@@ -101,16 +103,16 @@ export const ScaffolderPage: React.FC<{}> = () => {
)}
{error && (
-
+
Oops! Something went wrong loading the templates: {error.message}
-
+
)}
{templates &&
templates?.length > 0 &&
templates.map(template => {
return (
-
+
);
diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx
index 3c22e8d50c..1ed0504894 100644
--- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx
+++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx
@@ -91,8 +91,12 @@ export const TemplatePage = () => {
const handleClose = () => setJobId(null);
const handleCreate = async () => {
- const job = await scaffolderApi.scaffold(template!, formState);
- setJobId(job);
+ try {
+ const job = await scaffolderApi.scaffold(template!, formState);
+ setJobId(job);
+ } catch (e) {
+ errorApi.post(e);
+ }
};
const [entity, setEntity] = React.useState(
@@ -157,7 +161,7 @@ export const TemplatePage = () => {
/>
)}
{template && (
-
+
new ScaffolderApi({ discoveryApi }),
+ }),
+ ],
register({ router }) {
router.addRoute(rootRoute, ScaffolderPage);
router.addRoute(templateRoute, TemplatePage);
diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json
index 294d7159bf..e3b900ca8d 100644
--- a/plugins/sentry/package.json
+++ b/plugins/sentry/package.json
@@ -24,7 +24,7 @@
"@backstage/catalog-model": "^0.1.1-alpha.21",
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@types/react": "^16.9",
diff --git a/plugins/tech-radar/README.md b/plugins/tech-radar/README.md
index fb9d220b8c..cbe4dd22eb 100644
--- a/plugins/tech-radar/README.md
+++ b/plugins/tech-radar/README.md
@@ -29,71 +29,34 @@ For either simple or advanced installations, you'll need to add the dependency u
yarn add @backstage/plugin-tech-radar
```
-### Simple Configuration
+### Configuration
-In your `apis.ts` set up the simple "out of the box" implementation for Tech Radar:
-
-```ts
-import { ApiHolder, ApiRegistry } from '@backstage/core';
-import {
- techRadarApiRef,
- TechRadar,
-} from '@backstage/plugin-tech-radar';
-
-const builder = ApiRegistry.builder();
-
-builder.add(techRadarApiRef, new TechRadar({
- width: 1400,
- height: 800
-));
-
-export default builder.build() as ApiHolder;
-```
-
-Congrats, you're done! We'll just load it with [example data](src/sampleData.ts) to get you started. Just go to to see it live in action.
-
-And if you'd like to configure it more, such as providing it with your own data, see the `TechRadarApi` TypeScript interface below for the options:
-
-```ts
-export interface TechRadarComponentProps {
- width: number;
- height: number;
- getData?: () => Promise;
- svgProps?: object;
-}
-
-export interface TechRadarApi extends TechRadarComponentProps {
- title?: string;
- subtitle?: string;
-}
-```
-
-You can see the API directly over at [src/api.ts](./src/api.ts).
-
-### Advanced Configuration
-
-This way won't expose an `/tech-radar` path. Instead, you'll need to create your own Backstage plugin and use the Tech Radar as any other React UI component.
-
-In your Backstage app, run the following command:
-
-```sh
-yarn create-plugin
-```
-
-In your plugin, in any React component you'd like to import the Tech Radar, do the following:
+Modify your app routes to include the Router component exported from the tech radar, for example:
```tsx
-import { TechRadarComponent } from '@backstage/plugin-tech-radar';
+import { Router as TechRadarRouter } from '@backstage/plugin-tech-radar';
-function MyCustomRadar() {
- return ;
-}
+// Inside App component
+
+ {/* other routes ... */}
+ }
+ />
+ {/* other routes ... */}
+ ;
```
-If you'd like to configure it more, see the `TechRadarComponentProps` TypeScript interface for options:
+If you'd like to configure it more, see the `TechRadarPageProps` and `TechRadarComponentProps` types for options:
```ts
-export interface TechRadarComponentProps {
+export type TechRadarPageProps = TechRadarComponentProps & {
+ title?: string;
+ subtitle?: string;
+ pageTitle?: string;
+};
+
+export interface TechRadarPageProps {
width: number;
height: number;
getData?: () => Promise;
@@ -101,8 +64,6 @@ export interface TechRadarComponentProps {
}
```
-You can see the API directly over at [src/api.ts](./src/api.ts).
-
## Frequently Asked Questions
### Who created the Tech Radar?
@@ -111,7 +72,7 @@ You can see the API directly over at [src/api.ts](./src/api.ts).
### How do I load in my own data?
-It's simple. In both the Simple (Backstage plugin) and Advanced (React component) configurations, you can pass through a `getData` prop which expects a `Promise` signature. See more in [src/api.ts](./src/api.ts).
+It's simple, you can pass through a `getData` prop which expects a `Promise` signature.
Here's an example:
@@ -133,42 +94,21 @@ const getHardCodedData = () =>
],
});
-// Simple
-builder.add(techRadarApiRef, new TechRadar({
- width: 1400,
- height: 800,
- getData: getHardCodedData
-));
-
-// Advanced
-
+ ;
```
### How do I write tests?
You can use the `svgProps` option to pass custom React props to the `` element we create for the Tech Radar. This complements well with the `data-testid` attribute and the `@testing-library/react` library we use in Backstage.
-```ts
-// Simple
-builder.add(
- techRadarApiRef,
- new TechRadar({
- width: 1400,
- height: 800,
- svgProps: {
- 'data-testid': 'tech-radar-svg',
- },
- }),
-);
-
-// Advanced
+```tsx
;
+/>
// Then, in your tests...
// const { getByTestId } = render(...);
diff --git a/plugins/tech-radar/dev/index.tsx b/plugins/tech-radar/dev/index.tsx
index ac19b63a90..92eb6da567 100644
--- a/plugins/tech-radar/dev/index.tsx
+++ b/plugins/tech-radar/dev/index.tsx
@@ -15,14 +15,6 @@
*/
import { createDevApp } from '@backstage/dev-utils';
-import { plugin } from '../src/plugin';
-import { techRadarApiRef, TechRadar } from '../src';
+import { plugin } from '../src';
-createDevApp()
- .registerPlugin(plugin)
- .registerApiFactory({
- implements: techRadarApiRef,
- deps: {},
- factory: () => new TechRadar({ width: 1500, height: 800 }),
- })
- .render();
+createDevApp().registerPlugin(plugin).render();
diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json
index 4039f40878..fc29d9c836 100644
--- a/plugins/tech-radar/package.json
+++ b/plugins/tech-radar/package.json
@@ -22,9 +22,9 @@
},
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
- "@backstage/test-utils-core": "^0.1.1-alpha.21",
+ "@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"color": "^3.1.2",
diff --git a/plugins/tech-radar/src/api.ts b/plugins/tech-radar/src/api.ts
index 856cf4ff3c..7a6e790136 100644
--- a/plugins/tech-radar/src/api.ts
+++ b/plugins/tech-radar/src/api.ts
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { createApiRef } from '@backstage/core';
import { MovedState } from './utils/types';
/**
@@ -72,37 +71,3 @@ export interface TechRadarApi extends TechRadarComponentProps {
subtitle?: string;
pageTitle?: string;
}
-
-export const techRadarApiRef = createApiRef({
- id: 'plugin.techradar',
- description: 'Used by the Tech Radar to render the visualization',
-});
-
-export class TechRadar implements TechRadarApi {
- // Default columns
- public width: TechRadarApi['width'];
- public height: TechRadarApi['height'];
- public getData: TechRadarApi['getData'];
- public svgProps: TechRadarApi['svgProps'];
- public title: TechRadarApi['title'];
- public subtitle: TechRadarApi['subtitle'];
- public pageTitle: TechRadarApi['pageTitle'];
-
- constructor(overrideOptions: TechRadarApi) {
- const defaultOptions: Partial = {
- title: 'Tech Radar',
- subtitle: 'Pick the recommended technologies for your projects',
- pageTitle: 'Company Radar',
- };
-
- const options = { ...defaultOptions, ...overrideOptions };
-
- this.width = options.width;
- this.height = options.height;
- this.getData = options.getData;
- this.svgProps = options.svgProps;
- this.title = options.title;
- this.subtitle = options.subtitle;
- this.pageTitle = options.pageTitle;
- }
-}
diff --git a/plugins/tech-radar/src/components/RadarComponent.test.tsx b/plugins/tech-radar/src/components/RadarComponent.test.tsx
index 2f3fcff198..5591d74b82 100644
--- a/plugins/tech-radar/src/components/RadarComponent.test.tsx
+++ b/plugins/tech-radar/src/components/RadarComponent.test.tsx
@@ -19,7 +19,7 @@ import { render, waitForElement } from '@testing-library/react';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { ApiRegistry, ApiProvider, errorApiRef } from '@backstage/core';
-import { withLogCollector } from '@backstage/test-utils-core';
+import { withLogCollector } from '@backstage/test-utils';
import GetBBoxPolyfill from '../utils/polyfills/getBBox';
import RadarComponent from './RadarComponent';
diff --git a/plugins/tech-radar/src/components/RadarPage.test.tsx b/plugins/tech-radar/src/components/RadarPage.test.tsx
index c318985acd..97fae16380 100644
--- a/plugins/tech-radar/src/components/RadarPage.test.tsx
+++ b/plugins/tech-radar/src/components/RadarPage.test.tsx
@@ -19,11 +19,10 @@ import { render, waitForElement } from '@testing-library/react';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { ApiRegistry, ApiProvider, errorApiRef } from '@backstage/core';
-import { withLogCollector } from '@backstage/test-utils-core';
import GetBBoxPolyfill from '../utils/polyfills/getBBox';
-import { techRadarApiRef, TechRadar } from '../index';
-import RadarPage from './RadarPage';
+import { RadarPage } from './RadarPage';
+import { MockErrorApi, wrapInTestApp } from '@backstage/test-utils';
describe('RadarPage', () => {
beforeAll(() => {
@@ -35,24 +34,18 @@ describe('RadarPage', () => {
});
it('should render a progress bar', async () => {
- const errorApi = { post: () => {} };
- const techRadarApi = new TechRadar({
+ const techRadarProps = {
width: 1200,
height: 800,
svgProps: { 'data-testid': 'tech-radar-svg' },
- });
+ };
const { getByTestId, queryByTestId } = render(
-
-
-
-
- ,
+ wrapInTestApp(
+
+
+ ,
+ ),
);
expect(getByTestId('progress')).toBeInTheDocument();
@@ -61,24 +54,18 @@ describe('RadarPage', () => {
});
it('should render a header with a svg', async () => {
- const errorApi = { post: () => {} };
- const techRadarApi = new TechRadar({
+ const techRadarProps = {
width: 1200,
height: 800,
svgProps: { 'data-testid': 'tech-radar-svg' },
- });
+ };
const { getByText, getByTestId } = render(
-
-
-
-
- ,
+ wrapInTestApp(
+
+
+ ,
+ ),
);
await waitForElement(() => getByTestId('tech-radar-svg'));
@@ -90,78 +77,29 @@ describe('RadarPage', () => {
});
it('should call the errorApi if load fails', async () => {
- const errorApi = { post: jest.fn() };
+ const errorApi = new MockErrorApi({ collect: true });
const techRadarLoadFail = () =>
Promise.reject(new Error('404 Page Not Found'));
- const techRadarApi = new TechRadar({
+ const techRadarProps = {
width: 1200,
height: 800,
getData: techRadarLoadFail,
svgProps: { 'data-testid': 'tech-radar-svg' },
- });
+ };
const { queryByTestId } = render(
-
-
+
+
,
);
await waitForElement(() => !queryByTestId('progress'));
- expect(errorApi.post).toHaveBeenCalledTimes(1);
- expect(errorApi.post).toHaveBeenCalledWith(new Error('404 Page Not Found'));
+ expect(errorApi.getErrors()).toEqual([
+ { error: new Error('404 Page Not Found'), context: undefined },
+ ]);
expect(queryByTestId('tech-radar-svg')).not.toBeInTheDocument();
});
-
- it('should not render without errorApiRef', () => {
- const techRadarApi = new TechRadar({
- width: 1200,
- height: 800,
- });
-
- expect(
- withLogCollector(['error'], () => {
- expect(() => {
- render(
-
-
-
-
- ,
- );
- }).toThrow();
- }).error[0],
- ).toMatch(
- /^Error: Uncaught \[Error: No implementation available for apiRef{core.error}\]/,
- );
- });
-
- it('should not render without techRadarApiRef', () => {
- const errorApi = { post: () => {} };
-
- expect(
- withLogCollector(['error'], () => {
- expect(() => {
- render(
-
-
-
-
- ,
- );
- }).toThrow();
- }).error[0],
- ).toMatch(
- /^Error: Uncaught \[Error: No implementation available for apiRef{plugin.techradar}\]/,
- );
- });
});
diff --git a/plugins/tech-radar/src/components/RadarPage.tsx b/plugins/tech-radar/src/components/RadarPage.tsx
index 1aefb72eca..519c9afeee 100644
--- a/plugins/tech-radar/src/components/RadarPage.tsx
+++ b/plugins/tech-radar/src/components/RadarPage.tsx
@@ -24,36 +24,46 @@ import {
HeaderLabel,
SupportButton,
pageTheme,
- useApi,
} from '@backstage/core';
import RadarComponent from '../components/RadarComponent';
-import { techRadarApiRef, TechRadarApi } from '../api';
+import { TechRadarComponentProps } from '../api';
-const RadarPage = (): JSX.Element => {
- const techRadarApi = useApi(techRadarApiRef);
-
- return (
-
-
-
-
-
- This is used for visualizing the official guidelines of different
- areas of software development such as languages, frameworks,
- infrastructure and processes.
-
-
-
-
-
-
-
-
-
- );
+export type TechRadarPageProps = TechRadarComponentProps & {
+ title?: string;
+ subtitle?: string;
+ pageTitle?: string;
};
-export default RadarPage;
+export const RadarPage = ({
+ title,
+ subtitle,
+ pageTitle,
+ ...props
+}: TechRadarPageProps): JSX.Element => (
+
+
+
+
+
+ This is used for visualizing the official guidelines of different
+ areas of software development such as languages, frameworks,
+ infrastructure and processes.
+
+
+
+
+
+
+
+
+
+);
+
+RadarPage.defaultProps = {
+ title: 'Tech Radar',
+ subtitle: 'Pick the recommended technologies for your projects',
+ pageTitle: 'Company Radar',
+};
diff --git a/plugins/tech-radar/src/index.ts b/plugins/tech-radar/src/index.ts
index df22e57558..d7b4921e9a 100644
--- a/plugins/tech-radar/src/index.ts
+++ b/plugins/tech-radar/src/index.ts
@@ -16,6 +16,8 @@
export { plugin } from './plugin';
+export { RadarPage as Router } from './components/RadarPage';
+
/**
* The TypeScript API for configuring Tech Radar.
*/
diff --git a/plugins/tech-radar/src/plugin.ts b/plugins/tech-radar/src/plugin.ts
index 3497a66f5d..d8dc41af2e 100644
--- a/plugins/tech-radar/src/plugin.ts
+++ b/plugins/tech-radar/src/plugin.ts
@@ -15,11 +15,7 @@
*/
import { createPlugin } from '@backstage/core';
-import RadarPage from './components/RadarPage';
export const plugin = createPlugin({
id: 'tech-radar',
- register({ router }) {
- router.registerRoute('/tech-radar', RadarPage);
- },
});
diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json
index 00650ccca8..edafe31657 100644
--- a/plugins/techdocs-backend/package.json
+++ b/plugins/techdocs-backend/package.json
@@ -26,6 +26,7 @@
"@backstage/config": "^0.1.1-alpha.21",
"@types/dockerode": "^2.5.34",
"@types/express": "^4.17.6",
+ "default-branch": "^1.0.8",
"dockerode": "^3.2.1",
"express": "^4.17.1",
"express-promise-router": "^3.0.3",
diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts b/plugins/techdocs-backend/src/helpers.ts
similarity index 64%
rename from plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts
rename to plugins/techdocs-backend/src/helpers.ts
index d962242262..25d7f593c2 100644
--- a/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts
+++ b/plugins/techdocs-backend/src/helpers.ts
@@ -13,14 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { Entity } from '@backstage/catalog-model';
-import { InputError } from '@backstage/backend-common';
-import { RemoteProtocol } from './types';
+
+import os from 'os';
+import path from 'path';
import parseGitUrl from 'git-url-parse';
import { Clone, Repository } from 'nodegit';
import fs from 'fs-extra';
-import os from 'os';
-import path from 'path';
+// @ts-ignore
+import defaultBranch from 'default-branch';
+import { Entity } from '@backstage/catalog-model';
+import { InputError } from '@backstage/backend-common';
+import { RemoteProtocol } from './techdocs/stages/prepare/types';
export type ParsedLocationAnnotation = {
type: RemoteProtocol;
@@ -58,16 +61,43 @@ export const parseReferenceAnnotation = (
};
};
-export const clearGithubRepositoryCache = () => {
- fs.removeSync(path.join(fs.realpathSync(os.tmpdir()), 'backstage-repo'));
+export const getLocationForEntity = (
+ entity: Entity,
+): ParsedLocationAnnotation => {
+ const { type, target } = parseReferenceAnnotation(
+ 'backstage.io/techdocs-ref',
+ entity,
+ );
+
+ switch (type) {
+ case 'github':
+ return { type, target };
+ case 'dir':
+ if (path.isAbsolute(target)) return { type, target };
+
+ return parseReferenceAnnotation(
+ 'backstage.io/managed-by-location',
+ entity,
+ );
+ default:
+ throw new Error(`Invalid reference annotation ${type}`);
+ }
};
-export const checkoutGitRepository = async (
- repoUrl: string,
+export const getGitHubRepositoryTempFolder = async (
+ repositoryUrl: string,
): Promise => {
- const parsedGitLocation = parseGitUrl(repoUrl);
+ const parsedGitLocation = parseGitUrl(repositoryUrl);
+ // removes .git from git location path
+ parsedGitLocation.git_suffix = false;
- const repositoryTmpPath = path.join(
+ if (!parsedGitLocation.ref) {
+ parsedGitLocation.ref = await defaultBranch(
+ parsedGitLocation.toString('https'),
+ );
+ }
+
+ return path.join(
// fs.realpathSync fixes a problem with macOS returning a path that is a symlink
fs.realpathSync(os.tmpdir()),
'backstage-repo',
@@ -76,48 +106,22 @@ export const checkoutGitRepository = async (
parsedGitLocation.name,
parsedGitLocation.ref,
);
-
- if (fs.existsSync(repositoryTmpPath)) {
- const repository = await Repository.open(repositoryTmpPath);
- const currentBranchName = (await repository.getCurrentBranch()).shorthand();
- await repository.mergeBranches(
- currentBranchName,
- `origin/${currentBranchName}`,
- );
- return repositoryTmpPath;
- }
-
- const repositoryCheckoutUrl = parsedGitLocation.toString('https');
-
- fs.mkdirSync(repositoryTmpPath, { recursive: true });
- await Clone.clone(repositoryCheckoutUrl, repositoryTmpPath, {});
-
- return repositoryTmpPath;
};
-// Could be merged with checkoutGitRepository
export const checkoutGithubRepository = async (
repoUrl: string,
): Promise => {
const parsedGitLocation = parseGitUrl(repoUrl);
+ const repositoryTmpPath = await getGitHubRepositoryTempFolder(repoUrl);
- // Should propably not be hardcoded names of env variables, but seems too hard to access config down here
+ // TODO: Should propably not be hardcoded names of env variables, but seems too hard to access config down here
const user = process.env.GITHUB_PRIVATE_TOKEN_USER || '';
const token = process.env.GITHUB_PRIVATE_TOKEN || '';
- const repositoryTmpPath = path.join(
- // fs.realpathSync fixes a problem with macOS returning a path that is a symlink
- fs.realpathSync(os.tmpdir()),
- 'backstage-repo',
- parsedGitLocation.source,
- parsedGitLocation.owner,
- parsedGitLocation.name,
- parsedGitLocation.ref,
- );
-
if (fs.existsSync(repositoryTmpPath)) {
const repository = await Repository.open(repositoryTmpPath);
const currentBranchName = (await repository.getCurrentBranch()).shorthand();
+ await repository.fetch('origin');
await repository.mergeBranches(
currentBranchName,
`origin/${currentBranchName}`,
@@ -136,3 +140,14 @@ export const checkoutGithubRepository = async (
return repositoryTmpPath;
};
+
+export const getLastCommitTimestamp = async (
+ repositoryUrl: string,
+): Promise => {
+ const repositoryLocation = await checkoutGithubRepository(repositoryUrl);
+
+ const repository = await Repository.open(repositoryLocation);
+ const commit = await repository.getReferenceCommit('HEAD');
+
+ return commit.date().getTime();
+};
diff --git a/plugins/techdocs-backend/src/service/helpers.ts b/plugins/techdocs-backend/src/service/helpers.ts
new file mode 100644
index 0000000000..0fc4f41b3d
--- /dev/null
+++ b/plugins/techdocs-backend/src/service/helpers.ts
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import Docker from 'dockerode';
+import { Logger } from 'winston';
+import { Entity } from '@backstage/catalog-model';
+import {
+ PreparerBuilder,
+ PublisherBase,
+ GeneratorBuilder,
+ PreparerBase,
+ GeneratorBase,
+} from '../techdocs';
+import { BuildMetadataStorage } from '../storage';
+import { getLocationForEntity, getLastCommitTimestamp } from '../helpers';
+
+const getEntityId = (entity: Entity) => {
+ return `${entity.kind}:${entity.metadata.namespace ?? ''}:${
+ entity.metadata.name
+ }`;
+};
+
+type DocsBuilderArguments = {
+ preparers: PreparerBuilder;
+ generators: GeneratorBuilder;
+ publisher: PublisherBase;
+ entity: Entity;
+ logger: Logger;
+ dockerClient: Docker;
+};
+
+export class DocsBuilder {
+ private preparer: PreparerBase;
+ private generator: GeneratorBase;
+ private publisher: PublisherBase;
+ private entity: Entity;
+ private logger: Logger;
+ private dockerClient: Docker;
+
+ constructor({
+ preparers,
+ generators,
+ publisher,
+ entity,
+ logger,
+ dockerClient,
+ }: DocsBuilderArguments) {
+ this.preparer = preparers.get(entity);
+ this.generator = generators.get(entity);
+ this.publisher = publisher;
+ this.entity = entity;
+ this.logger = logger;
+ this.dockerClient = dockerClient;
+ }
+
+ public async build() {
+ this.logger.info(
+ `[TechDocs] Running preparer on entity ${getEntityId(this.entity)}`,
+ );
+ const preparedDir = await this.preparer.prepare(this.entity);
+
+ this.logger.info(
+ `[TechDocs] Running generator on entity ${getEntityId(this.entity)}`,
+ );
+ const { resultDir } = await this.generator.run({
+ directory: preparedDir,
+ dockerClient: this.dockerClient,
+ });
+
+ this.logger.info(
+ `[TechDocs] Running publisher on entity ${getEntityId(this.entity)}`,
+ );
+ await this.publisher.publish({
+ entity: this.entity,
+ directory: resultDir,
+ });
+
+ if (!this.entity.metadata.uid) {
+ throw new Error(
+ 'Trying to build documentation for entity not in service catalog',
+ );
+ }
+
+ new BuildMetadataStorage(this.entity.metadata.uid).storeBuildTimestamp();
+ }
+
+ public async docsUpToDate() {
+ if (!this.entity.metadata.uid) {
+ throw new Error(
+ 'Trying to build documentation for entity not in service catalog',
+ );
+ }
+
+ const buildMetadataStorage = new BuildMetadataStorage(
+ this.entity.metadata.uid,
+ );
+ const { type, target } = getLocationForEntity(this.entity);
+
+ // Should probably be broken out and handled per type later. Doing this for now since we only support github age checks
+ if (type === 'github') {
+ const lastCommit = await getLastCommitTimestamp(target);
+ const storageTimeStamp = buildMetadataStorage.getTimestamp();
+
+ // Check if documentation source is newer than what we have
+ if (storageTimeStamp && storageTimeStamp >= lastCommit) {
+ this.logger.debug(
+ `[TechDocs] Docs for entity ${getEntityId(
+ this.entity,
+ )} is up to date.`,
+ );
+ return true;
+ }
+ }
+
+ this.logger.debug(
+ `[TechDocs] Docs for entity ${getEntityId(this.entity)} was outdated.`,
+ );
+ return false;
+ }
+}
diff --git a/plugins/techdocs-backend/src/service/router.test.ts b/plugins/techdocs-backend/src/service/router.test.ts
deleted file mode 100644
index c266f98677..0000000000
--- a/plugins/techdocs-backend/src/service/router.test.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2020 Spotify AB
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { getVoidLogger } from '@backstage/backend-common';
-import { Preparers, LocalPublish, Generators } from '../techdocs';
-import { ConfigReader } from '@backstage/config';
-import Docker from 'dockerode';
-import express from 'express';
-import request from 'supertest';
-import { createRouter } from './router';
-
-describe('createRouter', () => {
- let app: express.Express;
- const logger = getVoidLogger();
-
- beforeAll(async () => {
- const router = await createRouter({
- preparers: new Preparers(),
- generators: new Generators(),
- publisher: new LocalPublish(logger),
- logger: getVoidLogger(),
- dockerClient: new Docker(),
- config: ConfigReader.fromConfigs([]),
- });
- app = express().use(router);
- });
-
- describe('GET /', () => {
- it('does not explode', async () => {
- const response = await request(app).get('/');
-
- expect(response.status).toEqual(200);
- expect(response.text).toEqual('Hello TechDocs Backend');
- });
- });
-});
diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts
index bb26ea19b8..e0a066c168 100644
--- a/plugins/techdocs-backend/src/service/router.ts
+++ b/plugins/techdocs-backend/src/service/router.ts
@@ -28,6 +28,7 @@ import {
} from '../techdocs';
import { resolvePackagePath } from '@backstage/backend-common';
import { Entity } from '@backstage/catalog-model';
+import { DocsBuilder } from './helpers';
type RouterOptions = {
preparers: PreparerBuilder;
@@ -54,77 +55,36 @@ export async function createRouter({
}: RouterOptions): Promise {
const router = Router();
- const getEntityId = (entity: Entity) => {
- return `${entity.kind}:${entity.metadata.namespace ?? ''}:${
- entity.metadata.name
- }`;
- };
-
- const buildDocsForEntity = async (entity: Entity) => {
- const preparer = preparers.get(entity);
- const generator = generators.get(entity);
-
- logger.info(`[TechDocs] Running preparer on entity ${getEntityId(entity)}`);
- const preparedDir = await preparer.prepare(entity);
-
- logger.info(
- `[TechDocs] Running generator on entity ${getEntityId(entity)}`,
- );
- const { resultDir } = await generator.run({
- directory: preparedDir,
- dockerClient,
- });
-
- logger.info(
- `[TechDocs] Running publisher on entity ${getEntityId(entity)}`,
- );
- await publisher.publish({
- entity,
- directory: resultDir,
- });
- };
-
- router.get('/', async (_, res) => {
- res.status(200).send('Hello TechDocs Backend');
- });
-
- // TODO: This route should not exist in the future
- router.get('/buildall', async (_, res) => {
+ router.get('/docs/:kind/:namespace/:name/*', async (req, res) => {
const baseUrl = config.getString('backend.baseUrl');
- const entitiesResponse = (await (
- await fetch(`${baseUrl}/catalog/entities`)
- ).json()) as Entity[];
+ const storageUrl = config.getString('techdocs.storageUrl');
- const entitiesWithDocs = entitiesResponse.filter(
- entity => entity.metadata.annotations?.['backstage.io/techdocs-ref'],
- );
+ const { kind, namespace, name } = req.params;
- entitiesWithDocs.forEach(async entity => {
- await buildDocsForEntity(entity);
+ const entity = (await (
+ await fetch(
+ `${baseUrl}/catalog/entities/by-name/${kind}/${namespace}/${name}`,
+ )
+ ).json()) as Entity;
+
+ const docsBuilder = new DocsBuilder({
+ preparers,
+ generators,
+ publisher,
+ dockerClient,
+ logger,
+ entity,
});
- res.send('Successfully generated documentation');
+ if (!(await docsBuilder.docsUpToDate())) {
+ await docsBuilder.build();
+ }
+
+ return res.redirect(`${storageUrl}${req.path.replace('/docs', '')}`);
});
if (publisher instanceof LocalPublish) {
- router.use('/static/docs/', express.static(staticDocsDir));
- router.use(
- '/static/docs/:kind/:namespace/:name',
- async (req, res, next) => {
- const baseUrl = config.getString('backend.baseUrl');
- const { kind, namespace, name } = req.params;
-
- const entityResponse = await fetch(
- `${baseUrl}/catalog/entities/by-name/${kind}/${namespace}/${name}`,
- );
- if (!entityResponse.ok) next();
- const entity = (await entityResponse.json()) as Entity;
-
- await buildDocsForEntity(entity);
-
- res.redirect(req.originalUrl);
- },
- );
+ router.use('/static/docs', express.static(staticDocsDir));
}
return router;
diff --git a/plugins/circleci/src/state/types.ts b/plugins/techdocs-backend/src/storage/BuildMetadataStorage.ts
similarity index 58%
rename from plugins/circleci/src/state/types.ts
rename to plugins/techdocs-backend/src/storage/BuildMetadataStorage.ts
index 41b3577082..b82ec1291a 100644
--- a/plugins/circleci/src/state/types.ts
+++ b/plugins/techdocs-backend/src/storage/BuildMetadataStorage.ts
@@ -13,24 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-export type Settings = { owner: string; repo: string; token: string };
-export type SettingsState = Settings & {
- showSettings: boolean;
+type buildInfo = {
+ // uid: timestamp
+ [key: string]: number;
};
-export type State = SettingsState;
+const builds = {} as buildInfo;
-type SettingsAction =
- | {
- type: 'setCredentials';
- payload: {
- repo: string;
- owner: string;
- token: string;
- };
- }
- | { type: 'showSettings' }
- | { type: 'hideSettings' };
+export class BuildMetadataStorage {
+ public entityUid: string;
+ private builds: buildInfo;
-export type Action = SettingsAction;
+ constructor(entityUid: string) {
+ this.entityUid = entityUid;
+ this.builds = builds;
+ }
+
+ storeBuildTimestamp() {
+ this.builds[this.entityUid] = Date.now();
+ }
+
+ getTimestamp() {
+ return this.builds[this.entityUid];
+ }
+}
diff --git a/plugins/circleci/src/components/PluginHeader/index.ts b/plugins/techdocs-backend/src/storage/index.ts
similarity index 93%
rename from plugins/circleci/src/components/PluginHeader/index.ts
rename to plugins/techdocs-backend/src/storage/index.ts
index 4de972f6f2..3d37f69679 100644
--- a/plugins/circleci/src/components/PluginHeader/index.ts
+++ b/plugins/techdocs-backend/src/storage/index.ts
@@ -13,4 +13,4 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export * from './PluginHeader';
+export * from './BuildMetadataStorage';
diff --git a/plugins/techdocs-backend/src/techdocs/stages/generate/index.ts b/plugins/techdocs-backend/src/techdocs/stages/generate/index.ts
index 5c086a976a..3a8b9ad828 100644
--- a/plugins/techdocs-backend/src/techdocs/stages/generate/index.ts
+++ b/plugins/techdocs-backend/src/techdocs/stages/generate/index.ts
@@ -15,4 +15,4 @@
*/
export { TechdocsGenerator } from './techdocs';
export { Generators } from './generators';
-export type { GeneratorBuilder } from './types';
+export type { GeneratorBuilder, GeneratorBase } from './types';
diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts
index a77c7978da..e2b94d7e88 100644
--- a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts
+++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.test.ts
@@ -15,7 +15,7 @@
*/
import { DirectoryPreparer } from './dir';
import { getVoidLogger } from '@backstage/backend-common';
-import { checkoutGitRepository } from './helpers';
+import { checkoutGithubRepository } from '../../../helpers';
function normalizePath(path: string) {
return path
@@ -24,9 +24,11 @@ function normalizePath(path: string) {
.join('/');
}
-jest.mock('./helpers', () => ({
- ...jest.requireActual<{}>('./helpers'),
- checkoutGitRepository: jest.fn(() => '/tmp/backstage-repo/org/name/branch/'),
+jest.mock('../../../helpers', () => ({
+ ...jest.requireActual<{}>('../../../helpers'),
+ checkoutGithubRepository: jest.fn(
+ () => '/tmp/backstage-repo/org/name/branch/',
+ ),
}));
const logger = getVoidLogger();
@@ -85,6 +87,6 @@ describe('directory preparer', () => {
expect(normalizePath(await directoryPreparer.prepare(mockEntity))).toEqual(
'/tmp/backstage-repo/org/name/branch/docs',
);
- expect(checkoutGitRepository).toHaveBeenCalledTimes(1);
+ expect(checkoutGithubRepository).toHaveBeenCalledTimes(1);
});
});
diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts
index cc0416c331..a9eda943c6 100644
--- a/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts
+++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/dir.ts
@@ -16,7 +16,10 @@
import { PreparerBase } from './types';
import { Entity } from '@backstage/catalog-model';
import path from 'path';
-import { parseReferenceAnnotation, checkoutGitRepository } from './helpers';
+import {
+ parseReferenceAnnotation,
+ checkoutGithubRepository,
+} from '../../../helpers';
import { InputError } from '@backstage/backend-common';
import parseGitUrl from 'git-url-parse';
import { Logger } from 'winston';
@@ -40,7 +43,7 @@ export class DirectoryPreparer implements PreparerBase {
switch (type) {
case 'github': {
const parsedGitLocation = parseGitUrl(target);
- const repoLocation = await checkoutGitRepository(target);
+ const repoLocation = await checkoutGithubRepository(target);
return path.dirname(
path.join(repoLocation, parsedGitLocation.filepath),
diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/github.test.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/github.test.ts
index b78203d766..a188e1c986 100644
--- a/plugins/techdocs-backend/src/techdocs/stages/prepare/github.test.ts
+++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/github.test.ts
@@ -16,7 +16,7 @@
import { getVoidLogger } from '@backstage/backend-common';
import { GithubPreparer } from './github';
-import { checkoutGithubRepository } from './helpers';
+import { checkoutGithubRepository } from '../../../helpers';
function normalizePath(path: string) {
return path
@@ -25,8 +25,8 @@ function normalizePath(path: string) {
.join('/');
}
-jest.mock('./helpers', () => ({
- ...jest.requireActual<{}>('./helpers'),
+jest.mock('../../../helpers', () => ({
+ ...jest.requireActual<{}>('../../../helpers'),
checkoutGithubRepository: jest.fn(
() => '/tmp/backstage-repo/org/name/branch',
),
diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts
index 242b19c24d..7b9fc7fbe2 100644
--- a/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts
+++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/github.ts
@@ -18,7 +18,11 @@ import { Entity } from '@backstage/catalog-model';
import { InputError } from '@backstage/backend-common';
import { PreparerBase } from './types';
import parseGitUrl from 'git-url-parse';
-import { parseReferenceAnnotation, checkoutGithubRepository } from './helpers';
+import {
+ parseReferenceAnnotation,
+ checkoutGithubRepository,
+} from '../../../helpers';
+
import { Logger } from 'winston';
export class GithubPreparer implements PreparerBase {
diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/index.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/index.ts
index 535b56f327..48ffa75903 100644
--- a/plugins/techdocs-backend/src/techdocs/stages/prepare/index.ts
+++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/index.ts
@@ -16,4 +16,4 @@
export { DirectoryPreparer } from './dir';
export { GithubPreparer } from './github';
export { Preparers } from './preparers';
-export type { PreparerBuilder } from './types';
+export type { PreparerBuilder, PreparerBase } from './types';
diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/preparers.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/preparers.ts
index 1c8fadd145..2f0df47de4 100644
--- a/plugins/techdocs-backend/src/techdocs/stages/prepare/preparers.ts
+++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/preparers.ts
@@ -16,7 +16,7 @@
import { PreparerBase, RemoteProtocol, PreparerBuilder } from './types';
import { Entity } from '@backstage/catalog-model';
-import { parseReferenceAnnotation } from './helpers';
+import { parseReferenceAnnotation } from '../../../helpers';
export class Preparers implements PreparerBuilder {
private preparerMap = new Map();
diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/types.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/types.ts
index 8baf6347bb..1ceb884ea8 100644
--- a/plugins/techdocs-backend/src/techdocs/stages/prepare/types.ts
+++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/types.ts
@@ -30,4 +30,4 @@ export type PreparerBuilder = {
get(entity: Entity): PreparerBase;
};
-export type RemoteProtocol = 'dir' | string;
+export type RemoteProtocol = 'dir' | 'github' | 'file';
diff --git a/plugins/techdocs/dev/index.tsx b/plugins/techdocs/dev/index.tsx
index b415256fde..12974ef8b7 100644
--- a/plugins/techdocs/dev/index.tsx
+++ b/plugins/techdocs/dev/index.tsx
@@ -20,13 +20,13 @@ import { TechDocsDevStorageApi } from './api';
import { techdocsStorageApiRef } from '../src';
createDevApp()
- .registerApiFactory({
+ .registerApi({
+ api: techdocsStorageApiRef,
deps: {},
factory: () =>
new TechDocsDevStorageApi({
apiOrigin: 'http://localhost:3000/api',
}),
- implements: techdocsStorageApiRef,
})
.registerPlugin(plugin)
.render();
diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json
index 2d63c9dd53..a3471c8142 100644
--- a/plugins/techdocs/package.json
+++ b/plugins/techdocs/package.json
@@ -28,7 +28,7 @@
"@backstage/plugin-catalog": "^0.1.1-alpha.21",
"@backstage/test-utils": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"@types/react": "^16.9",
diff --git a/plugins/catalog/src/components/EntityPageDocs/EntityDocsPage.tsx b/plugins/techdocs/src/EntityPageDocs.tsx
similarity index 72%
rename from plugins/catalog/src/components/EntityPageDocs/EntityDocsPage.tsx
rename to plugins/techdocs/src/EntityPageDocs.tsx
index ceba7e3200..d10c7a5932 100644
--- a/plugins/catalog/src/components/EntityPageDocs/EntityDocsPage.tsx
+++ b/plugins/techdocs/src/EntityPageDocs.tsx
@@ -16,19 +16,16 @@
import React from 'react';
import { Entity } from '@backstage/catalog-model';
-import { Reader } from '@backstage/plugin-techdocs';
-import { Content } from '@backstage/core';
+import { Reader } from './reader';
export const EntityPageDocs = ({ entity }: { entity: Entity }) => {
return (
-
-
-
+
);
};
diff --git a/plugins/techdocs/src/Router.tsx b/plugins/techdocs/src/Router.tsx
new file mode 100644
index 0000000000..6dc0dd02ad
--- /dev/null
+++ b/plugins/techdocs/src/Router.tsx
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import React from 'react';
+import { Entity } from '@backstage/catalog-model';
+import { Route, Routes } from 'react-router-dom';
+import { WarningPanel } from '@backstage/core';
+
+import {
+ rootRouteRef,
+ rootDocsRouteRef,
+ rootCatalogDocsRouteRef,
+} from './plugin';
+import { TechDocsHome } from './reader/components/TechDocsHome';
+import { TechDocsPage } from './reader/components/TechDocsPage';
+import { EntityPageDocs } from './EntityPageDocs';
+
+const TECHDOCS_ANNOTATION = 'backstage.io/techdocs-ref';
+
+export const Router = () => {
+ return (
+
+ } />
+ } />
+
+ );
+};
+
+export const EmbeddedDocsRouter = ({ entity }: { entity: Entity }) => {
+ const projectId = entity.metadata.annotations?.[TECHDOCS_ANNOTATION];
+
+ if (!projectId) {
+ return (
+
+ {TECHDOCS_ANNOTATION} annotation is missing on the entity.
+
+
+ Getting Started
+
+
+ );
+ }
+
+ return (
+
+ }
+ />
+
+ );
+};
diff --git a/plugins/techdocs/src/index.ts b/plugins/techdocs/src/index.ts
index 2a412d1b20..1726e0daf3 100644
--- a/plugins/techdocs/src/index.ts
+++ b/plugins/techdocs/src/index.ts
@@ -15,5 +15,6 @@
*/
export { plugin } from './plugin';
+export { Router, EmbeddedDocsRouter } from './Router';
export * from './reader';
export * from './api';
diff --git a/plugins/techdocs/src/plugin.ts b/plugins/techdocs/src/plugin.ts
index 9b09544427..1f15e9ebd6 100644
--- a/plugins/techdocs/src/plugin.ts
+++ b/plugins/techdocs/src/plugin.ts
@@ -29,24 +29,39 @@
* limitations under the License.
*/
-import { createPlugin, createRouteRef } from '@backstage/core';
-import { TechDocsHome } from './reader/components/TechDocsHome';
-import { TechDocsPage } from './reader/components/TechDocsPage';
+import {
+ createPlugin,
+ createRouteRef,
+ createApiFactory,
+ configApiRef,
+} from '@backstage/core';
+import { techdocsStorageApiRef, TechDocsStorageApi } from './api';
export const rootRouteRef = createRouteRef({
- path: '/docs',
+ path: '',
title: 'TechDocs Landing Page',
});
export const rootDocsRouteRef = createRouteRef({
- path: '/docs/:entityId/*',
+ path: ':entityId/*',
+ title: 'Docs',
+});
+
+export const rootCatalogDocsRouteRef = createRouteRef({
+ path: '*',
title: 'Docs',
});
export const plugin = createPlugin({
id: 'techdocs',
- register({ router }) {
- router.addRoute(rootRouteRef, TechDocsHome);
- router.addRoute(rootDocsRouteRef, TechDocsPage);
- },
+ apis: [
+ createApiFactory({
+ api: techdocsStorageApiRef,
+ deps: { configApi: configApiRef },
+ factory: ({ configApi }) =>
+ new TechDocsStorageApi({
+ apiOrigin: configApi.getString('techdocs.requestUrl'),
+ }),
+ }),
+ ],
});
diff --git a/plugins/techdocs/src/reader/components/TechDocsHome.tsx b/plugins/techdocs/src/reader/components/TechDocsHome.tsx
index 17b9fd36f7..e51de8f57f 100644
--- a/plugins/techdocs/src/reader/components/TechDocsHome.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsHome.tsx
@@ -16,11 +16,12 @@
import React from 'react';
import { useAsync } from 'react-use';
-import { useNavigate } from 'react-router-dom';
+import { useNavigate, generatePath } from 'react-router-dom';
import { Grid } from '@material-ui/core';
import { ItemCard, Progress, useApi } from '@backstage/core';
import { TechDocsPageWrapper } from './TechDocsPageWrapper';
import { catalogApiRef } from '@backstage/plugin-catalog';
+import { rootDocsRouteRef } from '../../plugin';
export const TechDocsHome = () => {
const catalogApi = useApi(catalogApiRef);
@@ -67,9 +68,11 @@ export const TechDocsHome = () => {
navigate(
- `/docs/${entity.kind}:${
- entity.metadata.namespace ?? ''
- }:${entity.metadata.name}`,
+ generatePath(rootDocsRouteRef.path, {
+ entityId: `${entity.kind}:${
+ entity.metadata.namespace ?? ''
+ }:${entity.metadata.name}`,
+ }),
)
}
title={entity.metadata.name}
diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json
index 2d754a51a6..a0dae6859d 100644
--- a/plugins/welcome/package.json
+++ b/plugins/welcome/package.json
@@ -23,7 +23,7 @@
"dependencies": {
"@backstage/core": "^0.1.1-alpha.21",
"@backstage/theme": "^0.1.1-alpha.21",
- "@material-ui/core": "^4.9.1",
+ "@material-ui/core": "^4.11.0",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.45",
"react": "^16.13.1",
diff --git a/yarn.lock b/yarn.lock
index 72391dcef2..d86f62d9f0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -375,7 +375,7 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
-"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.11.1", "@babel/parser@^7.7.5":
+"@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.0", "@babel/parser@^7.11.1":
version "7.11.3"
resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.11.3.tgz#9e1eae46738bcd08e23e867bab43e7b95299a8f9"
integrity sha512-REo8xv7+sDxkKvoxEywIdsNFiZLybwdI7hcT5uEPyQrSMB4YQ973BfC9OOrD/81MaIjh6UxdulIQXkjmiH3PcA==
@@ -660,14 +660,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.10.4"
-"@babel/plugin-transform-block-scoping@^7.10.4":
- version "7.10.5"
- resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.10.5.tgz#b81b8aafefbfe68f0f65f7ef397b9ece68a6037d"
- integrity sha512-6Ycw3hjpQti0qssQcA6AMSFDHeNJ++R6dIMnpRqUjFeBBTmTDPa8zgF90OVfTvAo11mXZTlVUViY1g8ffrURLg==
- dependencies:
- "@babel/helper-plugin-utils" "^7.10.4"
-
-"@babel/plugin-transform-block-scoping@^7.8.3":
+"@babel/plugin-transform-block-scoping@^7.10.4", "@babel/plugin-transform-block-scoping@^7.8.3":
version "7.11.1"
resolved "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.11.1.tgz#5b7efe98852bef8d652c0b28144cd93a9e4b5215"
integrity sha512-00dYeDE0EVEHuuM+26+0w/SCL0BH2Qy7LwHuI4Hi4MH5gkC8/AqMN5uWFJIsoXZrAphiMm1iXzBw6L2T+eA0ew==
@@ -969,81 +962,7 @@
"@babel/helper-create-regexp-features-plugin" "^7.10.4"
"@babel/helper-plugin-utils" "^7.10.4"
-"@babel/preset-env@^7.9.5":
- version "7.11.0"
- resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.0.tgz#860ee38f2ce17ad60480c2021ba9689393efb796"
- integrity sha512-2u1/k7rG/gTh02dylX2kL3S0IJNF+J6bfDSp4DI2Ma8QN6Y9x9pmAax59fsCk6QUQG0yqH47yJWA+u1I1LccAg==
- dependencies:
- "@babel/compat-data" "^7.11.0"
- "@babel/helper-compilation-targets" "^7.10.4"
- "@babel/helper-module-imports" "^7.10.4"
- "@babel/helper-plugin-utils" "^7.10.4"
- "@babel/plugin-proposal-async-generator-functions" "^7.10.4"
- "@babel/plugin-proposal-class-properties" "^7.10.4"
- "@babel/plugin-proposal-dynamic-import" "^7.10.4"
- "@babel/plugin-proposal-export-namespace-from" "^7.10.4"
- "@babel/plugin-proposal-json-strings" "^7.10.4"
- "@babel/plugin-proposal-logical-assignment-operators" "^7.11.0"
- "@babel/plugin-proposal-nullish-coalescing-operator" "^7.10.4"
- "@babel/plugin-proposal-numeric-separator" "^7.10.4"
- "@babel/plugin-proposal-object-rest-spread" "^7.11.0"
- "@babel/plugin-proposal-optional-catch-binding" "^7.10.4"
- "@babel/plugin-proposal-optional-chaining" "^7.11.0"
- "@babel/plugin-proposal-private-methods" "^7.10.4"
- "@babel/plugin-proposal-unicode-property-regex" "^7.10.4"
- "@babel/plugin-syntax-async-generators" "^7.8.0"
- "@babel/plugin-syntax-class-properties" "^7.10.4"
- "@babel/plugin-syntax-dynamic-import" "^7.8.0"
- "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
- "@babel/plugin-syntax-json-strings" "^7.8.0"
- "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
- "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.0"
- "@babel/plugin-syntax-numeric-separator" "^7.10.4"
- "@babel/plugin-syntax-object-rest-spread" "^7.8.0"
- "@babel/plugin-syntax-optional-catch-binding" "^7.8.0"
- "@babel/plugin-syntax-optional-chaining" "^7.8.0"
- "@babel/plugin-syntax-top-level-await" "^7.10.4"
- "@babel/plugin-transform-arrow-functions" "^7.10.4"
- "@babel/plugin-transform-async-to-generator" "^7.10.4"
- "@babel/plugin-transform-block-scoped-functions" "^7.10.4"
- "@babel/plugin-transform-block-scoping" "^7.10.4"
- "@babel/plugin-transform-classes" "^7.10.4"
- "@babel/plugin-transform-computed-properties" "^7.10.4"
- "@babel/plugin-transform-destructuring" "^7.10.4"
- "@babel/plugin-transform-dotall-regex" "^7.10.4"
- "@babel/plugin-transform-duplicate-keys" "^7.10.4"
- "@babel/plugin-transform-exponentiation-operator" "^7.10.4"
- "@babel/plugin-transform-for-of" "^7.10.4"
- "@babel/plugin-transform-function-name" "^7.10.4"
- "@babel/plugin-transform-literals" "^7.10.4"
- "@babel/plugin-transform-member-expression-literals" "^7.10.4"
- "@babel/plugin-transform-modules-amd" "^7.10.4"
- "@babel/plugin-transform-modules-commonjs" "^7.10.4"
- "@babel/plugin-transform-modules-systemjs" "^7.10.4"
- "@babel/plugin-transform-modules-umd" "^7.10.4"
- "@babel/plugin-transform-named-capturing-groups-regex" "^7.10.4"
- "@babel/plugin-transform-new-target" "^7.10.4"
- "@babel/plugin-transform-object-super" "^7.10.4"
- "@babel/plugin-transform-parameters" "^7.10.4"
- "@babel/plugin-transform-property-literals" "^7.10.4"
- "@babel/plugin-transform-regenerator" "^7.10.4"
- "@babel/plugin-transform-reserved-words" "^7.10.4"
- "@babel/plugin-transform-shorthand-properties" "^7.10.4"
- "@babel/plugin-transform-spread" "^7.11.0"
- "@babel/plugin-transform-sticky-regex" "^7.10.4"
- "@babel/plugin-transform-template-literals" "^7.10.4"
- "@babel/plugin-transform-typeof-symbol" "^7.10.4"
- "@babel/plugin-transform-unicode-escapes" "^7.10.4"
- "@babel/plugin-transform-unicode-regex" "^7.10.4"
- "@babel/preset-modules" "^0.1.3"
- "@babel/types" "^7.11.0"
- browserslist "^4.12.0"
- core-js-compat "^3.6.2"
- invariant "^2.2.2"
- levenary "^1.1.1"
- semver "^5.5.0"
-
-"@babel/preset-env@^7.9.6":
+"@babel/preset-env@^7.9.5", "@babel/preset-env@^7.9.6":
version "7.11.5"
resolved "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.11.5.tgz#18cb4b9379e3e92ffea92c07471a99a2914e4272"
integrity sha512-kXqmW1jVcnB2cdueV+fyBM8estd5mlNfaQi6lwLgRwCby4edpavgbFhiBNjmWA3JpB/yZGSISa7Srf+TwxDQoA==
@@ -1191,7 +1110,7 @@
dependencies:
regenerator-runtime "^0.13.4"
-"@babel/template@^7.10.4", "@babel/template@^7.3.3", "@babel/template@^7.7.4":
+"@babel/template@^7.10.4", "@babel/template@^7.3.3":
version "7.10.4"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278"
integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==
@@ -1200,7 +1119,7 @@
"@babel/parser" "^7.10.4"
"@babel/types" "^7.10.4"
-"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.0", "@babel/traverse@^7.7.4":
+"@babel/traverse@^7.1.0", "@babel/traverse@^7.10.4", "@babel/traverse@^7.11.0":
version "7.11.0"
resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.0.tgz#9b996ce1b98f53f7c3e4175115605d56ed07dd24"
integrity sha512-ZB2V+LskoWKNpMq6E5UUCrjtDUh5IOTAyIl0dTjIEoXum/iKWkoIEKIRDnUucO6f+2FzNkE0oD4RLKoPIufDtg==
@@ -1215,16 +1134,7 @@
globals "^11.1.0"
lodash "^4.17.19"
-"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.9.5":
- version "7.11.0"
- resolved "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz#2ae6bf1ba9ae8c3c43824e5861269871b206e90d"
- integrity sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==
- dependencies:
- "@babel/helper-validator-identifier" "^7.10.4"
- lodash "^4.17.19"
- to-fast-properties "^2.0.0"
-
-"@babel/types@^7.11.5":
+"@babel/types@^7.0.0", "@babel/types@^7.10.4", "@babel/types@^7.10.5", "@babel/types@^7.11.0", "@babel/types@^7.11.5", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.9.5":
version "7.11.5"
resolved "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d"
integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==
@@ -2568,23 +2478,23 @@
resolved "https://registry.npmjs.org/@material-icons/font/-/font-1.0.3.tgz#f722e5a69a03f20ef47d015cb69420bebeeaabe5"
integrity sha512-aIRd0Z9b/HJ/O24KOaP7dNsXypMnXhpWrpLVYvQB/JesVqXYSbioYND200sR+C14a0LSCp+qWnWCnSXmN1hWGw==
-"@material-ui/core@^4.9.1":
- version "4.9.7"
- resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.9.7.tgz#0c1caf123278770f34c5d8e9ecd9e1314f87a621"
- integrity sha512-RTRibZgq572GHEskMAG4sP+bt3P3XyIkv3pOTR8grZAW2rSUd6JoGZLRM4S2HkuO7wS7cAU5SpU2s1EsmTgWog==
+"@material-ui/core@^4.11.0", "@material-ui/core@^4.9.1":
+ version "4.11.0"
+ resolved "https://registry.npmjs.org/@material-ui/core/-/core-4.11.0.tgz#b69b26e4553c9e53f2bfaf1053e216a0af9be15a"
+ integrity sha512-bYo9uIub8wGhZySHqLQ833zi4ZML+XCBE1XwJ8EuUVSpTWWG57Pm+YugQToJNFsEyiKFhPh8DPD0bgupz8n01g==
dependencies:
"@babel/runtime" "^7.4.4"
- "@material-ui/styles" "^4.9.6"
- "@material-ui/system" "^4.9.6"
- "@material-ui/types" "^5.0.0"
- "@material-ui/utils" "^4.9.6"
+ "@material-ui/styles" "^4.10.0"
+ "@material-ui/system" "^4.9.14"
+ "@material-ui/types" "^5.1.0"
+ "@material-ui/utils" "^4.10.2"
"@types/react-transition-group" "^4.2.0"
- clsx "^1.0.2"
+ clsx "^1.0.4"
hoist-non-react-statics "^3.3.2"
- popper.js "^1.14.1"
+ popper.js "1.16.1-lts"
prop-types "^15.7.2"
react-is "^16.8.0"
- react-transition-group "^4.3.0"
+ react-transition-group "^4.4.0"
"@material-ui/icons@^4.9.1":
version "4.9.1"
@@ -2616,16 +2526,16 @@
react-transition-group "^4.0.0"
rifm "^0.7.0"
-"@material-ui/styles@^4.9.6":
- version "4.9.6"
- resolved "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.6.tgz#924a30bf7c9b91af9c8f19c12c8573b8a4ecd085"
- integrity sha512-ijgwStEkw1OZ6gCz18hkjycpr/3lKs1hYPi88O/AUn4vMuuGEGAIrqKVFq/lADmZUNF3DOFIk8LDkp7zmjPxtA==
+"@material-ui/styles@^4.10.0":
+ version "4.10.0"
+ resolved "https://registry.npmjs.org/@material-ui/styles/-/styles-4.10.0.tgz#2406dc23aa358217aa8cc772e6237bd7f0544071"
+ integrity sha512-XPwiVTpd3rlnbfrgtEJ1eJJdFCXZkHxy8TrdieaTvwxNYj42VnnCyFzxYeNW9Lhj4V1oD8YtQ6S5Gie7bZDf7Q==
dependencies:
"@babel/runtime" "^7.4.4"
"@emotion/hash" "^0.8.0"
- "@material-ui/types" "^5.0.0"
+ "@material-ui/types" "^5.1.0"
"@material-ui/utils" "^4.9.6"
- clsx "^1.0.2"
+ clsx "^1.0.4"
csstype "^2.5.2"
hoist-non-react-statics "^3.3.2"
jss "^10.0.3"
@@ -2638,24 +2548,25 @@
jss-plugin-vendor-prefixer "^10.0.3"
prop-types "^15.7.2"
-"@material-ui/system@^4.9.6":
- version "4.9.6"
- resolved "https://registry.npmjs.org/@material-ui/system/-/system-4.9.6.tgz#fd060540224da4d1740da8ca6e7af288e217717e"
- integrity sha512-QtfoAePyqXoZ2HUVSwGb1Ro0kucMCvVjbI0CdYIR21t0Opgfm1Oer6ni9P5lfeXA39xSt0wCierw37j+YES48Q==
+"@material-ui/system@^4.9.14":
+ version "4.9.14"
+ resolved "https://registry.npmjs.org/@material-ui/system/-/system-4.9.14.tgz#4b00c48b569340cefb2036d0596b93ac6c587a5f"
+ integrity sha512-oQbaqfSnNlEkXEziDcJDDIy8pbvwUmZXWNqlmIwDqr/ZdCK8FuV3f4nxikUh7hvClKV2gnQ9djh5CZFTHkZj3w==
dependencies:
"@babel/runtime" "^7.4.4"
"@material-ui/utils" "^4.9.6"
+ csstype "^2.5.2"
prop-types "^15.7.2"
-"@material-ui/types@^5.0.0":
- version "5.0.0"
- resolved "https://registry.npmjs.org/@material-ui/types/-/types-5.0.0.tgz#26d6259dc6b39f4c2e1e9aceff7a11e031941741"
- integrity sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg==
+"@material-ui/types@^5.1.0":
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2"
+ integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==
-"@material-ui/utils@^4.7.1", "@material-ui/utils@^4.9.6":
- version "4.9.6"
- resolved "https://registry.npmjs.org/@material-ui/utils/-/utils-4.9.6.tgz#5f1f9f6e4df9c8b6a263293b68c94834248ff157"
- integrity sha512-gqlBn0JPPTUZeAktn1rgMcy9Iczrr74ecx31tyZLVGdBGGzsxzM6PP6zeS7FuoLS6vG4hoZP7hWnOoHtkR0Kvw==
+"@material-ui/utils@^4.10.2", "@material-ui/utils@^4.7.1", "@material-ui/utils@^4.9.6":
+ version "4.10.2"
+ resolved "https://registry.npmjs.org/@material-ui/utils/-/utils-4.10.2.tgz#3fd5470ca61b7341f1e0468ac8f29a70bf6df321"
+ integrity sha512-eg29v74P7W5r6a4tWWDAAfZldXIzfyO1am2fIsC39hdUUHm/33k6pGOKPbgDjg/U/4ifmgAePy/1OjkKN6rFRw==
dependencies:
"@babel/runtime" "^7.4.4"
prop-types "^15.7.2"
@@ -2783,12 +2694,12 @@
"@octokit/types" "^2.0.1"
deprecation "^2.3.1"
-"@octokit/plugin-rest-endpoint-methods@4.0.0":
- version "4.0.0"
- resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.0.0.tgz#b02a2006dda8e908c3f8ab381dd5475ef5a810a8"
- integrity sha512-emS6gysz4E9BNi9IrCl7Pm4kR+Az3MmVB0/DoDCmF4U48NbYG3weKyDlgkrz6Jbl4Mu4nDx8YWZwC4HjoTdcCA==
+"@octokit/plugin-rest-endpoint-methods@4.1.4":
+ version "4.1.4"
+ resolved "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.1.4.tgz#ca60736d761b304fec02a2608caaec2d822e9835"
+ integrity sha512-Y2tVpSa7HjV3DGIQrQOJcReJ2JtcN9FaGr9jDa332Flro923/h3/Iu9e7Y4GilnzfLclHEh5iCQoCkHm7tWOcg==
dependencies:
- "@octokit/types" "^5.0.0"
+ "@octokit/types" "^5.4.1"
deprecation "^2.3.1"
"@octokit/request-error@^1.0.2":
@@ -2846,14 +2757,14 @@
universal-user-agent "^4.0.0"
"@octokit/rest@^18.0.0":
- version "18.0.0"
- resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.0.tgz#7f401d9ce13530ad743dfd519ae62ce49bcc0358"
- integrity sha512-4G/a42lry9NFGuuECnua1R1eoKkdBYJap97jYbWDNYBOUboWcM75GJ1VIcfvwDV/pW0lMPs7CEmhHoVrSV5shg==
+ version "18.0.5"
+ resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.0.5.tgz#1f1498dcdc2d85d0f86b8168e4ff5779842b2742"
+ integrity sha512-SPKI24tQXrr1XsnaIjv2x0rl4M5eF1+hj8+vMe3d/exZ7NnL5sTe1BuFyCyJyrc+j1HkXankvgGN9zT0rwBwtg==
dependencies:
"@octokit/core" "^3.0.0"
"@octokit/plugin-paginate-rest" "^2.2.0"
"@octokit/plugin-request-log" "^1.0.0"
- "@octokit/plugin-rest-endpoint-methods" "4.0.0"
+ "@octokit/plugin-rest-endpoint-methods" "4.1.4"
"@octokit/types@^2.0.0", "@octokit/types@^2.0.1":
version "2.5.0"
@@ -2869,6 +2780,13 @@
dependencies:
"@types/node" ">= 8"
+"@octokit/types@^5.4.1":
+ version "5.4.1"
+ resolved "https://registry.npmjs.org/@octokit/types/-/types-5.4.1.tgz#d5d5f2b70ffc0e3f89467c3db749fa87fc3b7031"
+ integrity sha512-OlMlSySBJoJ6uozkr/i03nO5dlYQyE05vmQNZhAh9MyO4DPBP88QlwsDVLmVjIMFssvIZB6WO0ctIGMRG+xsJQ==
+ dependencies:
+ "@types/node" ">= 8"
+
"@open-draft/until@^1.0.0", "@open-draft/until@^1.0.3":
version "1.0.3"
resolved "https://registry.npmjs.org/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca"
@@ -3965,7 +3883,7 @@
resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0"
integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A==
-"@types/babel__core@^7.0.0":
+"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
version "7.1.9"
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d"
integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==
@@ -3976,17 +3894,6 @@
"@types/babel__template" "*"
"@types/babel__traverse" "*"
-"@types/babel__core@^7.1.7":
- version "7.1.7"
- resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89"
- integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==
- dependencies:
- "@babel/parser" "^7.1.0"
- "@babel/types" "^7.0.0"
- "@types/babel__generator" "*"
- "@types/babel__template" "*"
- "@types/babel__traverse" "*"
-
"@types/babel__generator@*":
version "7.6.1"
resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04"
@@ -4183,11 +4090,6 @@
resolved "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
-"@types/events@*":
- version "3.0.0"
- resolved "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
- integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==
-
"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.5":
version "4.17.9"
resolved "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.9.tgz#2d7b34dcfd25ec663c25c85d76608f8b249667f1"
@@ -4236,7 +4138,7 @@
resolved "https://registry.npmjs.org/@types/glob-base/-/glob-base-0.3.0.tgz#a581d688347e10e50dd7c17d6f2880a10354319d"
integrity sha1-pYHWiDR+EOUN18F9byiAoQNUMZ0=
-"@types/glob@*":
+"@types/glob@*", "@types/glob@^7.1.1":
version "7.1.3"
resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
integrity sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==
@@ -4244,15 +4146,6 @@
"@types/minimatch" "*"
"@types/node" "*"
-"@types/glob@^7.1.1":
- version "7.1.1"
- resolved "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
- integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
- dependencies:
- "@types/events" "*"
- "@types/minimatch" "*"
- "@types/node" "*"
-
"@types/google-protobuf@^3.7.2":
version "3.7.3"
resolved "https://registry.npmjs.org/@types/google-protobuf/-/google-protobuf-3.7.3.tgz#429512e541bbd777f2c867692e6335ee08d1f6d4"
@@ -5415,12 +5308,7 @@ ajv-errors@^1.0.0:
resolved "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
-ajv-keywords@^3.1.0, ajv-keywords@^3.4.1:
- version "3.4.1"
- resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da"
- integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==
-
-ajv-keywords@^3.5.2:
+ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
@@ -5445,7 +5333,7 @@ ajv@^5.0.0:
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
-ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.1, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.5.5, ajv@^6.7.0:
+ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.1, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.5.5, ajv@^6.7.0:
version "6.12.4"
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234"
integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==
@@ -7205,22 +7093,7 @@ chokidar@^2.1.8:
optionalDependencies:
fsevents "^1.2.7"
-chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1:
- version "3.4.1"
- resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz#e905bdecf10eaa0a0b1db0c664481cc4cbc22ba1"
- integrity sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==
- dependencies:
- anymatch "~3.1.1"
- braces "~3.0.2"
- glob-parent "~5.1.0"
- is-binary-path "~2.1.0"
- is-glob "~4.0.1"
- normalize-path "~3.0.0"
- readdirp "~3.4.0"
- optionalDependencies:
- fsevents "~2.1.2"
-
-chokidar@^3.4.1:
+chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.3.1, chokidar@^3.4.1:
version "3.4.2"
resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d"
integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A==
@@ -8000,6 +7873,17 @@ cosmiconfig@^5.0.0, cosmiconfig@^5.1.0, cosmiconfig@^5.2.1:
js-yaml "^3.13.1"
parse-json "^4.0.0"
+cosmiconfig@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3"
+ integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA==
+ dependencies:
+ "@types/parse-json" "^4.0.0"
+ import-fresh "^3.2.1"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+ yaml "^1.10.0"
+
create-ecdh@^4.0.0:
version "4.0.3"
resolved "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff"
@@ -8649,6 +8533,11 @@ deepmerge@^4.2.2:
resolved "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+default-branch@^1.0.8:
+ version "1.0.8"
+ resolved "https://registry.npmjs.org/default-branch/-/default-branch-1.0.8.tgz#0e2f36a90e3b0d9f73cdf8e02841364ed35b8b54"
+ integrity sha512-pViUZEnaxd/Hbu880MEXF7XqV8RJ1ssDlkvzx+woQhcKW8px3BrVDvwBGn09zRiKZ+gOLipK7ft5x3no+3vb8A==
+
default-gateway@^4.2.0:
version "4.2.0"
resolved "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz#167104c7500c2115f6dd69b0a536bb8ed720552b"
@@ -9267,16 +9156,7 @@ endent@^2.0.1:
fast-json-parse "^1.0.3"
objectorarray "^1.0.4"
-enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0:
- version "4.1.1"
- resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz#2937e2b8066cd0fe7ce0990a98f0d71a35189f66"
- integrity sha512-98p2zE+rL7/g/DzMHMTF4zZlCgeVdJ7yr6xzEpJRYwFYrGi9ANdn5DnJURg6RpBkyk60XYDnWIv51VfIhfNGuA==
- dependencies:
- graceful-fs "^4.1.2"
- memory-fs "^0.5.0"
- tapable "^1.0.0"
-
-enhanced-resolve@^4.3.0:
+enhanced-resolve@^4.0.0, enhanced-resolve@^4.3.0:
version "4.3.0"
resolved "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz#3b806f3bfafc1ec7de69551ef93cca46c1704126"
integrity sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==
@@ -11551,7 +11431,7 @@ html-to-react@^1.3.4:
lodash.camelcase "^4.3.0"
ramda "^0.26"
-html-webpack-plugin@^4.2.1:
+html-webpack-plugin@^4.2.1, html-webpack-plugin@^4.3.0:
version "4.4.1"
resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.4.1.tgz#61ab85aa1a84ba181443345ebaead51abbb84149"
integrity sha512-nEtdEIsIGXdXGG7MjTTZlmhqhpHU9pJFc1OYxcP36c5/ZKP6b0BJMww2QTvJGQYA9aMxUnjDujpZdYcVOXiBCQ==
@@ -11566,21 +11446,6 @@ html-webpack-plugin@^4.2.1:
tapable "^1.1.3"
util.promisify "1.0.0"
-html-webpack-plugin@^4.3.0:
- version "4.3.0"
- resolved "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-4.3.0.tgz#53bf8f6d696c4637d5b656d3d9863d89ce8174fd"
- integrity sha512-C0fzKN8yQoVLTelcJxZfJCE+aAvQiY2VUf3UuKrR4a9k5UMWYOtpDLsaXwATbcVCnI05hUS7L9ULQHWLZhyi3w==
- dependencies:
- "@types/html-minifier-terser" "^5.0.0"
- "@types/tapable" "^1.0.5"
- "@types/webpack" "^4.41.8"
- html-minifier-terser "^5.0.1"
- loader-utils "^1.2.3"
- lodash "^4.17.15"
- pretty-error "^2.1.1"
- tapable "^1.1.3"
- util.promisify "1.0.0"
-
html2canvas@1.0.0-alpha.12:
version "1.0.0-alpha.12"
resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.0.0-alpha.12.tgz#3b1992e3c9b3f56063c35fd620494f37eba88513"
@@ -11753,14 +11618,14 @@ humanize-ms@^1.2.1:
ms "^2.0.0"
husky@^4.2.3:
- version "4.2.5"
- resolved "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz#2b4f7622673a71579f901d9885ed448394b5fa36"
- integrity sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==
+ version "4.3.0"
+ resolved "https://registry.npmjs.org/husky/-/husky-4.3.0.tgz#0b2ec1d66424e9219d359e26a51c58ec5278f0de"
+ integrity sha512-tTMeLCLqSBqnflBZnlVDhpaIMucSGaYyX6855jM4AguGeWCeSzNdb1mfyWduTZ3pe3SJVvVWGL0jO1iKZVPfTA==
dependencies:
chalk "^4.0.0"
ci-info "^2.0.0"
compare-versions "^3.6.0"
- cosmiconfig "^6.0.0"
+ cosmiconfig "^7.0.0"
find-versions "^3.2.0"
opencollective-postinstall "^2.0.2"
pkg-dir "^4.2.0"
@@ -11868,7 +11733,7 @@ import-fresh@^2.0.0:
caller-path "^2.0.0"
resolve-from "^3.0.0"
-import-fresh@^3.0.0, import-fresh@^3.1.0:
+import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
version "3.2.1"
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==
@@ -12593,12 +12458,7 @@ is-wsl@^1.1.0:
resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d"
integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=
-is-wsl@^2.1.1:
- version "2.1.1"
- resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
- integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==
-
-is-wsl@^2.2.0:
+is-wsl@^2.1.1, is-wsl@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271"
integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==
@@ -12664,20 +12524,7 @@ istanbul-lib-coverage@^3.0.0:
resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
-istanbul-lib-instrument@^4.0.0:
- version "4.0.1"
- resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz#61f13ac2c96cfefb076fe7131156cc05907874e6"
- integrity sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==
- dependencies:
- "@babel/core" "^7.7.5"
- "@babel/parser" "^7.7.5"
- "@babel/template" "^7.7.4"
- "@babel/traverse" "^7.7.4"
- "@istanbuljs/schema" "^0.1.2"
- istanbul-lib-coverage "^3.0.0"
- semver "^6.3.0"
-
-istanbul-lib-instrument@^4.0.3:
+istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d"
integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==
@@ -14723,14 +14570,6 @@ minizlib@^1.2.1:
dependencies:
minipass "^2.9.0"
-minizlib@^2.1.0:
- version "2.1.0"
- resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz#fd52c645301ef09a63a2c209697c294c6ce02cf3"
- integrity sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==
- dependencies:
- minipass "^3.0.0"
- yallist "^4.0.0"
-
minizlib@^2.1.1:
version "2.1.2"
resolved "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
@@ -15625,15 +15464,7 @@ onetime@^5.1.0:
dependencies:
mimic-fn "^2.1.0"
-open@^7.0.2:
- version "7.0.3"
- resolved "https://registry.npmjs.org/open/-/open-7.0.3.tgz#db551a1af9c7ab4c7af664139930826138531c48"
- integrity sha512-sP2ru2v0P290WFfv49Ap8MF6PkzGNnGlAwHweB4WR4mr5d2d0woiCluUeJ218w7/+PmoBy9JmYgD5A4mLcWOFA==
- dependencies:
- is-docker "^2.0.0"
- is-wsl "^2.1.1"
-
-open@^7.0.3:
+open@^7.0.2, open@^7.0.3:
version "7.2.1"
resolved "https://registry.npmjs.org/open/-/open-7.2.1.tgz#07b0ade11a43f2a8ce718480bdf3d7563a095195"
integrity sha512-xbYCJib4spUdmcs0g/2mK1nKo/jO2T7INClWd/beL7PFkXRWgr8B23ssDHX/USPn2M2IjDR5UdpYs6I67SnTSA==
@@ -16551,7 +16382,12 @@ polished@^3.4.4:
dependencies:
"@babel/runtime" "^7.9.2"
-popper.js@^1.14.1, popper.js@^1.14.4, popper.js@^1.14.7:
+popper.js@1.16.1-lts:
+ version "1.16.1-lts"
+ resolved "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05"
+ integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==
+
+popper.js@^1.14.4, popper.js@^1.14.7:
version "1.16.1"
resolved "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
@@ -17920,10 +17756,10 @@ react-textarea-autosize@^8.1.1:
use-composed-ref "^1.0.0"
use-latest "^1.0.0"
-react-transition-group@^4.0.0, react-transition-group@^4.3.0:
- version "4.3.0"
- resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683"
- integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==
+react-transition-group@^4.0.0, react-transition-group@^4.4.0:
+ version "4.4.1"
+ resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
+ integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
dependencies:
"@babel/runtime" "^7.5.5"
dom-helpers "^5.0.1"
@@ -17953,9 +17789,9 @@ react-use@^12.2.0:
tslib "^1.10.0"
react-use@^15.3.3:
- version "15.3.3"
- resolved "https://registry.npmjs.org/react-use/-/react-use-15.3.3.tgz#f16de7a16286c446388e8bd99680952fc3dc9a95"
- integrity sha512-nYb94JbmDCaLZg3sOXmFW8HN+lXWxnl0caspXoYfZG1CON8JfLN9jMOyxRDUpm7dUq7WZ5mIept/ByqBQKJ0wQ==
+ version "15.3.4"
+ resolved "https://registry.npmjs.org/react-use/-/react-use-15.3.4.tgz#f853d310bd71f75b38900a8caa3db93f6dc6e872"
+ integrity sha512-cHq1dELW6122oi1+xX7lwNyE/ugZs5L902BuO8eFJCfn2api1KeuPVG1M/GJouVARoUf54S2dYFMKo5nQXdTag==
dependencies:
"@types/js-cookie" "2.2.6"
"@xobotyi/scrollbar-width" "1.9.5"
@@ -18894,16 +18730,7 @@ schema-utils@^1.0.0:
ajv-errors "^1.0.0"
ajv-keywords "^3.1.0"
-schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0:
- version "2.7.0"
- resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7"
- integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==
- dependencies:
- "@types/json-schema" "^7.0.4"
- ajv "^6.12.2"
- ajv-keywords "^3.4.1"
-
-schema-utils@^2.7.1:
+schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0, schema-utils@^2.7.1:
version "2.7.1"
resolved "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7"
integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==
@@ -20287,19 +20114,7 @@ tar@^4, tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
safe-buffer "^5.1.2"
yallist "^3.0.3"
-tar@^6.0.1:
- version "6.0.2"
- resolved "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz#5df17813468a6264ff14f766886c622b84ae2f39"
- integrity sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==
- dependencies:
- chownr "^2.0.0"
- fs-minipass "^2.0.0"
- minipass "^3.0.0"
- minizlib "^2.1.0"
- mkdirp "^1.0.3"
- yallist "^4.0.0"
-
-tar@^6.0.2:
+tar@^6.0.1, tar@^6.0.2:
version "6.0.5"
resolved "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f"
integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==
@@ -20397,16 +20212,7 @@ terser-webpack-plugin@^3.0.0:
terser "^4.8.0"
webpack-sources "^1.4.3"
-terser@^4.1.2, terser@^4.6.3:
- version "4.6.7"
- resolved "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72"
- integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==
- dependencies:
- commander "^2.20.0"
- source-map "~0.6.1"
- source-map-support "~0.5.12"
-
-terser@^4.8.0:
+terser@^4.1.2, terser@^4.6.3, terser@^4.8.0:
version "4.8.0"
resolved "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17"
integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==
@@ -21520,15 +21326,6 @@ watchpack-chokidar2@^2.0.0:
dependencies:
chokidar "^2.1.8"
-watchpack@^1.6.1:
- version "1.6.1"
- resolved "https://registry.npmjs.org/watchpack/-/watchpack-1.6.1.tgz#280da0a8718592174010c078c7585a74cd8cd0e2"
- integrity sha512-+IF9hfUFOrYOOaKyfaI7h7dquUIOgyEMoQMLA7OP5FxegKA2+XdXThAZ9TU2kucfhDH7rfMHs1oPYziVGWRnZA==
- dependencies:
- chokidar "^2.1.8"
- graceful-fs "^4.1.2"
- neo-async "^2.5.0"
-
watchpack@^1.7.4:
version "1.7.4"
resolved "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz#6e9da53b3c80bb2d6508188f5b200410866cd30b"
@@ -21660,9 +21457,9 @@ webpack-log@^2.0.0:
uuid "^3.3.2"
webpack-node-externals@^2.5.0:
- version "2.5.0"
- resolved "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-2.5.0.tgz#8d50f3289c71bc2b921a8da228e0b652acc503f1"
- integrity sha512-g7/Z7Q/gsP8GkJkKZuJggn6RSb5PvxW1YD5vvmRZIxaSxAzkqjfL5n9CslVmNYlSqBVCyiqFgOqVS2IOObCSRg==
+ version "2.5.2"
+ resolved "https://registry.npmjs.org/webpack-node-externals/-/webpack-node-externals-2.5.2.tgz#178e017a24fec6015bc9e672c77958a6afac861d"
+ integrity sha512-aHdl/y2N7PW2Sx7K+r3AxpJO+aDMcYzMQd60Qxefq3+EwhewSbTBqNumOsCE1JsCUNoyfGj5465N0sSf6hc/5w==
webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3:
version "1.4.3"
@@ -21679,36 +21476,7 @@ webpack-virtual-modules@^0.2.2:
dependencies:
debug "^3.0.0"
-webpack@^4.41.6:
- version "4.43.0"
- resolved "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz#c48547b11d563224c561dad1172c8aa0b8a678e6"
- integrity sha512-GW1LjnPipFW2Y78OOab8NJlCflB7EFskMih2AHdvjbpKMeDJqEgSx24cXXXiPS65+WSwVyxtDsJH6jGX2czy+g==
- dependencies:
- "@webassemblyjs/ast" "1.9.0"
- "@webassemblyjs/helper-module-context" "1.9.0"
- "@webassemblyjs/wasm-edit" "1.9.0"
- "@webassemblyjs/wasm-parser" "1.9.0"
- acorn "^6.4.1"
- ajv "^6.10.2"
- ajv-keywords "^3.4.1"
- chrome-trace-event "^1.0.2"
- enhanced-resolve "^4.1.0"
- eslint-scope "^4.0.3"
- json-parse-better-errors "^1.0.2"
- loader-runner "^2.4.0"
- loader-utils "^1.2.3"
- memory-fs "^0.4.1"
- micromatch "^3.1.10"
- mkdirp "^0.5.3"
- neo-async "^2.6.1"
- node-libs-browser "^2.2.1"
- schema-utils "^1.0.0"
- tapable "^1.1.3"
- terser-webpack-plugin "^1.4.3"
- watchpack "^1.6.1"
- webpack-sources "^1.4.1"
-
-webpack@^4.43.0:
+webpack@^4.41.6, webpack@^4.43.0:
version "4.44.1"
resolved "https://registry.npmjs.org/webpack/-/webpack-4.44.1.tgz#17e69fff9f321b8f117d1fda714edfc0b939cc21"
integrity sha512-4UOGAohv/VGUNQJstzEywwNxqX417FnjZgZJpJQegddzPmTvph37eBIRbRTfdySXzVtJXLJfbMN3mMYhM6GdmQ==
@@ -21782,9 +21550,9 @@ whatwg-fetch@^2.0.4:
integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==
whatwg-fetch@^3.0.0, whatwg-fetch@^3.4.0:
- version "3.4.0"
- resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.0.tgz#e11de14f4878f773fbebcde8871b2c0699af8b30"
- integrity sha512-rsum2ulz2iuZH08mJkT0Yi6JnKhwdw4oeyMjokgxd+mmqYSd9cPpOQf01TIWgjxG/U4+QR+AwKq6lSbXVxkyoQ==
+ version "3.4.1"
+ resolved "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz#e5f871572d6879663fa5674c8f833f15a8425ab3"
+ integrity sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==
whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
version "2.3.0"